更新更新了,大家快来围观
最近开发的混合APP,原生与h5交互通信,Android是使用JsBridge库实现,iOS是使用WebViewJavascriptBridge库实现的。由于原生ios由UIWebView替换成WKWebView,前端配置js文件会有所不同,具体内容不再本文讨论范围,本文主要讲安卓端与h5的通信实现
1. 什么是JSBridge
JSBridge主要是给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用 Native 的功能(例如:地址位置、摄像头)。而且 JSBridge 的功能不止调用 Native 功能这么简单宽泛。实际上,JSBridge 就像其名称中的Bridge的意义一样,是 Native 和非 Native 之间的桥梁,它的核心是构建 Native 和非 Native 间消息通信的通道,而且这个通信的通道是双向的。
2. JSBridge 通信实现原理
主要有两种:注入API 和 拦截URL SCHEME。
1.JavaScript 调用 Native的方式
注入API
注入 API 方式的主要原理是,通过 WebView 提供的接口,向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。拦截URL SCHEME。 拦截 URL SCHEME 的主要流程是:Web 端通过某种方式(例如 iframe.src)发送URL Scheme请求,之后 Native 拦截到请求并根据 URL SCHEME(包括所带的参数)进行相关操作.
在时间过程中,这种方式有一定的缺陷: 使用 iframe.src 发送 URL SCHEME 会有 url 长度的隐患。有些方案为了规避 url 长度隐患的缺陷,在 iOS 上采用了使用 Ajax 发送同域请求的方式,并将参数放到 head 或 body 里。这样,虽然规避了 url 长度的隐患,但是 WKWebView 并不支持这样的方式。为什么选择 iframe.src 不选择 locaiton.href ?因为如果通过 location.href 连续调用 Native,很容易丢失一些调用。 创建请求,需要一定的耗时,比注入 API 的方式调用同样的功能,耗时会较长.
因此:JavaScript 调用 Native 推荐使用注入 API 的方式
2.Native 调用 JavaScript 的方式
相比于 JavaScript 调用 Native, Native 调用 JavaScript 较为简单,直接执行拼接好的 JavaScript 代码即可。从外部调用 JavaScript 中的方法,因此 JavaScript 的方法必须在全局的 window 上。
3. 前端部分具体代码
在我自己的项目,我是使用mixins混入的方式注入到每个页面当中,但是比较繁琐,后续再将代码优化的过程; 在src下创建mixin文件夹,其中index.js关键代码如下:
handleJsBridge(callback) { // 根据不同的终端,执行不同的方法 if (this.isiOS) { this.setupWKWebViewJavascriptBridge(callback) } else { this.connectWebViewJavascriptBridge(callback) } }, // ios下的执行方法,注意这段代码需要ios使用的是WKWebView,如果使用的是UIWebView,可自行百度 setupWKWebViewJavascriptBridge(callback) { if (window.WKWebViewJavascriptBridge) { return callback(window.WKWebViewJavascriptBridge) } if (window.WKWVJBCallbacks) { return window.WKWVJBCallbacks.push(callback) } window.WKWVJBCallbacks = [callback] // 此处是判断浏览器内核,核心通信是调用postMessage方法 if (window.webkit) window.webkit.messageHandlers.iOS_Native_InjectJavascript.postMessage(null) }, // 安卓下的配置方法,原生会在webview上定义全局的WebViewJavascriptBridge对象 connectWebViewJavascriptBridge(callback) { // 判断window.WebViewJavascriptBridge是否存在 if (window.WebViewJavascriptBridge) { callback(window.WebViewJavascriptBridge) } else { // 如果不存在则添加监听事件 document.addEventListener('WebViewJavascriptBridgeReady', function () { callback(window.WebViewJavascriptBridge) }, false) } }上述代码还是比较好理解,既把原生定义在webview上的全局对象作为参数传入callback方法,那我们接下来看callback指的是什么
看看页面中的具体调用 首先需要通过import mixin from './../../mixin'引入,然后mixins: [mixin],混合注入,具体调用见如下代码:
created() { ... // 这里的birige就是原生定义在全局的WebViewJavascriptBridge _this.handleJsBridge(function(bridge) { // 这里在try-catch里执行bridge.init是因为安卓终端下的需要执行初始化方法后才可以注册通信方法registerHandler,否则会报错,但是ios下的bridge是没有init方法的。 // 这里我有个问题没想明白,如何防止bridge.init()在多个路由页面被重复执行 try { bridge.init(function(message, responseCallback) { responseCallback('get message success') }) } catch (error) { console.log(error) } // 这里既是原生调用h5,h5通过registerHandler方法注册可供原生的方法 /* * 参数1: functionName: 方法名, string类型 * 参数2: cb:回调函数,当该方法注册后被原生调回时会触发,data和responseCallback都是在调用时传入 */ // 另外需要说明的一点,当原生调用该方法时,可能存在方法还未注册,所有需要在合适的生命周期内注册和调用 // function(data, responseCallback)不可以用箭头函数代替 bridge.registerHandler(functionName, function(data, responseCallback) { }) }接下来我们看看WebViewJavascriptBridge的另一个方法callHandler(既调用已经通过registerHandler注册过的方法)
// 参数和registerHandler的参数一一对应,这里需要注意的是,安卓只能传参字符串,也就是说data在安卓端是string类型 // 参数function(cb) {console.log(responseCallback)} 就是registerHandler方法中参数responseCallback // registerHandler中执行responseCallback(message),是会执行function(cb),message作为cb传入,也就是说h5要给原生传递信息,不一定需要调用原生register的方法,不过也没必要,太繁琐了。 bridge.callHandler(functionName, data, function(cb) { console.log(cb) })
忘了提一句,无论是注册方法时还是callhanlder时this并不是指向当前vue实例的。
4. 总结
总的来说,在前端部分的使用还算比较简单,但是由于需要多端联调,爬坑过程很让人吐血,后续还是有几个弊端尚未有比较好办法解决:
- 如何防止bridge.init在多个路由页面跳转时被重复调用
- 通过mixin的方法注入当前页面,代码比较繁琐(这里我想过一种方法,把bridge的register和callhanlder方法暴露出来,通过原型链Vue.typeProperty传递给vue实例)。另外页面需要做大量终端逻辑判断,头疼。
本文使用 mdnice 排版