Hybrid App: 对比UIWebView和WebKit实现JavaScript与Native交互

Posted 程序猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hybrid App: 对比UIWebView和WebKit实现JavaScript与Native交互相关的知识,希望对你有一定的参考价值。

一、简介

在前面一篇文章中讲到过实现javascript与Native交互的方式有一种就是使用原生内嵌webView。在ios8之前,开发者只能使用苹果提供的UIWebView类来加载URL或者html网页视图,然后通过设置代理,在回调函数中拦截并处理自定义交互事件,功能十分有限,通常只是作为一个辅助视图使用。在iOS8之后,苹果对这方面的技术进行了重构和优化,推出了一个新的框架WebKit。WebKit提供了Native与JavaScript交互的方法,整个框架的结构很清晰,对外暴露的接口友好实用,极大地方便了开发者实现网页视图和的Native交互。并且,WebKit框架采用导航堆栈的模式来管理网页视图的跳转,对于网页视图的管理和渲染,开发者更加容易管控。慢慢地,咱来比较这两种webView的使用区别。

 

二、UIWebView

1、UIWebView的详细构成

UIWebView的类构成之:属性

//id类型,遵守UIWebViewDelegate协议
@property (nullable, nonatomic, assign) id <UIWebViewDelegate> delegate

//只读属性,webView内部的滚动视图
@property (nonatomic, readonly, strong) UIScrollView *scrollView

//只读属性,是否可以后退
@property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack

//只读属性,是否可以前进
@property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward

//只读属性,是否正在加载
@property (nonatomic, readonly, getter=isLoading) BOOL loading

//是否支持缩放页面自适应
@property (nonatomic) BOOL scalesPageToFit

//是否检测电话号码(链接形式)
@property (nonatomic) BOOL detectsPhoneNumbers

//枚举类型,数据检测类型。如电话、邮箱等
@property (nonatomic) UIDataDetectorTypes dataDetectorTypes

//是否使用内联播放器播放视频
@property (nonatomic) BOOL allowsInlineMediaPlayback

//视频是否自动播放
@property (nonatomic) BOOL mediaPlaybackRequiresUserAction

//是否支持air play功能
@property (nonatomic) BOOL mediaPlaybackAllowsAirPlay

//是否将数据加载到内存后渲染界面
@property (nonatomic) BOOL suppressesIncrementalRendering

//是否支持用户打开键盘进行交互
@property (nonatomic) BOOL keyboardDisplayRequiresUserAction

//是否支持视频画中画
@property (nonatomic) BOOL allowsPictureInPictureMediaPlayback

//是否支持链接预览
@property (nonatomic) BOOL allowsLinkPreview

//页面长度
@property (nonatomic) CGFloat pageLength

//页面间距
@property (nonatomic) CGFloat gapBetweenPages

//页面数量
@property (nonatomic, readonly) NSUInteger pageCount

//枚举类型,分页模式
@property (nonatomic) UIWebPaginationMode paginationMode

//枚举类型,决定加载页面具有CSS属性时是采用页样式还是类样式
@property (nonatomic) UIWebPaginationBreakingMode paginationBreakingMode

UIWebView的类构成之:方法

//加载URL类型的webView
- (void)loadRequest:(NSURLRequest *)request

//加载HTML类型的webView
- (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL

//加载NSData类型的webView
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL

//刷新webView
- (void)reload

//停止加载webView
- (void)stopLoading

//返回上一页
- (void)goBack

//前进下一页
- (void)goForward

//调用JavaScript代码
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script

UIWebView的类构成之:协议

//即将对网页URL发送请求,通过返回布尔值决定是否跳转,根据scheme或者navigationType匹配来做拦截处理,以完成端上的交互行为
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

//网页已经开始加载时调用
- (void)webViewDidStartLoad:(UIWebView *)webView

//网页完成加载时调用
- (void)webViewDidFinishLoad:(UIWebView *)webView

//网页加载失败时调用
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error 

UIWebView的类构成之:枚举

//导航类型
UIWebViewNavigationType
    UIWebViewNavigationTypeLinkClicked //触击一个链接
    UIWebViewNavigationTypeFormSubmitted //提交一个表单
    UIWebViewNavigationTypeBackForward //触击前进或返回按钮
    UIWebViewNavigationTypeReload //触击重新加载的按钮
    UIWebViewNavigationTypeFormResubmitted //用户重复提交表单
    UIWebViewNavigationTypeOther //发生其它行为

//翻页模式
UIWebPaginationMode
    UIWebPaginationModeUnpaginated //无分页
    UIWebPaginationModeLeftToRight //从左往右翻页
    UIWebPaginationModeTopToBottom //从下往上翻页
    UIWebPaginationModeBottomToTop //从上往下翻页
    UIWebPaginationModeRightToLeft //从右往左翻页

//CSS模式
UIWebPaginationBreakingMode
    UIWebPaginationBreakingModePage //页样式
    UIWebPaginationBreakingModeColumn //列样式

2、UIWebView的使用,混合开发

[2-1] 创建一个HTML文件

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  
  <style>
      .divcss{ border:1px solid #F00; width:300px; height:200px}
  </style>
  
  <script type="text/javascript">
      function showAlert(){
          alert("我被成功调起来了");
          
          //<!-- JS通知WKWebView, methodInvoke就是和端上约定的方法名称 -->
          var list = [1,2,3];
          var dict = {"name":"XYQ", "qq":"1273843", "list":list};
          window.webkit.messageHandlers.methodInvoke.postMessage(dict);
          
      }
    
      function disp_confirm()
      {
            var r=confirm("Press a button")
            if (r==true){
                  document.write("You pressed OK!")
            }
            else{
                  document.write("You pressed Cancel!")
            }
      }
  
      function disp_prompt()
      {
          var name=prompt("Please enter your name","")
          if (name!=null && name!=""){
              document.write("Hello " + name + "!")
          }
      }
  
  </script>
  
</head>

<body>
    
    <div class="divcss">
        <!-- 这是一个链接跳转地址,点击时,端上将会进行拦截,弹出日志 -->
        <a href="http://www.w3school.com.cn/">Visit W3School</a>
        <br>
        <br>
        <a href="parent://www.parent.com.cn/">open parent</a>
    </div>
    
    <input type="button" onclick="disp_confirm()"
    value="Display a confirm box" />
    
    <input type="button" onclick="disp_prompt()"
    value="Display a prompt box" />
    
</body>

</html>
View Code

[2-2] 创建并加载webView

- (void)viewDidLoad {
    [super viewDidLoad];
   
    //创建webView
    self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.webView.delegate = self;
    
    //创建html
    NSString *file = [[NSBundle mainBundle] pathForResource:@"web" ofType:@"html"];
    NSString *html = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
    
    //加载webView
    [self.webView loadHTMLString:html baseURL:nil];
    [self.view addSubview:self.webView];
}

[2-3] 设置代理,事件拦截

#pragma mark - UIWebViewDelegate
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {

    NSLog(@"------------网页即将开始加载------------");
    
    //实际工作中,获取scheme或者navigationType,通过匹配scheme或者navigationType来拦截页面中的点击行为来完成端上交互
    //这个scheme可以自定义的,不过需要跟前端约定好。例如"http"、"https"、"iOS"等等 , 例子如:"iOS://baidu.com/con/"
//1、点击链接 if (navigationType == UIWebViewNavigationTypeLinkClicked) { // <a href="http://www.w3school.com.cn/">Visit W3School</a> NSString *scheme = request.URL.scheme; if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]) { //H5调用原生方法,显示iOS的弹框 NSString *href = request.URL.absoluteString; [self showAlertView:href]; } return NO; //取消网页加载, 那么该链接就不会进行跳转了 } //2、点击按钮 if (navigationType == UIWebViewNavigationTypeFormSubmitted) { //H5调用原生方法,打开相册 [self openPhotoLibrary]; //取消网页加载, 那么该提交事件就不会触发了 return NO; } return YES; } -(void)webViewDidStartLoad:(UIWebView *)webView { NSLog(@"------------网页已经开始加载------------"); } -(void)webViewDidFinishLoad:(UIWebView *)webView { NSLog(@"------------网页已经完成加载------------"); //原生调用js方法,显示window的alert内容 [self showWindowAlertString]; } -(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { NSLog(@"------------网页已经加载失败------------"); }

[2-4] 原生方法和JS事件

#pragma mark - h5 call native
-(void)openPhotoLibrary {
    
    //打开Native系统相册
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    picker.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:picker animated:YES completion:nil];
}

-(void)showAlertView:(NSString *)message {
    
    //打开Native系统弹框
    UIAlertController *aletVc = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
    [aletVc addAction:action];
    [self presentViewController:aletVc animated:YES completion:nil];
}

#pragma mark - native call js 
-(void)showWindowAlertString {
    
    //显示H5页面中的被调用的JS函数返回的弹框内容
    NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:@"alert()"];
    [self showAlertView:result];
}

[2-5] 结果分析和显示

通过html格式即将加载webView时,代理方法会依次调用123。除非加载失败,才会打印出4;

加载成功后,此时原生调用JS代码,页面会弹出调用js函数返回的内容;

点击链接时,本来应该跳转到新的页面,但是端上在即将加载页面做了拦截,返回值设置了NO,所以不会跳转;

点击按钮时,本来HTML页面的js事件会触发,但是由于端上在即将加载页面做了拦截,返回值设置了NO,所以不会触发js事件;

//首次加载HTM
2019-11-15 16:22:19.402276+0800 WebView[57408:2041607] ------------网页即将开始加载------------
2019-11-15 16:22:19.403887+0800 WebView[57408:2041607] ------------网页已经开始加载------------
2019-11-15 16:22:19.457337+0800 WebView[57408:2041607] ------------网页已经完成加载------------

//点击链接
2019-11-15 16:22:23.674148+0800 WebView[57408:2041607] ------------网页即将开始加载------------

//点击按钮
2019-11-15 16:22:28.682868+0800 WebView[57408:2041607] ------------网页即将开始加载------------

 

 

三、WebKit

1、可以看到使用UIWebView虽然可以实现Native与H5/JS的交互,但是功能过于简单,而且在iOS系统快速升级之后,UIWebView已经被苹果摒弃,不再被推荐使用,取而代之的则是WebKit框架,就跟UserNotification框架取代UILocalNotification类一样。WebKit框架用到的类很多,设计的思想更加面向对象化,模块化,开发者使用起来非常方便,代码结构清晰。WebKit框架图大致如下:

2、看完上面的WebKit框架图是不是感觉很清晰,每一个模块有自己的对应的职责,WKWebView很具需要依赖这些模块,基本可以满足开发者想要的大多数需求。现在对一些重要的类概念了解一下。

3、WKWebView属性和方法与UIWebView有很多相类似,具体的类的信息可以看API。

3-1 WKWebView提供了进度progress属性,可以使用KVO监控页面加载的进度,这个优化能提供用户一个更好的使用体验,而这个在UIWebView中是没有的。

/*! @abstract An estimate of what fraction of the current navigation has been completed.
 @discussion This value ranges from 0.0 to 1.0 based on the total number of
 bytes expected to be received, including the main document and all of its
 potential subresources. After a navigation completes, the value remains at 1.0
 until a new navigation starts, at which point it is reset to 0.0.
 @link WKWebView @/link is key-value observing (KVO) compliant for this
 property.
 */
@property (nonatomic, readonly) double estimatedProgress;

3-2 其次,WKWebView相比于UIWebView在导航管理上是更优秀的,采用堆栈管理的方式,能够任意进行不同视图之间的跳转。这个属性就是WKBackForwardList,表示的是所有的网页视图堆栈,管理每一个网页视图节点,可以看看它的构成如下:

//去某一个网页节点: WKWebView的方法,跳转到某一个网页节点
- (nullable WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item;
//这个WKBackForwardList类的构成
//当前网页节点
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *currentItem;

//前进的一个网页节点
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *backItem;

//回退的一个网页节点
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *forwardItem;

//获取某个index的网页节点
- (nullable WKBackForwardListItem *)itemAtIndex:(NSInteger)index;

//获取回退的节点数组
@property (nonatomic, readonly, copy) NSArray<WKBackForwardListItem *> *backList;

//获取前进的节点数组
@property (nonatomic, readonly, copy) NSArray<WKBackForwardListItem *> *forwardList; 

4、WKWebView中,Native与JavaScript交互如下几种,这里列一下基本实现方式,如何实现请看范例

// 第一种:JavaScript调用Native,采用WKUserContentController方式注册,在WKScriptMessageHandler代理方法中实现

// 第二种:Native调用JavaScript,采用evaluteJavaScript:complementionHandler:方法直接调用JavaScript脚本中的函数来实现

// 第三种:将自定义的JavaScript代码在端上采用addUserScript方式注入,然后再用evaluteJavaScript:complementionHandler:方法实现

// 第四种:通过WKUIDelegate来处理交互时来实现

5、现在来看一下在实例化WKWebView的过程中,开发者可以都设置哪些配置。

5-1 创建配置

//创建配置config
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.suppressesIncrementalRendering = NO;
config.applicationNameForUserAgent = @"Safari";

5-2 设置进程池

//设置进程池,拥有同一个pool进程池的多个webView可以共享数据,如Cookie、用户凭证等信息
WKProcessPool *pool = [[WKProcessPool alloc] init];
config.processPool = pool;

4-2 设置偏好

//设置偏好,可以设置一些页面信息,如字体、是否支持js交互、是否允许自动打开js窗体
WKPreferences *preference = [[WKPreferences alloc] init];
preference.minimumFontSize = 25;
preference.javaScriptEnabled = YES;
preference.javaScriptCanOpenWindowsAutomatically = YES;
config.preferences = preference;

5-3 设置内容交互控制器,处理JavaScript与Native交互

//创建内容交互控制器,处理js与native的交互
//使用addScriptMessageHandler:name: 注册JavaScript要调用的方法名称,设置处理代理并且注册要被JavaScript调用的方法名称 name:方法名称
//使用addUserScript:注入代码,用window.webkit.messageHandlers.name.postMessage()向Native发送消息,支持OC中字典、数组、NSNumber等,设置Cookie
//例如注入cookie代码:NSString *cookieSource= @"document.cookie = \'token=12344\';document.cookie = \'userName=xyq\';";
//例如注入函数代码:NSString *funcSource = @"function func(){}";
//注意:要想使用方式二,方式一这一步不可省略。方式一实现后,可以根据情况选择是否需要使用方式二。
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
    
//方式一:注册函数,代理回调,这个在H5文件内使用了 "window.webkit.messageHandlers.methodInvoke.postMessage()"
[userContentController addScriptMessageHandler:self name:methodInvoke];
    
//方式二:注入代码,直接调用,手动注入代码实现了"window.webkit.messageHandlers.methodInvoke.postMessage()"
NSString *methodSource = @"function print(){ window.webkit.messageHandlers.methodInvoke.postMessage(\\"hello js!\\")}";
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:methodSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
[userContentController addUserScript:userScript];

//设置内容交互控制器
config.userContentController = userContentController; 

5-4 设置数据存储

//设置数据存储,单例
//defaultDataStore:默认的存储器,会将数据写入磁盘
//nonPersistentDataStore:临时的存储器
WKWebsiteDataStore *store = [WKWebsiteDataStore nonPersistentDataStore];
config.websiteDataStore = store;

5-5 设置代理

//创建WKWebView
self.wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
  
//设置导航代理
self.wkWebView.navigationDelegate = self;
    
//设置js弹出框代理
self.wkWebView.UIDelegate = self;

5-6 加载资源webkit.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  
  <style>
      .divcss{ border:1px solid #F00; width:300px; height:200px}
  </style>
  
  <script type="text/javascript">
      function showAlert(){
          alert("我被成功调起来了");
      }
    
      function disp_confirm()
      {
            var r=confirm("Press a button")
            if (r==true){
                  document.write("You pressed OK!")
            }
            else{
                  document.write("You pressed Cancel!")
            }
      }
  
      function disp

以上是关于Hybrid App: 对比UIWebView和WebKit实现JavaScript与Native交互的主要内容,如果未能解决你的问题,请参考以下文章

将 iOS Hybrid App 从 UIWebView 迁移到 WKWebview

跨终端Web之Hybrid App开发对比

Native APP ,Web APP,Hybrid APP三者对比

Hybrid App: 看看第三方WebViewJavascriptBridge是如何来实现Native和JavaScript交互

Hybrid技术的设计与实现(转)

Hybrid----优秀开源代码解读之JS与iOS Native Code互调的优雅实现方案-备