目前很多App都采用HyBrid的开发模式,需要实现H5和Native相互通信,比如以下场景:
1. H5需要唤起Native分享组件把内容分享到其他App里;
2. 当手机网络从3G切换到WIFI时,需通知H5展示更为清晰的商品图片;
类似这样的通信场景很多,那么如何实现H5和Native通信呢?下面以IOS平台,来进行剖析:
H5和Native的通信,可以简化为js和object-c两种语言的通信。
object-c调用js方法和js变量、方法注入
在IOS底层框架,提供了一个非常有用的API:stringByEvaluatingJavaScriptFromString,借助于这个API,能够实现object-c调用js方法以及js变量、方法的注入
在H5页面定义了一个getInutName的js方法:
function getInputName(){
return "ok";
}
通过如下代码object-c就可以调用到H5定义的方法getInputName,并且把返回值赋值给returnvalue
NSString *returnvalue = [webView stringByEvaluatingJavaScriptFromString:@"getInputName()"];//ok
object-c还可以完成js方法和变量注入
NSString *network = @"'wifi'";
NSString *javascriptCommand = @"function test(){return 'ok';};var network=";
javascriptCommand = [javascriptCommand stringByAppendingString:network];
[webView stringByEvaluatingJavaScriptFromString:javascriptCommand];
这样H5就可以使用test方法和network变量
object-c还可以读取一个JS文件,完成JS方法批量导入
NSBundle *bundle = [NSBundle mainBundle];
NSString *filePath = [bundle pathForResource:@"WebViewJavascriptBridge.js" ofType:@"txt"];
NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
[webView stringByEvaluatingJavaScriptFromString:js];
js调用object-c方法
在IOS7及以下版本,目前js还无法直接调用object-c定义的API,但在Mac OSX,js已经可以调用object-c定义的API,详细请见这里,查阅了相关文档,在IOS8预览版已支持类似Mac OSX的调用方式,有待验证。
虽然无法直接调用object-c定义的API,但可以另辟蹊径。
在H5页面js执行以下2种操作,都会被H5所在的WebView捕获,同时执行相关object-c代码。
1. 改变location.href
window.location.href = mytestscheme://callfunction/parameter1/parameter2?parameter3=value
2. 创建iframe,设置src,并插入到body节点
function execute(url)
{
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", url);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
execute("mytestscheme://callfunction/parameter1/parameter2?parameter3=value");
虽然以上两种方法都可以达到通信的目的,但是第1种方法,在某些场景会有问题,比如H5唤起Native分享组件时,同时进行监控打点(通过客户段来实现打点),结果是执行了两次改变location.href操作,但实际就会执行一次操作,保险起见,请采用iframe的方案。
这个时候WebView就可以监控到请求,并且做出相应响应:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *URL = [request URL];
if ([[URL scheme] isEqualToString:@"mytestscheme"]) {
// parse the rest of the URL object and execute functions
}
}
可能还会有疑问,某些场景需要进行回调(如H5向Native获取当前地理位置,拿到地理位置后,在当前页面显示),这个如何处理呢?
H5向Native获取地理位置的请求,通过iframe方法就可以达到,Native把地理传递给H5,通过执行js方法并带上地理位置,这个里面关键点是如何正确进行回调。有以下两种方式:
- 设置iframe src的时候,把执行回调函数名传递给native,src类似为mytestscheme://getLocation/callbackName,最后native执行callName js方法
- 同样也是通过设置iframe src,但是src为 mytestscheme:onJsCall:callbackId:getLocation(url根据自己需要定义),native统一执行和H5约定的回调函数,如runCallback,并且传递参数为callbackId和地理位置,JS内部建立一个对象映射,如下:
(function(){
var Bridge = {
callbacks:{},
callbacksCount : 1,
getCallbackId:function(callback){
var hasCallback = callback && typeof callback == "function";
if(callback.__jsbridgeId__){
return callback.__jsbridgeId__;
}
var callbackId = hasCallback ? this.callbacksCount++ : 0;
if (hasCallback) {
this.callbacks[callbackId] = callback;
callback.__jsbridgeId__ = callbackId;
return callbackId;
}
},
ios:function(options){
var callbackId = this.getCallbackId(opt.callback),
path,
src;
path = [opt.fn].concat(opt.param);
src = "mytestscheme:onJsCall:" + callbackId+ ":" + encodeURIComponent(JSON.stringify(path));
}
};
window.runCallback = function(callbackId,resultArray){
try {
var callback = Bridge.callbacks[callbackId];
if (!callback) return;
callback.call(this,resultArray);
} catch(e) {}
}
})();
H5和Native通信原理基本介绍完了,下面再从开发便利性和可维护性,谈谈如何封装,安全控制,以及推荐下业界优秀类库。
关于JSBridge
有移动端开发经验的同学,对JSBridge应该不会陌生,借助于它,能够轻松让H5和Native进行通信,而且开发很简单,JSBridge为H5和Native通信提供了一套完整的解决方案。
JSBridge的流程图
Bridge特点是把所有通信相关代码进行封装,包括js和object-c代码,同时暴露相关的API给H5进行调用,这样好处可以在一个地方进行维护和升级,注意,这里有个细节点,这个WebViewJavascript.js可以选择直接打包在App里面,或者每个页面进行引用。
个人比较推荐直接打包App里面,节省流量,这块代码还是比较稳定的,目前微信采用这样的方式,手淘则需要在页面进行引用。
关于安全
考虑到各种域名下H5页面会出现WebView里,需考虑到不是所有Native API都可以开放给所有域名下的H5,需要进行一定权限控制,比较可行的办法是通过域名进行分级控制,有些API是全部开放,有些API只开放给某些域名的(白名单),白名单放到服务端,实现实时更新。