天天看点

一文搞懂jsBridge的运行机制

我司的APP是一个典型的混合开发APP,内嵌的都是前端页面,前端页面要做到和原生的效果相似,就避免不了调用一些原生的方法,​<code>​jsBridge​</code>​​就是​<code>​js​</code>​​和​<code>​原生​</code>​​通信的桥梁,本文不讲概念性的东西,而是通过分析一下我司项目中的​<code>​jsBridge​</code>​源码,来从前端角度大概了解一下它是怎么实现的。

js调用方式

先来看一下,​<code>​js​</code>​​是怎么来调用某个原生方法的,首先初始化的时候会调用​<code>​window.WebViewJavascriptBridge.init​</code>​方法:

然后如果要调用某个原生方法可以使用下面的函数:

传入要调用的方法名、参数和回调即可,它先校验了一下参数,然后会调用​<code>​window.WebViewJavascriptBridge.callHandler​</code>​方法。

此外也可以提供回调供原生调用:

接下来看一下​<code>​window.WebViewJavascriptBridge​</code>​对象到底是啥。

安卓

​<code>​WebViewJavascriptBridge.js​</code>​文件内是一个自执行函数,首先定义了一些变量:

根据变量名简单翻译了一下,具体用处接下来会分析。接下来定义了​<code>​WebViewJavascriptBridge​</code>​对象:

可以看到就是一个普通的对象,上面挂载了一些方法,具体方法暂时不看,继续往下:

调用了​<code>​_createQueueReadyIframe​</code>​方法:

这个方法很简单,就是创建了一个隐藏的​<code>​iframe​</code>​插入到页面,继续往下:

这里定义了一个自定义事件,并直接派发了,其他地方可以像通过监听原生事件一样监听该事件:

这里的用处我理解就是当该​<code>​jsBridge​</code>​​文件如果是在其他代码之后引入的话需要保证之前的代码能知道​<code>​window.WebViewJavascriptBridge​</code>​​对象何时可用,如果规定该​<code>​jsBridge​</code>​必须要最先引入的话那么就不需要这个处理了。

到这里自执行函数就结束了,接下来看一下最开始的​<code>​init​</code>​方法:

从初始化的角度来看,这个​<code>​init​</code>​​方法似乎啥也没做。接下来我们来看​<code>​callHandler​</code>​方法,看看是如何调用安卓的方法的:

处理了一下参数又调用了​<code>​_doSend​</code>​方法:

这个方法首先把调用原生方法时的回调函数通过生成一个唯一的​<code>​id​</code>​​保存到最开始定义的​<code>​responseCallbacks​</code>​​对象里,然后把该​<code>​id​</code>​​添加到要发送的信息上,所以一个​<code>​message​</code>​的结构是这样的:

接着把该​<code>​message​</code>​​添加到最开始定义的​<code>​sendMessageQueue​</code>​​数组里,最后设置了​<code>​iframe​</code>​​的​<code>​src​</code>​​属性:​<code>​yy://__QUEUE_MESSAGE__/​</code>​​,这其实就是一个自定义协议的​<code>​url​</code>​​,我简单搜索了一下,​<code>​native​</code>​​会拦截这个​<code>​url​</code>​​来做相应的处理,到这里我们就走不下去了,因为不知道原生做了什么事情,简单搜索了一下,发现了这个库:​​WebViewJavascriptBridge​​,我司应该是在这个库基础上修改的,结合了网上的一些文章后大概知道了,原生拦截到这个​<code>​url​</code>​​后会调用​<code>​js​</code>​​的​<code>​window.WebViewJavascriptBridge._fetchQueue​</code>​方法:

安卓拦截到​<code>​url​</code>​​后,知道​<code>​js​</code>​​给安卓发送消息了,所以主动调用​<code>​js​</code>​​的​<code>​_fetchQueue​</code>​​方法,取出之前添加到队列里的消息,因为无法直接读取​<code>​js​</code>​​方法返回的数据,所以把格式化后的消息添加到​<code>​url​</code>​​上,再次通过​<code>​iframe​</code>​​来发送,此时原生又会拦截到​<code>​yy://return/_fetchQueue/​</code>​​这个​<code>​url​</code>​​,那么取出后面的消息,解析出要其中要执行的原生方法名和参数后执行对应的原生方法,当原生方法执行完后又会主动调用​<code>​js​</code>​​的​<code>​window.WebViewJavascriptBridge._handleMessageFromNative​</code>​方法:

看一下​<code>​_dispatchMessageFromNative​</code>​方法做了什么:

​<code>​messageJSON​</code>​​就是原生发回的消息,里面除了执行完原生方法后返回的相关信息外,还带着之前我们传给它的​<code>​callbackId​</code>​​,所以我们可以通过这个​<code>​id​</code>​​来在​<code>​responseCallbacks​</code>​​里找到关联的回调并执行,本次​<code>​js​</code>​​调用原生方法流程结束。但是,明显函数里还有不存在​<code>​id​</code>​​时的分支,这里是用来干啥的呢,我们前面介绍的都是​<code>​js​</code>​​调用原生方法,但是显然,原生也可以直接给​<code>​js​</code>​​发消息,比如常见的拦截返回键功能,当原生监听到返回键事件后它会主动发送信息告诉前端页面,页面就可以执行对应的逻辑,这个​<code>​else​</code>​分支就是用来处理这种情况:

比如我们要监听原生的返回键事件,我们先通过​<code>​window.WebViewJavascriptBridge​</code>​对象的方法注册一下:

​<code>​registerHandler​</code>​方法如下:

很简单,把我们要监听的事件名和方法都存储到​<code>​messageHandlers​</code>​对象上,然后如果原生监听到返回键事件后会发送如下结构的消息:

这样就可以通过​<code>​handlerName​</code>​找到我们注册的函数进行执行了。

到此,安卓环境的​<code>​js​</code>​和原生互相调用的逻辑就结束了,总结一下就是:

1.​<code>​js​</code>​调用原生

生成一个唯一的​<code>​id​</code>​​,把回调和​<code>​id​</code>​​保存起来,然后将要发送的信息(带上本次生成的唯一id)添加到一个队列里,之后通过​<code>​iframe​</code>​​发送一个自定义协议的请求,原生拦截到后调用​<code>​js​</code>​​的​<code>​window.WebViewJavascriptBridge​</code>​​对象的一个方法来获取队列的信息,解析出请求和参数后执行对应的原生方法,然后再把响应(带上前端传来的id)通过调用​<code>​js​</code>​​的​<code>​window.WebViewJavascriptBridge​</code>​​的指定方法传递给前端,前端再通过​<code>​id​</code>​找到之前存储的回调,进行执行。

2.原生调用​<code>​js​</code>​

首先前端需要事先注册要监听的事件,把事件名和回调保存起来,然后原生在某个时刻会调用​<code>​js​</code>​​的​<code>​window.WebViewJavascriptBridge​</code>​​对象的指定方法,前端根据返回参数的事件名找到注册的回调进行执行,同时原生也会传过来一个​<code>​id​</code>​​,如果前端执行完相应逻辑后还要给原生回消息,那么要把该​<code>​id​</code>​​带回去,原生根据该​<code>​id​</code>​来找到对应的回调进行执行。

可以看到,​<code>​js​</code>​和原生两边的逻辑都是一致的。

ios

​<code>​ios​</code>​​和安卓基本是一致的,部分细节上有点区别,首先是协议不一样,​<code>​ios​</code>​的是这样的:

然后​<code>​ios​</code>​​初始化创建​<code>​iframe​</code>​的时候会发送一个请求:

再然后是​<code>​ios​</code>​​获取我们的消息队列时不需要通过​<code>​iframe​</code>​​,它能直接获取执行​<code>​js​</code>​函数返回的数据:

其他部分都是一样的。

总结

本文分析了一下​<code>​jsBridge​</code>​的源码,可以发现其实是个很简单的东西,但是平时可能就没有去认真了解过它,总想做一些”大“的事情,以至于沦为了一个”好高骛远“的人,希望各位不要像笔者一样。

另外本文分析的只是笔者公司的​<code>​jsBridge​</code>​实现,可能有不一样、更好或更新的实现,欢迎留言探讨。

上一篇: 中国意境
下一篇: WIN命令

继续阅读