Hybrid技术使用记录

本次记录的是关于在项目开发中使用Hybrid技术的实践记录。主要使用的是WKWebview,关于UIWebView只有一些简单的了解,当然做了一些简单的对比后,在新建的项目中还是建议使用WKWebview

前言

在最近的项目中很多页面都是前端开发然后和iOS端进行交互。理由当然是前端的更新方便快速。在项目中并没有使用 WebViewJavascriptBridge 和 cordova 这些比较著名的Hybrid技术框架。大部分使用的还是WKWebview提供的原生Api。对于我的项目也是够用。

实现方案


JS 调用 Native 的几种通信方案

  1. 拦截跳转请求
  2. 弹窗拦截
  3. JS上下文注入

Native 调用 JS 的几种通信方案

由于项目中主要使用的是WKWebview这里使用的主要是WKWebView官方提供的Api

  1. evaluateJavaScript

实现方案分析


JS 调用 Native

  1. 拦截跳转请求

在WKWebview还未问世的时候,苹果提供的是UIWebview,那个时候最常见的通讯方式就是拦截跳转请求,然后从指定的URL中获取需要的信息

//普通的Http地址
https://www.baidu.com/xxxx?xx=xx
//假的请求通信地址
milan://milan123/action?param=obj

一般我们用于通讯的URL地址都是假的跳转链接,目的是为了传递参数。我们可以分析上面的URL

  • 协议:http/https/file等,包括自己定义的milan
  • 域名:www.baidu.com,milan123
  • 路径:action?,xxxx?
  • 参数:xx=xx,param=obj

我们通过webview提供的Api拦截到特定的URL后获得传递的参数,就能做相应的动作

1.UIWebview拦截URL

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    //1 根据url,判断是否是所需要的拦截的调用 判断协议/域名
    if (是){
      //2 取出路径,确认要发起的native调用的指令是什么
      //3 取出参数,拿到JS传过来的数据
      //4 根据指令调用对应的native方法,传递数据
      return NO;
      //确认拦截,拒绝WebView继续发起请求
    }    
    return YES;
}

2.WKWebView拦截URL

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    //1 根据url,判断是否是所需要的拦截的调用 判断协议/域名
    if (是){
      //2 取出路径,确认要发起的native调用的指令是什么
      //3 取出参数,拿到JS传过来的数据
      //4 根据指令调用对应的native方法,传递数据

      //确认拦截,拒绝WebView继续发起请求
        decisionHandler(WKNavigationActionPolicyCancel);
    }else{
        decisionHandler(WKNavigationActionPolicyAllow);
    }
    return YES;
}

弹窗拦截

在webview中分为alert/confirm/prompt三种弹窗。由js发起弹窗,然后移动端拦截弹窗请求,这里的例子主要是prompt

  1. WKWebView拦截prompt弹窗
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler{
    //1 根据传来的字符串反解出数据,判断是否是所需要的拦截而非常规H5弹框
    if (是){
        //2 取出指令参数,确认要发起的native调用的指令是什么
        //3 取出数据参数,拿到JS传过来的数据
        //4 根据指令调用对应的native方法,传递数据
        //直接返回JS空字符串
        completionHandler(@"");
    }else{
        //直接返回JS空字符串
        completionHandler(@"");
    }
}

2.UIWebView从我了解到的信息中不支持拦截弹窗。

JS上下文注入

  1. UIWebView
    由于本次使用的是WKWebview,对于UIWebView中的实现方法只做简单的介绍
  • 通过KVC获取当前webview的js上下文documentView.webView.mainFrame.javaScriptContext
  • 通过拿到的JSContext进行js注入

2.WKWebView

//配置对象注入
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"nativeObject"];
//移除对象注入
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"nativeObject"];

scriptMessageHandler的使用需要记得在页面销毁前进行释放,否则会造成内存泄漏

window.webkit.messageHandlers.nativeObject.postMessage(data);

JS代码中只需要使用上面的方法就能发送信息给客户端

-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    //1 解读JS传过来的JSValue  data数据
    NSDictionary *msgBody = message.body;
    //2 取出指令参数,确认要发起的native调用的指令是什么
    //3 取出数据参数,拿到JS传过来的数据
    //4 根据指令调用对应的native方法,传递数据
}

客户端通过上面的代理方法就能接收信息

Native 调用 JS


evaluatingJavaScript 执行JS代码

function calljs(data){
    console.log(JSON.parse(data)) 
    //1 识别客户端传来的数据
    //2 对数据进行分析,从而调用或执行其他逻辑  
}

//不展开了,data是一个字典,把字典序列化
NSString *paramsString = [self _serializeMessageData:data];
NSString* javascriptCommand = [NSString stringWithFormat:@"calljs('%@');", paramsString];
//要求必须在主线程执行JS
if ([[NSThread currentThread] isMainThread]) {
    [self.webView evaluateJavaScript:javascriptCommand completionHandler:nil];
} else {
    __strong typeof(self)strongSelf = self;
    dispatch_sync(dispatch_get_main_queue(), ^{
        [strongSelf.webView evaluateJavaScript:javascriptCommand completionHandler:nil];
    });
}

上面的例子是使用WKWebview来执行js代码,我们首先在前端的js文件中先协定好一个js方法。我们在使用的时候只需要通过js方法的名字就能调用

WKUserScript 执行JS代码

//在loadurl之前使用
//time是一个时机参数,可选dom开始加载/dom加载完毕,2个时机进行执行JS
//构建userscript
WKUserScript *script = [[WKUserScript alloc]initWithSource:source injectionTime:time forMainFrameOnly:mainOnly];
WKUserContentController *userController = webView.userContentController;
//配置userscript
[userController addUserScript:script]

evaluatingJavaScript能够随时执行js方法。WKUserScript需要预先准备好js代码,在网页加载的时候再逐条执行可扩展性不高

综合对比和选择


  1. 跳转请求拦截

这种实现方式的优点是版本兼容性好,从iOS6开始支持。但是现在最低的版本兼容都从iOS8开始了,微信也从iOS9开始支持。这个方案最初是用在UIWebView上面,从iOS9开始WKWebview的使用明显更多。所以这个优点并不明显

然而他的缺点也比较明显:

  • 丢消息:当我们在同时发送多个请求的时候,部分请求我们是拦截不到的,webview会自动拦截掉后面的请求,因此我们可能会失去一些信息
  • URL长度限制:有时候我们一个请求含有的信息量比较大,所以URL的长度也会过长,webview中对URL的长度也会进行限制,这就导致某些请求无法进行
  1. 弹窗拦截

这种实现方式有明显的优点和缺点:

  • 优点:请求同步执行,前端能够接受到回调,就是说js调用了OC的代码后,OC能够返回一些信息。这样前端就能够确定了是否执行成功,并且能够返回一些参数
  • 缺点:他的缺点还是同步执行,这个弹窗返回的时候前端的整个加载都会停止。如果一些请求需要在页面加载的时候进行的话,就会影响前端页面的正常加载,用户体验十分差
  1. JS上下文注入
  • UIWebview的JSContext注入:上面已经说到项目中比较常用的是WKWebview,而JSContext只支持UIWebview,JSContext支持js的同步返回。这个功能点是比较牛逼的,但是考虑到WKWebview的性能优化,还是放弃了
  • WKWebView的scriptMessageHandler注入:这个是我目前采用的方式.直接支持json数据的传递。简单快捷
  1. evaluatingJavaScript 直接执行JS代码

就是Native主动调用JS的普遍方式缺点和优点就不说了

总结


由于我采用的是WKWebview,所以关于和js的交互一般使用

MessageHandler注入/Prompt弹框拦截(JSToNative) + evaluatingJavaScript (NativeToJS)

这个方案无论是实现方式和稳定性都是最好的,其中弹窗拦截的方式拥有同步返回数据的回调非常友好,倒是前面已经说了这个是同步执行的,甚至会阻碍前端的加载,因此在搭配上需要根据不同的功能进行选择

你可能感兴趣的:(Hybrid技术使用记录)