我司的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>实现,可能有不一样、更好或更新的实现,欢迎留言探讨。