本次记录的是关于在项目开发中使用Hybrid技术的实践记录。主要使用的是WKWebview,关于UIWebView只有一些简单的了解,当然做了一些简单的对比后,在新建的项目中还是建议使用WKWebview
前言
在最近的项目中很多页面都是前端开发然后和iOS端进行交互。理由当然是前端的更新方便快速。在项目中并没有使用 WebViewJavascriptBridge 和 cordova 这些比较著名的Hybrid技术框架。大部分使用的还是WKWebview提供的原生Api。对于我的项目也是够用。
实现方案
JS 调用 Native 的几种通信方案
- 拦截跳转请求
- 弹窗拦截
- JS上下文注入
Native 调用 JS 的几种通信方案
由于项目中主要使用的是WKWebview这里使用的主要是WKWebView官方提供的Api
- evaluateJavaScript
实现方案分析
JS 调用 Native
- 拦截跳转请求
在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
- 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上下文注入
- 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代码,在网页加载的时候再逐条执行可扩展性不高
综合对比和选择
- 跳转请求拦截
这种实现方式的优点是版本兼容性好,从iOS6开始支持。但是现在最低的版本兼容都从iOS8开始了,微信也从iOS9开始支持。这个方案最初是用在UIWebView上面,从iOS9开始WKWebview的使用明显更多。所以这个优点并不明显
然而他的缺点也比较明显:
- 丢消息:当我们在同时发送多个请求的时候,部分请求我们是拦截不到的,webview会自动拦截掉后面的请求,因此我们可能会失去一些信息
- URL长度限制:有时候我们一个请求含有的信息量比较大,所以URL的长度也会过长,webview中对URL的长度也会进行限制,这就导致某些请求无法进行
- 弹窗拦截
这种实现方式有明显的优点和缺点:
- 优点:请求同步执行,前端能够接受到回调,就是说js调用了OC的代码后,OC能够返回一些信息。这样前端就能够确定了是否执行成功,并且能够返回一些参数
- 缺点:他的缺点还是同步执行,这个弹窗返回的时候前端的整个加载都会停止。如果一些请求需要在页面加载的时候进行的话,就会影响前端页面的正常加载,用户体验十分差
- JS上下文注入
- UIWebview的JSContext注入:上面已经说到项目中比较常用的是WKWebview,而JSContext只支持UIWebview,JSContext支持js的同步返回。这个功能点是比较牛逼的,但是考虑到WKWebview的性能优化,还是放弃了
- WKWebView的scriptMessageHandler注入:这个是我目前采用的方式.直接支持json数据的传递。简单快捷
- evaluatingJavaScript 直接执行JS代码
就是Native主动调用JS的普遍方式缺点和优点就不说了
总结
由于我采用的是WKWebview,所以关于和js的交互一般使用
MessageHandler注入/Prompt弹框拦截(JSToNative) + evaluatingJavaScript (NativeToJS)
这个方案无论是实现方式和稳定性都是最好的,其中弹窗拦截的方式拥有同步返回数据的回调非常友好,倒是前面已经说了这个是同步执行的,甚至会阻碍前端的加载,因此在搭配上需要根据不同的功能进行选择
网友评论