一、整體介紹
UIWebView自iOS2就有,WKWebView從iOS8才有,毫無疑問WKWebView將逐步取代笨重的UIWebView。通過簡單的測試即可發現UIWebView占用過多內存,且內存峰值更是誇張。WKWebView網頁加載速度也有提升,但是並不像內存那樣提升那么多。下面列舉一些其它的優勢:
- 更多的支持HTML5的特性
- 官方宣稱的高達60fps的滾動刷新率以及內置手勢
- Safari相同的JavaScript引擎
- 將UIWebViewDelegate與UIWebView拆分成了14類與3個協議(官方文檔說明)
- 另外用的比較多的,增加加載進度屬性:
estimatedProgress
二、UIWebView使用說明
1 舉例:簡單的使用
UIWebView使用非常簡單,可以分為三步,也是最簡單的用法,顯示網頁
- (void)simpleExampleTest { // 1.創建webview,並設置大小,"20"為狀態欄高度 UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height - 20)]; // 2.創建請求 NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.cnblogs.com/mddblog/"]]; // 3.加載網頁 [webView loadRequest:request]; // 最后將webView添加到界面 [self.view addSubview:webView]; }
2 一些實用函數
- 加載函數。
- (void)loadRequest:(NSURLRequest *)request; - (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL; - (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;
UIWebView不僅可以加載HTML頁面,還支持pdf、word、txt、各種圖片等等的顯示。下面以加載mac桌面上的png圖片:/Users/coohua/Desktop/bigIcon.png為例
// 1.獲取url NSURL *url = [NSURL fileURLWithPath:@"/Users/coohua/Desktop/bigIcon.png"]; // 2.創建請求 NSURLRequest *request=[NSURLRequest requestWithURL:url]; // 3.加載請求 [self.webView loadRequest:request];
- 網頁導航刷新有關函數
// 刷新 - (void)reload; // 停止加載 - (void)stopLoading; // 后退函數 - (void)goBack; // 前進函數 - (void)goForward; // 是否可以后退 @property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack; // 是否可以向前 @property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward; // 是否正在加載 @property (nonatomic, readonly, getter=isLoading) BOOL loading;
3 代理協議使用:UIWebViewDelegate
一共有四個方法
/// 是否允許加載網頁,也可獲取js要打開的url,通過截取此url可與js交互 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString *urlString = [[request URL] absoluteString]; urlString = [urlString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSArray *urlComps = [urlString componentsSeparatedByString:@"://"]; NSLog(@"urlString=%@---urlComps=%@",urlString,urlComps); return YES; } /// 開始加載網頁 - (void)webViewDidStartLoad:(UIWebView *)webView { NSURLRequest *request = webView.request; NSLog(@"webViewDidStartLoad-url=%@--%@",[request URL],[request HTTPBody]); } /// 網頁加載完成 - (void)webViewDidFinishLoad:(UIWebView *)webView { NSURLRequest *request = webView.request; NSURL *url = [request URL]; if ([url.path isEqualToString:@"/normal.html"]) { NSLog(@"isEqualToString"); } NSLog(@"webViewDidFinishLoad-url=%@--%@",[request URL],[request HTTPBody]); NSLog(@"%@",[self.webView stringByEvaluatingJavaScriptFromString:@"document.title"]); } /// 網頁加載錯誤 - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { NSURLRequest *request = webView.request; NSLog(@"didFailLoadWithError-url=%@--%@",[request URL],[request HTTPBody]); }
4 與js交互
主要有兩方面:js執行OC代碼、oc調取寫好的js代碼
- js執行OC代碼:js是不能執行oc代碼的,但是可以變相的執行,js可以將要執行的操作封裝到網絡請求里面,然后oc攔截這個請求,獲取url里面的字符串解析即可,這里用到代理協議的
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType函數。 - oc調取寫好的js代碼:這里用到UIwebview的一個方法。示例代碼一個是網頁定位,一個是獲取網頁title:
// 實現自動定位js代碼, htmlLocationID為定位的位置(由js開發人員給出),實現自動定位代碼,應該在網頁加載完成之后再調用 NSString *javascriptStr = [NSString stringWithFormat:@"window.location.href = '#%@'",htmlLocationID]; // webview執行代碼 [self.webView stringByEvaluatingJavaScriptFromString:javascriptStr]; // 獲取網頁的title NSString *title = [self.webView stringByEvaluatingJavaScriptFromString:@"document.title"]
三、WKWebView使用說明
1 簡單使用
與UIWebview一樣,僅需三步:記住導入(#import <WebKit/WebKit.h>)
- (void)simpleExampleTest { // 1.創建webview,並設置大小,"20"為狀態欄高度 WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height - 20)]; // 2.創建請求 NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.cnblogs.com/mddblog/"]]; // 3.加載網頁 [webView loadRequest:request]; // 最后將webView添加到界面 [self.view addSubview:webView]; }
2 一些實用函數
- 加載網頁函數
相比UIWebview,WKWebView也支持各種文件格式,並新增了loadFileURL函數,顧名思義加載本地文件。
/// 模擬器調試加載mac本地文件 - (void)loadLocalFile { // 1.創建webview,並設置大小,"20"為狀態欄高度 WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height - 20)]; // 2.創建url userName:電腦用戶名 NSURL *url = [NSURL fileURLWithPath:@"/Users/userName/Desktop/bigIcon.png"]; // 3.加載文件 [webView loadFileURL:url allowingReadAccessToURL:url]; // 最后將webView添加到界面 [self.view addSubview:webView]; }
/// 其它三個加載函數 - (WKNavigation *)loadRequest:(NSURLRequest *)request; - (WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL; - (WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL;
- 網頁導航刷新相關函數
和UIWebview幾乎一樣,不同的是有返回值,WKNavigation(已更新),另外增加了函數reloadFromOrigin和goToBackForwardListItem。- reloadFromOrigin會比較網絡數據是否有變化,沒有變化則使用緩存,否則從新請求。
- goToBackForwardListItem:比向前向后更強大,可以跳轉到某個指定歷史頁面
@property (nonatomic, readonly) BOOL canGoBack; @property (nonatomic, readonly) BOOL canGoForward; - (WKNavigation *)goBack; - (WKNavigation *)goForward; - (WKNavigation *)reload; - (WKNavigation *)reloadFromOrigin; // 增加的函數 - (WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item; // 增加的函數 - (void)stopLoading;
- 一些常用屬性
- allowsBackForwardNavigationGestures:BOOL類型,是否允許左右划手勢導航,默認不允許
- estimatedProgress:加載進度,取值范圍0~1
- title:頁面title
- .scrollView.scrollEnabled:是否允許上下滾動,默認允許
- backForwardList:WKBackForwardList類型,訪問歷史列表,可以通過前進后退按鈕訪問,或者通過goToBackForwardListItem函數跳到指定頁面
3 代理協議使用
一共有三個代理協議:
- WKNavigationDelegate:最常用,和UIWebViewDelegate功能類似,追蹤加載過程,有是否允許加載、開始加載、加載完成、加載失敗。下面會對函數做簡單的說明,並用數字標出調用的先后次序:1-2-3-4-5
三個是否允許加載函數:
/// 接收到服務器跳轉請求之后調用 (服務器端redirect),不一定調用 - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation; /// 3 在收到服務器的響應頭,根據response相關信息,決定是否跳轉。decisionHandler必須調用,來決定是否跳轉,參數WKNavigationActionPolicyCancel取消跳轉,WKNavigationActionPolicyAllow允許跳轉 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler; /// 1 在發送請求之前,決定是否跳轉 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
追蹤加載過程函數:
/// 2 頁面開始加載 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation; /// 4 開始獲取到網頁內容時返回 - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation; /// 5 頁面加載完成之后調用 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation; /// 頁面加載失敗時調用 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation;
- WKScriptMessageHandler:必須實現的函數,是APP與js交互,提供從網頁中收消息的回調方法
/// message: 收到的腳本信息. - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
- WKUIDelegate:UI界面相關,原生控件支持,三種提示框:輸入、確認、警告。首先將web提示框攔截然后再做處理。
/// 創建一個新的WebView - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures; /// 輸入框 - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler; /// 確認框 - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler; /// 警告框 - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
四、示例代碼
- 代碼可以實現一般網絡顯示,加載本地文件(pdf、word、txt、圖片等等)
- 搜索框搜索界面,搜索框輸入
file://則加載本地文件,http://則加載網絡內容,如果兩者都不是則搜索輸入的關鍵字。 - 下部網絡導航,后退、前進、刷新、用Safari打開鏈接四個按鈕
/// 控件高度 #define kSearchBarH 44 #define kBottomViewH 44 /// 屏幕大小尺寸 #define kScreenWidth [UIScreen mainScreen].bounds.size.width #define kScreenHeight [UIScreen mainScreen].bounds.size.height #import "ViewController.h" #import <WebKit/WebKit.h> @interface ViewController () <UISearchBarDelegate, WKNavigationDelegate> @property (nonatomic, strong) UISearchBar *searchBar; /// 網頁控制導航欄 @property (weak, nonatomic) UIView *bottomView; @property (nonatomic, strong) WKWebView *wkWebView; @property (weak, nonatomic) UIButton *backBtn; @property (weak, nonatomic) UIButton *forwardBtn; @property (weak, nonatomic) UIButton *reloadBtn; @property (weak, nonatomic) UIButton *browserBtn; @property (weak, nonatomic) NSString *baseURLString; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // [self simpleExampleTest]; [self addSubViews]; [self refreshBottomButtonState]; [self.wkWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.cnblogs.com/mddblog/"]]]; } - (void)simpleExampleTest { // 1.創建webview,並設置大小,"20"為狀態欄高度 WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height - 20)]; // 2.創建請求 NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.cnblogs.com/mddblog/"]]; // // 3.加載網頁 [webView loadRequest:request]; // [webView loadFileURL:[NSURL fileURLWithPath:@"/Users/userName/Desktop/bigIcon.png"] allowingReadAccessToURL:[NSURL fileURLWithPath:@"/Users/userName/Desktop/bigIcon.png"]]; // 最后將webView添加到界面 [self.view addSubview:webView]; } /// 模擬器加載mac本地文件 - (void)loadLocalFile { // 1.創建webview,並設置大小,"20"為狀態欄高度 WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height - 20)]; // 2.創建url userName:電腦用戶名 NSURL *url = [NSURL fileURLWithPath:@"/Users/userName/Desktop/bigIcon.png"]; // 3.加載文件 [webView loadFileURL:url allowingReadAccessToURL:url]; // 最后將webView添加到界面 [self.view addSubview:webView]; } - (void)addSubViews { [self addBottomViewButtons]; [self.view addSubview:self.searchBar]; [self.view addSubview:self.wkWebView]; } - (void)addBottomViewButtons { // 記錄按鈕個數 int count = 0; // 添加按鈕 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; [button setTitle:@"后退" forState:UIControlStateNormal]; [button setTitleColor:[UIColor colorWithRed:249 / 255.0 green:102 / 255.0 blue:129 / 255.0 alpha:1.0] forState:UIControlStateNormal]; [button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateHighlighted]; [button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled]; [button.titleLabel setFont:[UIFont systemFontOfSize:15]]; button.tag = ++count; // 標記按鈕 [button addTarget:self action:@selector(onBottomButtonsClicled:) forControlEvents:UIControlEventTouchUpInside]; [self.bottomView addSubview:button]; self.backBtn = button; button = [UIButton buttonWithType:UIButtonTypeCustom]; [button setTitle:@"前進" forState:UIControlStateNormal]; [button setTitleColor:[UIColor colorWithRed:249 / 255.0 green:102 / 255.0 blue:129 / 255.0 alpha:1.0] forState:UIControlStateNormal]; [button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateHighlighted]; [button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled]; [button.titleLabel setFont:[UIFont systemFontOfSize:15]]; button.tag = ++count; [button addTarget:self action:@selector(onBottomButtonsClicled:) forControlEvents:UIControlEventTouchUpInside]; [self.bottomView addSubview:button]; self.forwardBtn = button; button = [UIButton buttonWithType:UIButtonTypeCustom]; [button setTitle:@"重新加載" forState:UIControlStateNormal]; [button setTitleColor:[UIColor colorWithRed:249 / 255.0 green:102 / 255.0 blue:129 / 255.0 alpha:1.0] forState:UIControlStateNormal]; [button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateHighlighted]; [button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled]; [button.titleLabel setFont:[UIFont systemFontOfSize:15]]; button.tag = ++count; [button addTarget:self action:@selector(onBottomButtonsClicled:) forControlEvents:UIControlEventTouchUpInside]; [self.bottomView addSubview:button]; self.reloadBtn = button; button = [UIButton buttonWithType:UIButtonTypeCustom]; [button setTitle:@"Safari" forState:UIControlStateNormal]; [button setTitleColor:[UIColor colorWithRed:249 / 255.0 green:102 / 255.0 blue:129 / 255.0 alpha:1.0] forState:UIControlStateNormal]; [button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateHighlighted]; [button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled]; [button.titleLabel setFont:[UIFont systemFontOfSize:15]]; button.tag = ++count; [button addTarget:self action:@selector(onBottomButtonsClicled:) forControlEvents:UIControlEventTouchUpInside]; [self.bottomView addSubview:button]; self.browserBtn = button; // 統一設置frame [self setupBottomViewLayout]; } - (void)setupBottomViewLayout { int count = 4; CGFloat btnW = 80; CGFloat btnH = 30; CGFloat btnY = (self.bottomView.bounds.size.height - btnH) / 2; // 按鈕間間隙 CGFloat margin = (self.bottomView.bounds.size.width - btnW * count) / count; CGFloat btnX = margin * 0.5; self.backBtn.frame = CGRectMake(btnX, btnY, btnW, btnH); btnX = self.backBtn.frame.origin.x + btnW + margin; self.forwardBtn.frame = CGRectMake(btnX, btnY, btnW, btnH); btnX = self.forwardBtn.frame.origin.x + btnW + margin; self.reloadBtn.frame = CGRectMake(btnX, btnY, btnW, btnH); btnX = self.reloadBtn.frame.origin.x + btnW + margin; self.browserBtn.frame = CGRectMake(btnX, btnY, btnW, btnH); } /// 刷新按鈕是否允許點擊 - (void)refreshBottomButtonState { if ([self.wkWebView canGoBack]) { self.backBtn.enabled = YES; } else { self.backBtn.enabled = NO; } if ([self.wkWebView canGoForward]) { self.forwardBtn.enabled = YES; } else { self.forwardBtn.enabled = NO; } } /// 按鈕點擊事件 - (void)onBottomButtonsClicled:(UIButton *)sender { switch (sender.tag) { case 1: { [self.wkWebView goBack]; [self refreshBottomButtonState]; } break; case 2: { [self.wkWebView goForward]; [self refreshBottomButtonState]; } break; case 3: [self.wkWebView reload]; break; case 4: [[UIApplication sharedApplication] openURL:self.wkWebView.URL]; break; default: break; } } #pragma mark - WKWebView WKNavigationDelegate 相關 /// 是否允許加載網頁 在發送請求之前,決定是否跳轉 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSString *urlString = [[navigationAction.request URL] absoluteString]; urlString = [urlString stringByRemovingPercentEncoding]; // NSLog(@"urlString=%@",urlString); // 用://截取字符串 NSArray *urlComps = [urlString componentsSeparatedByString:@"://"]; if ([urlComps count]) { // 獲取協議頭 NSString *protocolHead = [urlComps objectAtIndex:0]; NSLog(@"protocolHead=%@",protocolHead); } decisionHandler(WKNavigationActionPolicyAllow); } #pragma mark - searchBar代理方法 /// 點擊搜索按鈕 - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { // 創建url NSURL *url = nil; NSString *urlStr = searchBar.text; // 如果file://則為打開bundle本地文件,http則為網站,否則只是一般搜索關鍵字 if([urlStr hasPrefix:@"file://"]){ NSRange range = [urlStr rangeOfString:@"file://"]; NSString *fileName = [urlStr substringFromIndex:range.length]; url = [[NSBundle mainBundle] URLForResource:fileName withExtension:nil]; // 如果是模擬器加載電腦上的文件,則用下面的代碼 // url = [NSURL fileURLWithPath:fileName]; }else if(urlStr.length>0){ if ([urlStr hasPrefix:@"http://"]) { url=[NSURL URLWithString:urlStr]; } else { urlStr=[NSString stringWithFormat:@"http://www.baidu.com/s?wd=%@",urlStr]; } urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; url=[NSURL URLWithString:urlStr]; } NSURLRequest *request=[NSURLRequest requestWithURL:url]; // 加載請求頁面 [self.wkWebView loadRequest:request]; } #pragma mark - 懶加載 - (UIView *)bottomView { if (_bottomView == nil) { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, kScreenHeight - kBottomViewH, kScreenWidth, kBottomViewH)]; view.backgroundColor = [UIColor colorWithRed:230/255.0 green:230/255.0 blue:230/255.0 alpha:1]; [self.view addSubview:view]; _bottomView = view; } return _bottomView; } - (UISearchBar *)searchBar { if (_searchBar == nil) { UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 20, kScreenWidth, kSearchBarH)]; searchBar.delegate = self; searchBar.text = @"http://www.cnblogs.com/mddblog/"; _searchBar = searchBar; } return _searchBar; } - (WKWebView *)wkWebView { if (_wkWebView == nil) { WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 20 + kSearchBarH, kScreenWidth, kScreenHeight - 20 - kSearchBarH - kBottomViewH)]; webView.navigationDelegate = self; // webView.scrollView.scrollEnabled = NO; // webView.backgroundColor = [UIColor colorWithPatternImage:self.image]; // 允許左右划手勢導航,默認允許 webView.allowsBackForwardNavigationGestures = YES; _wkWebView = webView; } return _wkWebView; } @end
五、UIWebView 和 WKWebView的性能分析
iOS 8 推出 WKWebView 以及相關特性,告訴開發者提高多么多么大的性能,實測告訴你WKWebView的性能有多好!
為了驗證,我在項目中的分別 使用UIWebView 和 WKWebView 測試,來回加載十多個網頁 產生的內存狀況如下:
UIWebView
對比發現 WKWebview 這貨 在persistent 常駐內存 使用得少多了,想想看,一個app去哪兒省下幾十M的內存,再加上用戶量分布
iOS 9 60%
iOS 8 30%
iOS 7 6%左右
所以我建議能換WKWebview 就換吧,省下一大塊內存減少不必要的異常bug。
iOSH5性能監控技術角度分析
性能數據了解
分析移動端H5性能數據,首先我們說說是哪些性能數據。
- 白屏時間,白屏時間無論安卓還是iOS在加載網頁的時候都會存在的問題,也是目前無法解決的;
- 頁面耗時,頁面耗時指的是開始加載這個網頁到整個頁面load完成即渲染完成的時間;
- 加載鏈接的一些性能數據,重定向時間,DNS解析時間,TCP鏈接時間,request請求時間,response響應時間,dom節點解析時間,page渲染時間,同時我們還需要抓取資源時序數據,
什么是資源時序數據呢?每個網頁是有很多個資源組成的,有.js、.png、.css、.script等等,我們就需要將這些每個資源鏈接的耗時拿到,是什么類型的資源,完整鏈接;對於客戶來說有了這些還不夠,還需要JS錯誤,頁面的ajax請求。JS錯誤獲取的當然是堆棧信息和錯誤類型。ajax請求一般是獲取三個時間,響應時間,ajax下載時間,ajax回調時間。
上面分析的是能夠獲取到移動端H5的性能數據,這些數據怎么得到就是接下來要講的了。數據獲取是需要js來做的,都知道移動端是通過webView來加載網頁的,js里面能通過performance這個接口來從webView內部api獲取上面的那些數據,js獲取的數據然后發給OC;那JS怎么樣才能拿到這些數據呢,這就是最關鍵的,OC代碼如何寫才能讓JS獲取數據。
代碼編寫
有兩種方法可以得到數據,先介紹用NSURLProtocol這個類獲取數據。
iOS的UIWebView加載網頁的時候會走進NSURLProtocol這個類,所以我們就需要在這個類里面作文章了,我們先用UIWebView加載一個鏈接,例如百度等等,然后創建一個繼承NSURLProtocol的類。
NSURLProtocol里面有很多方法,就不一一介紹了,有一個startLoading的方法,我們在這個方法里面用NSURLConnection發送一個請求,設置代理,請求的request就是加載網頁的request,因為NSURLProtocol有一個NSURLRequest的屬性。
- (instancetype)initWithRequest:(NSURLRequest*)request delegate:(id)delegate startImmediately:(BOOL)startImmediately
這個就是請求的方法。
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
通過NSURLConnection設置代理,就需要寫出代理方法,在其中一個代理方法里面能獲得網頁的代碼,這就是我們最關鍵的地方,就是上面這個方法,將data用utf-8轉碼就會得到代碼。
得到網頁的代碼有什么用呢?熟悉網頁端代碼的都知道,網頁端的代碼都是由很多標簽組成,我們先找到<head>這個標簽,在下面插入一個<script>標簽,里面放入js代碼,這個js代碼就是用來獲取上面介紹的性能數據。
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
上面會用到很多這個方法,為什么要用這個呢,因為你注入了新的代碼,你需要將這個新的網頁代碼用這個方法加載一下,不然網頁會加載不出來的。
最后只需要將MonitorURLProtocol在appDelegate里面注冊一下就可以了。
以上是通過NSURLProtocol這個類注入獲取數據的代碼,將JS代碼注入后就需要JS將數據發送給我們,可以通過交互這些方式,接下來我就介紹一種直白的交互,大多數的交互也是這么做的,只不過封裝了,考慮的情況更多,我就只是根據實際情況來做了。
OC與JS交互獲取數據
交互都會有一個標識,OC傳一個標識給JS,JS通過標識判斷,發送給你想要的數據。我首先是通過運行時動態加載的方法將加載鏈接的三個方法給hook,進入同一個我定義的方法,然后在這個方法里面傳標識給JS。
將標識和block作為鍵值對存起來,然后將JS將數據用url的形式發過來,我們取出來,匹配一下對應相應的標識,然后用block回調相應的數據,相應的代碼這里就不貼了,最后我會給github上代碼的鏈接。接受url就是用UIWebView的代理方法。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
{
[WebViewTracker webView:webView withUrl:request.URL];
return YES;
}
我們還可以在didFinishLoad里面手動調用JS代碼來獲取獲取數據,和NSURLProtocol結合起來用,各有各的優缺點,一個是在頂部注入代碼,一個是在尾部調用代碼,都是會丟失一部分數據,所以最好的辦法就是結合起來用。
以上是自己獲取這些數據,如果是做成SDK監控APP獲取這些數據,就不一樣了,需要去hookUIWebView的代理方法,hook靜態庫的方法和普通的運行時加載是不一樣的,這個我可以在以后的文章里面介紹。
WKWebView
WKWebView是iOS8.0以后出來的,目前使用的人還不是很多,但是這個比UIWebView性能方面好很多,從內核到內存優化等等,但是由於還有iOS7以下的用戶,所以面使用的人不多,但是這個是趨勢。WKWebView獲取上面的性能數據是一模一樣的,不同的是WKWebView不會走進NSURLProtocol,所以實在didFinishLoad里面手動調用,這里就不詳細說了,直接給代碼鏈接
https://github.com/LonelyWise/WebViewMonitor
WKWebView 和 UIWebView 允許背景音樂自動播放
WKWebView
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; config.allowsInlineMediaPlayback = YES; config.mediaPlaybackRequiresUserAction = false; wkWebView=[[WKWebView alloc] initWithFrame:rect configuration:config]; wkWebView.UIDelegate=self; wkWebView.navigationDelegate=self;
UIWebVIew
[self.webView setMediaPlaybackRequiresUserAction:NO];
