这样,在Native执行完你需要的指令后会再次执行那段神奇的代码进入WebView的世界,执行定义在window上名为callbackName的方法,并把native执行的结果传给这个方法。就像这样:
const messageQueue = eval('window.messageQueue') const messages = JSON.parse(messageQueue) for (const message in messages) { const result = doSomeThingWithMessage(message) eval(`window[${message.callbackName}](${result})`) … } eval('window.messageQueue = []')
同时,这也就揭露了Native是如何给H5发送消息的,直接执行window上定义好的一个方法即可。
当然,为了代码更规范,保证H5不胡乱的创建callBackName,Native并不是直接执行window上的callbackName方法,而是会调用一个大概叫handleMessageFromNative
的方法,这个方法是H5这边提前准备并定义在window上的方法,在这个方法中对消息的处理进行了收口,在里面调用window上的callbackName方法,执行完成后,将callbackName方法从window上删除掉 ,整个流程的代码大概是这样的:
// H5 function handleMessageFromNative (message) { if (typeof message.callbackName === 'function') { window[callbackName](message.result) delete window[callbackName] } } window.handleMessageFromNative = handleMessageFromNative
// Native const messageQueue = eval('window.messageQueue') const messages = JSON.parse(messageQueue) for (const message in messages) { const result = doSomeThingWithMessage(message) const messageFromNative = JSON.stringify({ result, callbackName: message.callbackName }) eval(`window.handleMessageFromNative(${messageFromNative})`) … } eval('window.messageQueue = []')
这里关于callbackName的生成也有一点规则,感兴趣可以去撸一下相关源码。大概和jsonp的规则类似。
接下来,为了方便他人使用,将以上的流程整理封装完善一下,H5和Native同时暴露两个接口,便成了如下的样子:
// H5与Native同时增加如下两个接口供对方使用: // ≈ function addEventListener(eventName, callback) function registerHandler (handlerName, block) { window.handlers[handlerName] = block … } // Web或Native调用对方接口的方式 // ≈ dispatchEvent(eventName, data, callback) function callHandler (handlerName, message, callback) { window.handlers[handlerName](message) … }
这样就可以很方便的使用了,例如要实现一个扫描二维码的功能:
// Native // 注册了一个扫描二维码的方法 registerHanlder('scanQRCode', () => { // ... Camera.open().scanQRCode() // ... })
// H5 // 调用扫描二维码的方法 callHanlder('scanQRCode', { type: 'qrcode' }, result => { console.log('扫码结果:', result) })
喜欢的话可以用Promise封装一下:
// 为H5封装好的bridge-sdk.js,在H5中使用 /** * 扫描二维码并返回结果 * ... * @memberOf Camera * @async * @returns {Promise} 可以在then中接受扫码结果`result`,参数为 { code: 'xxxxxx' } * ... */ export async function scanQRCode () { return new Promise((resolve, reject) => { callHanlder('scanQRCode', { type: 'qrcode' }, result => { console.log('扫码结果:', result) resolve(result) }) }) }
好了,以上就是经典的JSBridge
的实现方案,看起来非常的简单,且没有兼容性问题。
既然Native有神奇的代码,有没有更彻底些的办法呢?
有!!!Native中有另一个神奇的API,我们暂且称它为defineFunc函数吧,它可以直接将Native的代码注入到H5的载体WebView中,并挂在WebView的window上。
// define 翻译过来大概就是下面的这个意思 function defineFunc (funcName, func) { const window = webView.window ... // 通过一些Native的API拿到WebView的window window[funcName] = func // 这里的func 是Native的func,执行的是纯Native的代码 } // Native defineFunc('callSomeNativeFunction', () => { // 这些是由Native的代码翻译成javascript的伪代码 const file = io.readFile('/path/to/file') ... // 做一些H5做不到的事情 file.write('/path/to/file', 'content') ... })
这就是利用如iOS中JavaScriptCore的API来实现交互的原理,安卓也有类似的方式,对系统版本有些许的要求,可以忽略不计。这里就不讨论了。