2023年7月14日发(作者:)
iOSH5容器的⼀些探究(⼀):UIWebView和WKWebView的⽐较和选择⼀、Native开发中为什么需要H5容器Native开发原⽣应⽤是⼿机操作系统⼚商(⽬前主要是苹果的iOS和google的Android)对外界提供的标准化的开发模式,他们对于native开发提供了⼀套标准化实现和优化⽅案。但是他们存在⼀些硬伤,⽐如App的发版周期偏长、有时⽆法跟上产品的更新节奏;灵活性差,如果有较⼤的⽅案变更,需要发版才能解决;如果存在bug,在当前版本修复的难度⽐较⼤(iOS的JSPatch⽅案和Android的Dex修复⽅案);需要根据不同的平台写不同的代码,iOS主要为object_c和swift,android为Java。⽽作为H5为主要开发模式的Web App的灵活性就⽐较强,他利⽤操作系统中的h5容器作为⼀个承载,对外提供⼀个url链接,⽽该url链接对应的内容可以实时在服务端进⾏修改,灵活⾏很强,避免了Native发版周期带来的时间成本。但是h5虽然灵活,但是他也有⾃⼰的硬伤。每次都需要下载完整的UI数据(html,css,js),弱⽹⽤户体验较差,流量消耗较⼤;⽆法调⽤系统⽂件系统,硬件资源等等;Native App和Web App都有他们的优势和劣势。我们也不能⼀棍⼦拍死说谁好谁劣。通常的经验是:对于⼀些⽐较稳当的业务,对⽤户体验要求较⾼的,我们可以选择Native开发。⽽对于⼀些业务变更⽐较快、处在不断试⽔的过程,⽽且不涉及调⽤⽂件系统和硬件调⽤的业务我们可以选择h5开发。所以说,在⼀款app中我们需要同时⽀持Native代码和h5代码。这也是我们标题所说的Native开发中需要H5容器的必要性。iOS存在的h5容器主要包括UIWebView和WKWebView,下⾯我们就分别来说说他们的⽤法和优劣。⼆、UIWebView的基本⽤法2.1、加载⽹页 UIWebView *webView = [[UIWebView alloc] initWithFrame:]; te = self; [ addSubview:webView]; //⽹络地址 NSURL *url = [[NSURL alloc] initWithString:@""]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [webView loadRequest:request];2.2、UIWebViewDelegate⼏个常⽤的代理⽅法//进⾏加载前的预判断,如果返回YES,则会进⼊后续流程(StartLoad,FinishLoad)。如果返回NO,这不会进⼊后续流程。- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;//开始加载⽹页- (void)webViewDidStartLoad:(UIWebView *)webView;//加载完成- (void)webViewDidFinishLoad:(UIWebView *)webView;//加载失败- (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)error;2.3、Native调⽤JS中的⽅法⽐如我们在加载的HTML⽂件中有如下js代码:我们可以调⽤- (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 = Element('p'); ext = 'New Line'; Child(p); "; [_webView stringByEvaluatingJavaScriptFromString:jsString];2.4、JS中调⽤Naitve的⽅法具体让js通知native进⾏⽅法调⽤,我们可以让js产⽣⼀个特殊的请求。可以让Native代码可以拦截到,⽽且不然⽤户察觉。业界⼀般的实现⽅案是在⽹页中加载⼀个隐藏的iframe来实现该功能。通过将iframe的src指定为⼀个特殊的URL,实现在- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
⽅案中进⾏拦截处理。对应的js调⽤代码如下: function loadURL(url) { var iFrame; iFrame = Element("iframe"); ribute("src", url); ribute("style", "display:none;"); ribute("height", "0px"); ribute("width", "0px"); ribute("frameborder", "0"); Child(iFrame); // 发起请求后这个iFrame就没⽤了,所以把它从dom上移除掉 Child(iFrame); iFrame = null; }⽐如我们在js代码中,调⽤⼀下两个js⽅法: function iOS_alert() {//调⽤⾃定义对话框 loadURL("alert://abc"); } function call() {// js中进⾏拨打电话处理 loadURL("tel://"); }当你触发以上⽅法的时候,就会进⼊webview的代理⽅法中进⾏拦截。- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ NSURL * url = [request URL]; if ([[url scheme] isEqualToString:@"alert"]) {//拦截请求,弹出⾃定义对话框 UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"test" message:[url host] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alertView show]; return NO; }else if([[url scheme] isEqualToString:@"tel"]){//拦截拨打电话请求 BOOL result = [[UIApplication sharedApplication] openURL:url]; if (!result) { NSLog(@"您的设备不⽀持打电话"); } else { NSLog(@"电话打了"); } return NO; }
return YES;}这样我们就可以让js进⾏native的调⽤。三、WKWebView的基本⽤法3.1、加载⽹页 WKWebView *webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds]; NSURL *url = [NSURL URLWithString:@""]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [webView loadRequest:request]; [ addSubview:webView];3.2、⼏个常⽤的代理⽅法/** * 根据webView、navigationAction相关信息决定这次跳转是否可以继续进⾏,这些信息包含HTTP发送请求,如头部包含User-Agent,Accept,refer * 在发送请求之前,决定是否跳转的代理 * @param webView * @param navigationAction * @param decisionHandler */- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))d decisionHandler(WKNavigationActionPolicyAllow);}/** * 这个代理⽅法表⽰当客户端收到服务器的响应头,根据response相关信息,可以决定这次跳转是否可以继续进⾏。 * 在收到响应后,决定是否跳转的代理 * @param webView * @param navigationResponse * @param decisionHandler */- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationRe- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNaviga decisionHandler(WKNavigationResponsePolicyAllow);}/** * 准备加载页⾯。等同于UIWebViewDelegate: - webView:shouldStartLoadWithRequest:navigationType * * @param webView * @param navigation */- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{}/** * 这个代理是服务器redirect时调⽤ * 接收到服务器跳转请求的代理 * @param webView * @param navigation */- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
}- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
}/** * 内容开始加载. 等同于UIWebViewDelegate: - webViewDidStartLoad: * * @param webView * @param navigation */- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation{
}/** * 页⾯加载完成。 等同于UIWebViewDelegate: - webViewDidFinishLoad: * * @param webView * @param navigation */- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
}/** * 页⾯加载失败。 等同于UIWebViewDelegate: - webView:didFailLoadWithError: * * @param webView * @param navigation * @param error
*/- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
}- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0){
}/* */#pragma mark - WKUIDelegate 我们看看WKUIDelegate的⼏个代理⽅法,虽然不是必须实现的,但是如果我们的页⾯中有调⽤了js的alert、confirm、prompt⽅法,我们应该实现下⾯这⼏个代理⽅法,然后在原- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)( UIAlertController *alertView = [UIAlertController alertControllerWithTitle:@"h5Container" message:message preferredStyle:UIAlertControllerStyleAlert];// [alertView addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {// lor = [UIColor redColor];// }]; [alertView addAction:[UIAlertAction actionWithTitle:@"我很确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { completionHandler(); completionHandler(); }]]; [self presentViewController:alertView animated:YES completion:nil];}显然WKWebView的代理⽅法提供了⽐UIWebView颗粒度更细的⽅法。让开发者可以进⾏更加细致的配置和处理。3.3 、Native调⽤JS中的⽅法WKWebView提供的调⽤js代码的函数是:- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;⽐如我们在加载的HTML⽂件中有如下js代码:我们可以调⽤如下代码进⾏js的调⽤:[_wkView evaluateJavaScript:@"hello()" completionHandler:^(id item, NSError * error) {
}];
[_wkView evaluateJavaScript:@"helloWithName('jack')" completionHandler:^(id item, NSError *error) {
}];同UIWebView⼀样,我们也可以通过字符串的形式进⾏js调⽤。 NSString *jsString = @"function sayHello(){ alert('jack11') } sayHello()"; [_wkView evaluateJavaScript:jsString completionHandler:^(id item, NSError *error) {
}];
jsString = @" var p = Element('p'); ext = 'New Line'; Child(p); "; [_wkView evaluateJavaScript:jsString completionHandler:^(id item, NSError *error) {
}];3.4、JS中调⽤Naitve的⽅法除了和UIWebView加载⼀个隐藏的ifame之外,WKWebView⾃⾝还提供了⼀套js调⽤native的规范。我们可以在初始化WKWebView的时候,给他设置⼀个config参数。//⾼端配置 //创建配置 WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; //创建UserContentController(提供javaScript向webView发送消息的⽅法) WKUserContentController *userContent = [[WKUserContentController alloc] init]; //添加消息处理,注意:self指代的是需要遵守WKScriptMessageHandler协议,结束时需要移除 [userContent addScriptMessageHandler:self name:@"NativeMethod"]; //将UserContentController设置到配置⽂件中 ntentController = userContent; //⾼端的⾃定义配置创建WKWebView _wkView = [[YXWKView alloc] initWithFrame: configuration:config]; NSURL *url = [NSURL URLWithString:@"localhost:8080/myDiary/"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [_wkView loadRequest:request]; _gate = self; _tionDelegate = self; [ addSubview:_wkView];我们在js可以通过NativeMethod这个Handler让js代码调⽤native。⽐如在js代码中,我新增了⼀个⽅法触发以上⽅法的时候,会在native以下⽅法中进⾏拦截处理。#pragma mark - WKScriptMessageHandler- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ //这⾥就是使⽤⾼端配置,js调⽤native的处理地⽅。我们可以根据name和body,进⾏桥协议的处理。 NSString *messageName = ; if ([@"NativeMethod" isEqualToString:messageName]) { id messageBody = ; NSLog(@"%@",messageBody); }}四、UIWebView和WKWebView的⽐较和选择WKWebView是苹果在WWDC2014发布会中发布IOS8的时候公布WebKit时候使⽤的新型的H5容器。它与UIWebView相⽐较,拥有更快的加载速度和性能,更低的内存占⽤。将UIWebViewDelegate和UIWebView重构成了14个类,3个协议,可以让开发者进⾏更加细致的配置。但是他有⼀个最致命的缺陷,就是WKWebView的请求不能被NSURLProtocol截获。⽽我们团队开发的app中对于H5容器最佳的优化点主要就在于使⽤NSURLProtocol技术对于H5进⾏离线包的处理和H5的图⽚和Native的图⽚公⽤⼀套缓存的技术。因为该问题的存在,⽬前我们团队还没有使⽤WKWebView代替UIWebVIew。五、联系⽅式欢迎加好友、⼀起交流。
发布者:admin,转转请注明出处:http://www.yc00.com/web/1689264112a226405.html
评论列表(0条)