一吉嫩、Native開發(fā)中為什么需要H5容器
Native開發(fā)原生應用是手機操作系統(tǒng)廠商(目前主要是蘋果的iOS和google的Android)對外界提供的標準化的開發(fā)模式,他們對于native開發(fā)提供了一套標準化實現和優(yōu)化方案浪谴。但是他們存在一些硬傷,比如App的發(fā)版周期偏長、有時無法跟上產品的更新節(jié)奏柑蛇;靈活性差,如果有較大的方案變更驱闷,需要發(fā)版才能解決耻台;如果存在bug,在當前版本修復的難度比較大(iOS的JSPatch方案和Android的Dex修復方案);需要根據不同的平臺寫不同的代碼空另,iOS主要為object_c和swift盆耽,android為Java。
而作為H5為主要開發(fā)模式的Web App的靈活性就比較強扼菠,他利用操作系統(tǒng)中的h5容器作為一個承載摄杂,對外提供一個url鏈接,而該url鏈接對應的內容可以實時在服務端進行修改娇豫,靈活行很強匙姜,避免了Native發(fā)版周期帶來的時間成本。但是h5雖然靈活冯痢,但是他也有自己的硬傷氮昧。每次都需要下載完整的UI數據(html,css浦楣,js)袖肥,弱網用戶體驗較差,流量消耗較大振劳;無法調用系統(tǒng)文件系統(tǒng)椎组,硬件資源等等;
Native App和Web App都有他們的優(yōu)勢和劣勢历恐。我們也不能一棍子拍死說誰好誰劣寸癌。通常的經驗是:對于一些比較穩(wěn)當的業(yè)務,對用戶體驗要求較高的弱贼,我們可以選擇Native開發(fā)蒸苇。而對于一些業(yè)務變更比較快、處在不斷試水的過程吮旅,而且不涉及調用文件系統(tǒng)和硬件調用的業(yè)務我們可以選擇h5開發(fā)溪烤。所以說,在一款app中我們需要同時支持Native代碼和h5代碼。這也是我們標題所說的Native開發(fā)中需要H5容器的必要性檬嘀。
iOS存在的h5容器主要包括UIWebView和WKWebView槽驶,下面我們就分別來說說他們的用法和優(yōu)劣。
二鸳兽、UIWebView的基本用法
2.1掂铐、加載網頁
UIWebView*webView = [[UIWebViewalloc] initWithFrame:self.view.bounds];? ? webView.delegate =self;? ? [self.view addSubview:webView];//網絡地址NSURL*url = [[NSURLalloc] initWithString:@"http://www.taobao.com"];NSURLRequest*request = [NSURLRequestrequestWithURL:url];? ? [webView loadRequest:request];
2.2、UIWebViewDelegate幾個常用的代理方法
//進行加載前的預判斷贸铜,如果返回YES堡纬,則會進入后續(xù)流程(StartLoad,FinishLoad)聂受。如果返回NO蒿秦,這不會進入后續(xù)流程。- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType;//開始加載網頁- (void)webViewDidStartLoad:(UIWebView*)webView;//加載完成- (void)webViewDidFinishLoad:(UIWebView*)webView;//加載失敗- (void)webView:(UIWebView*)webView didFailLoadWithError:(nullableNSError*)error;
2.3蛋济、Native調用JS中的方法
比如我們在加載的HTML文件中有如下js代碼:
functionhello(){? ? alert("你好棍鳖!");}functionhelloWithName(name){? ? alert(name +",你好碗旅!");}
我們可以調用- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
函數進行js調用渡处。
[webView stringByEvaluatingJavaScriptFromString:@"hello()"];[webView stringByEvaluatingJavaScriptFromString:@"helloWithName('jack')"];
js代碼不一定要在js文件中預留,也可以在代碼中通過字符串的形式進行調用祟辟,比如下面:
//自定義js函數NSString*jsString =@"function sayHello(){ \
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? alert('jack11')? \
? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? \
? ? ? ? ? ? ? ? ? ? ? ? ? sayHello()";? ? [_webView stringByEvaluatingJavaScriptFromString:jsString];NSString*jsString =@" var p = document.createElement('p'); \
? ? ? ? ? ? ? ? ? ? ? ? ? ? p.innerText = 'New Line';? ? ? ? ? ? \
? ? ? ? ? ? ? ? ? ? ? ? ? ? document.body.appendChild(p);? ? ? ? \
? ? ";? ? [_webView stringByEvaluatingJavaScriptFromString:jsString];
2.4医瘫、JS中調用Naitve的方法
具體讓js通知native進行方法調用,我們可以讓js產生一個特殊的請求旧困〈挤荩可以讓Native代碼可以攔截到,而且不然用戶察覺吼具。業(yè)界一般的實現方案是在網頁中加載一個隱藏的iframe來實現該功能僚纷。通過將iframe的src指定為一個特殊的URL,實現在- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
方案中進行攔截處理拗盒。對應的js調用代碼如下:
functionloadURL(url){variFrame;? ? ? ? iFrame =document.createElement("iframe");? ? ? ? iFrame.setAttribute("src", url);? ? ? ? iFrame.setAttribute("style","display:none;");? ? ? ? iFrame.setAttribute("height","0px");? ? ? ? iFrame.setAttribute("width","0px");? ? ? ? iFrame.setAttribute("frameborder","0");document.body.appendChild(iFrame);// 發(fā)起請求后這個iFrame就沒用了怖竭,所以把它從dom上移除掉iFrame.parentNode.removeChild(iFrame);? ? ? ? iFrame =null;? ? }
比如我們在js代碼中,調用一下兩個js方法:
functioniOS_alert(){//調用自定義對話框loadURL("alert://abc");? ? }functioncall(){//? js中進行撥打電話處理loadURL("tel://17715022071");? ? }
當你觸發(fā)以上方法的時候陡蝇,就會進入webview的代理方法中進行攔截痊臭。
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType{NSURL* url = [request URL];if([[url scheme] isEqualToString:@"alert"]) {//攔截請求,彈出自定義對話框UIAlertView* alertView = [[UIAlertViewalloc] initWithTitle:@"test"message:[url host] delegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil];? ? ? ? [alertView show];returnNO;? ? }elseif([[url scheme] isEqualToString:@"tel"]){//攔截撥打電話請求BOOLresult = [[UIApplicationsharedApplication] openURL:url];if(!result) {NSLog(@"您的設備不支持打電話");? ? ? ? }else{NSLog(@"電話打了");? ? ? ? }returnNO;? ? }returnYES;}
這樣我們就可以讓js進行native的調用登夫。
三广匙、WKWebView的基本用法
3.1、加載網頁
WKWebView*webView = [[WKWebViewalloc] initWithFrame:[UIScreenmainScreen].bounds];NSURL*url = [NSURLURLWithString:@"http://www.taobao.com"];NSURLRequest*request = [NSURLRequestrequestWithURL:url];? ? [webView loadRequest:request];? ? [self.view addSubview:webView];
3.2悼嫉、幾個常用的代理方法
/**
*? 根據webView艇潭、navigationAction相關信息決定這次跳轉是否可以繼續(xù)進行,這些信息包含HTTP發(fā)送請求,如頭部包含User-Agent,Accept,refer
*? 在發(fā)送請求之前,決定是否跳轉的代理
*? @param webView
*? @param navigationAction
*? @param decisionHandler
*/- (void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void(^)(WKNavigationActionPolicy))decisionHandler{? ? decisionHandler(WKNavigationActionPolicyAllow);}/**
*? 這個代理方法表示當客戶端收到服務器的響應頭蹋凝,根據response相關信息鲁纠,可以決定這次跳轉是否可以繼續(xù)進行。
*? 在收到響應后鳍寂,決定是否跳轉的代理
*? @param webView
*? @param navigationResponse
*? @param decisionHandler
*/- (void)webView:(WKWebView*)webView decidePolicyForNavigationResponse:(WKNavigationResponse*)navigationResponse decisionHandler:(void(^)(WKNavigationResponsePolicy))decisionHandler{? ? decisionHandler(WKNavigationResponsePolicyAllow);}/**
*? 準備加載頁面改含。等同于UIWebViewDelegate: - webView:shouldStartLoadWithRequest:navigationType
*
*? @param webView
*? @param navigation
*/- (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(null_unspecifiedWKNavigation*)navigation{}/**
*? 這個代理是服務器redirect時調用
*? 接收到服務器跳轉請求的代理
*? @param webView
*? @param navigation
*/- (void)webView:(WKWebView*)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecifiedWKNavigation*)navigation{? ? }- (void)webView:(WKWebView*)webView didFailProvisionalNavigation:(null_unspecifiedWKNavigation*)navigation withError:(NSError*)error{? ? }/**
*? 內容開始加載. 等同于UIWebViewDelegate: - webViewDidStartLoad:
*
*? @param webView
*? @param navigation
*/- (void)webView:(WKWebView*)webView didCommitNavigation:(null_unspecifiedWKNavigation*)navigation{? ? }/**
*? 頁面加載完成。 等同于UIWebViewDelegate: - webViewDidFinishLoad:
*
*? @param webView
*? @param navigation
*/- (void)webView:(WKWebView*)webView didFinishNavigation:(null_unspecifiedWKNavigation*)navigation{? ? }/**
*? 頁面加載失敗迄汛。 等同于UIWebViewDelegate: - webView:didFailLoadWithError:
*
*? @param webView
*? @param navigation
*? @param error? ? ?
*/- (void)webView:(WKWebView*)webView didFailNavigation:(null_unspecifiedWKNavigation*)navigation withError:(NSError*)error{? ? }- (void)webViewWebContentProcessDidTerminate:(WKWebView*)webViewNS_AVAILABLE(10_11,9_0){? ? }/*
我們看看WKUIDelegate的幾個代理方法捍壤,雖然不是必須實現的,但是如果我們的頁面中有調用了js的alert鞍爱、confirm鹃觉、prompt方法,我們應該實現下面這幾個代理方法睹逃,然后在原來這里調用native的彈出窗盗扇,因為使用WKWebView后,HTML中的alert沉填、confirm疗隶、prompt方法調用是不會再彈出窗口了,只是轉化成ios的native回調代理方法
*/#pragma mark - WKUIDelegate- (void)webView:(WKWebView*)webView runJavaScriptAlertPanelWithMessage:(NSString*)message initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void(^)(void))completionHandler{UIAlertController*alertView = [UIAlertControlleralertControllerWithTitle:@"h5Container"message:message preferredStyle:UIAlertControllerStyleAlert];//? ? [alertView addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {//? ? ? ? textField.textColor = [UIColor redColor];//? ? }];[alertView addAction:[UIAlertActionactionWithTitle:@"我很確定"style:UIAlertActionStyleDefaulthandler:^(UIAlertAction* _Nonnull action) {? ? ? ? completionHandler();? ? }]];? ? [selfpresentViewController:alertView animated:YEScompletion:nil];}
顯然WKWebView的代理方法提供了比UIWebView顆粒度更細的方法翼闹。讓開發(fā)者可以進行更加細致的配置和處理斑鼻。
3.3 、Native調用JS中的方法
WKWebView提供的調用js代碼的函數是:
- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void(^ __nullable)(__nullableid,NSError* __nullableerror))completionHandler;
比如我們在加載的HTML文件中有如下js代碼:
functionhello(){? ? alert("你好猎荠!");}functionhelloWithName(name){? ? alert(name +"坚弱,你好!");}
我們可以調用如下代碼進行js的調用:
[_wkView evaluateJavaScript:@"hello()"completionHandler:^(iditem,NSError* error) {? ? ? }];? ? [_wkView evaluateJavaScript:@"helloWithName('jack')"completionHandler:^(iditem,NSError*error) {? ? ? }];
同UIWebView一樣法牲,我們也可以通過字符串的形式進行js調用史汗。
NSString*jsString =@"function sayHello(){ \
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? alert('jack11')? \
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? \
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? sayHello()";? ? [_wkView evaluateJavaScript:jsString completionHandler:^(iditem,NSError*error) {? ? ? ? ? ? }];? ? ? ? jsString =@" var p = document.createElement('p'); \
? ? p.innerText = 'New Line';? ? ? ? ? ? \
? ? document.body.appendChild(p);? ? ? ? \
? ? ";? ? [_wkView evaluateJavaScript:jsString completionHandler:^(iditem,NSError*error) {? ? ? ? ? ? }];
3.4、JS中調用Naitve的方法
除了和UIWebView加載一個隱藏的ifame之外拒垃,WKWebView自身還提供了一套js調用native的規(guī)范停撞。
我們可以在初始化WKWebView的時候,給他設置一個config參數悼瓮。
//高端配置//創(chuàng)建配置WKWebViewConfiguration*config = [[WKWebViewConfigurationalloc] init];//創(chuàng)建UserContentController(提供javaScript向webView發(fā)送消息的方法)WKUserContentController*userContent = [[WKUserContentControlleralloc] init];//添加消息處理戈毒,注意:self指代的是需要遵守WKScriptMessageHandler協議,結束時需要移除[userContent addScriptMessageHandler:selfname:@"NativeMethod"];//將UserContentController設置到配置文件中config.userContentController = userContent;//高端的自定義配置創(chuàng)建WKWebView_wkView = [[YXWKView alloc] initWithFrame:self.view.bounds configuration:config];NSURL*url = [NSURLURLWithString:@"http://localhost:8080/myDiary/index.html"];NSURLRequest*request = [NSURLRequestrequestWithURL:url];? ? [_wkView loadRequest:request];? ? _wkView.UIDelegate =self;? ? _wkView.navigationDelegate =self;? ? [self.view addSubview:_wkView];
我們在js可以通過NativeMethod這個Handler讓js代碼調用native横堡。
比如在js代碼中埋市,我新增了一個方法
functioninvokeNativeMethod(){window.webkit.messageHandlers.NativeMethod.postMessage("我要調用native的方法");? ? }
觸發(fā)以上方法的時候,會在native以下方法中進行攔截處理命贴。
#pragma mark - WKScriptMessageHandler- (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message{//這里就是使用高端配置道宅,js調用native的處理地方食听。我們可以根據name和body,進行橋協議的處理污茵。NSString*messageName = message.name;if([@"NativeMethod"isEqualToString:messageName]) {idmessageBody = message.body;NSLog(@"%@",messageBody);? ? }}
四樱报、UIWebView和WKWebView的比較和選擇
WKWebView是蘋果在WWDC2014發(fā)布會中發(fā)布IOS8的時候公布WebKit時候使用的新型的H5容器。它與UIWebView相比較泞当,擁有更快的加載速度和性能迹蛤,更低的內存占用。將UIWebViewDelegate和UIWebView重構成了14個類襟士,3個協議盗飒,可以讓開發(fā)者進行更加細致的配置。
但是他有一個最致命的缺陷陋桂,就是WKWebView的請求不能被NSURLProtocol截獲逆趣。而我們團隊開發(fā)的app中對于H5容器最佳的優(yōu)化點主要就在于使用NSURLProtocol技術對于H5進行離線包的處理和H5的圖片和Native的圖片公用一套緩存的技術。因為該問題的存在章喉,目前我們團隊還沒有使用WKWebView代替UIWebVIew汗贫。
本文轉自鏈接:http://www.reibang.com/p/84a6b1ac974a