目前很多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方法并带上地理位置,这个里面关键点是如何正确进行回调。有以下两种方式:

  1. 设置iframe src的时候,把执行回调函数名传递给native,src类似为mytestscheme://getLocation/callbackName,最后native执行callName js方法
  2. 同样也是通过设置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的流程图

IOS NSData和H5通讯 h5 native通信_H5

Bridge特点是把所有通信相关代码进行封装,包括js和object-c代码,同时暴露相关的API给H5进行调用,这样好处可以在一个地方进行维护和升级,注意,这里有个细节点,这个WebViewJavascript.js可以选择直接打包在App里面,或者每个页面进行引用。

个人比较推荐直接打包App里面,节省流量,这块代码还是比较稳定的,目前微信采用这样的方式,手淘则需要在页面进行引用。

关于安全

考虑到各种域名下H5页面会出现WebView里,需考虑到不是所有Native API都可以开放给所有域名下的H5,需要进行一定权限控制,比较可行的办法是通过域名进行分级控制,有些API是全部开放,有些API只开放给某些域名的(白名单),白名单放到服务端,实现实时更新。