(一)通过URL传递函数名称和参数
这个方案是历史最悠久,至少目前也是使用最普遍的方案。这个方案的优点是技术最简单,通过scheme://host?query的形式由url来传递,iOS和Android都可以用最基础的方式来实现。
当然这里还是有一个比较有名的第三方库WebViewJavascriptBridge不过用这个库,需要JS、iOS、Android三方要协调好,最好都要用它。最后的使用接口还是简单清晰的。同时支持UIWebView和WKWebView,这种与时俱进的做法还是值得提倡。
Native调JS
UIWebView
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
WKWebView
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
WKWebView需要添加WebKit.framework,最低支持版本iOS8,Xib不能用,只能代码写界面。
条件允许的情况下,建议用WKWebView,系统性能的提升是硬道理。
JS调Native
UIWebView
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
WKWebView
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
NSURLRequest中有NSURL参数,以格式scheme://host?query约定好,JS告诉Native想要达到的目的,Native根据参数完成相应的功能。
JS和iOS、Android的写法需要相互配合,才能完成相应的通讯过程。
JS调用Native大致步骤
Step1:Native通过截取的url,将scheme://host?key1=value1&key2=value2格式解析出来。一般根据scheme判断是否目标JS,决定是否处理;host部分可以约定为模块名或者类名或者函数名等;问号后面的key1=value1&key2=value2就是url的query部分,可以放参数名;当然也可以host部分不用,将所有信息都放入后面的参数中。
Step2: Native执行一段事先定义的固定的JS代码,告诉JS已经正确收到命令。比如:
[self.webView evaluateJavaScript:@"bridge.nativeCallComplete()" completionHandler:nil];
Step3:JS端收到命令响应之后等待或者做其他事情,Native端根据解析出来的参数调用本地模块完成响应的功能
Step4:Native执行完毕后,执行一段事先定义的固定的JS代码,告诉JS执行结果。
比如:
[self.webView evaluateJavaScript:@"[self.webView evaluateJavaScript:@"javascript:bridge.invokeJs('result=3')" completionHandler:nil];" completionHandler:nil];
- Step5:JS端根据收到的结果,决定下一步的响应。
参考文章
WebViewJavascriptBridge简介
通过前面的介绍可以知道,Native调用JS和JS调用Native是很不一样的。并且在JS调用Native的过程中,有好几次用到Native调用JS的过程(告诉JS命令接收,告诉JS处理结果)。估计这也是让人感觉复杂的地方。
WebViewJavascriptBridge的设计比较巧妙,经过包装,JS和Native互相调用的写法完全一致,实现了“全双工通讯”。
marcuswestin/WebViewJavascriptBridge
通讯函数
typedef void (^WVJBResponseCallback)(id responseData);
typedef void (^WVJBHandler)(id data, WVJBResponseCallback responseCallback);
- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;
在一端用registerHandler
进行注册,在另一端就可以用callHandler
进行调用,调用结果通过Block以异步的方式告知。从调用形式上看,Native和JS完全一样,使用起来非常方便。
Native端注意点
+ (instancetype)bridgeForWebView:(WKWebView*)webView;
- (void)setWebViewDelegate:(id<WKNavigationDelegate>)webViewDelegate;
第一个函数将Bridge和某个webView建立联系
第二个函数设置webView的代理(外部代理),在函数内部,Bridge是真正的代理,进行截取url操作之后,Bridge会调用在这里设的这个代理,让外部代理起作用。代理模式中WebView的代理只有一个,就是Bridge,通过“二传手”模式,从外面看起来好像WebView有两个代理一样。
如果是UIWebView,使用的类是
WebViewJavascriptBridge
如果是WKWebView,使用的类是
WKWebViewJavascriptBridge
两者的接口函数几乎是一样的,以上两个类基本上也是“二传手”,只是一个容器,真正的工作在类
WebViewJavascriptBridgeBase
中完成。Android的代码也是有的,不过是另外的第三方库,是为了配合这个库而写的Android版本。这样JS、iOS、Android可以良好地配合起来。
jesse01/WebViewJavascriptBridge
JS端注意点
- JS端有固定的写法,里面有一个例子,“ExampleApp.html”这里面有现成的代码,自己只要往里面填自定义的函数就可以。
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
setupWebViewJavascriptBridge(function(bridge) {
var uniqueId = 1
function log(message, data) {
var log = document.getElementById('log')
var el = document.createElement('div')
el.className = 'logLine'
el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
if (log.children.length) { log.insertBefore(el, log.children[0]) }
else { log.appendChild(el) }
}
bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
log('ObjC called testJavascriptHandler with', data)
var responseData = { 'Javascript Says':'Right back atcha!' }
log('JS responding with', responseData)
responseCallback(responseData)
})
document.body.appendChild(document.createElement('br'))
var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
callbackButton.innerHTML = 'Fire testObjcCallback'
callbackButton.onclick = function(e) {
e.preventDefault()
log('JS calling handler "testObjcCallback"')
bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
log('JS got response', response)
})
}
})
(二)通过JavaScriptCore.framework
需要引入系统库JavaScriptCore.framework
最低支持iOS7
有见到过用的场所,但是不多
对于iOS和Android两个平台,JS端是否能够统一,存在不确定性
关于iOS7里的JavaScriptCore framework
JavaScriptCore框架
(三)通过WKWebView的WKUserContentController
最低支持iOS8
需要引入库WebKit.framework
这是采用注入,代理监听,发消息等方式实现的
JS端无法统一iOS和Android两个平台的处理
如何选择
采用WebViewJavascriptBridge目前来看应该是首选,iOS、Android、JS三个平台配合都考虑到了,通信方式也比较简单
最低支持iOS8,WKWebView的注入方式是值得考虑的。只是目前还没有好的第三方库支持。JS需要对iOS和Android需要分别处理,因为两者的注入方式不同。不过这个方向是值得尝试的,功能强大。
不采用WebViewJavascriptBridge,利用截取url的方式是目前大多数的方案,也是值得考虑的。JS、Android、iOS三者要彼此协调好。
JavaScriptCore在iOS8之前由于注入的方式还是有吸引力的。不过WKWebView也提供了注入的方式,目前还看不出有什么优势。当前,也是使用者比较少的一种方式。