加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

React-Native系列Android——Native与Javascript通信原理(一)

发布时间:2020-12-15 03:35:01 所属栏目:百科 来源:网络整理
导读:React-Native 最核心的是 Native 与 Javascript 之间的通信,而且是双向通信, Native 层到 Javascript 层, Javascript 层到 Native 层,虽说是两个方向,但实现上大同小异,我们先从 Native 层入手,研究一下 Native 调用 Javascript 的过程。 1、通信模型

React-Native最核心的是NativeJavascript之间的通信,而且是双向通信,Native层到Javascript层,Javascript层到Native层,虽说是两个方向,但实现上大同小异,我们先从Native层入手,研究一下Native调用Javascript的过程。


1、通信模型

Android应用层的程序语言是JavaReact-NativeNative端的框架实现用的也是Java语言,所以实质上是JavaJavascript两种程序语言的调用。

其实这个过程,在Android系统上已经有了实现,就是WebView,熟悉WebView的都知道底层实现是WebKit,尽管在Android 4.4系统上切换成了Chromium,但归根结底还是WebKit的变种,只是加了谷歌自己的一些东西。然而React-NativeWebView并没有一点关系,而且后者的WebKit内核也不支持ES6特性(React语法大多基于ES6),那怎么办?只能自己弄一套最新的WebKit作为React-Native的解释器了,这个从安卓工程lib目录下面的libjsc.so动态链接库文件可以印证,这样做还有两个重要好处就是兼容绝大多少设备版本和方便添加自定义功能。

所以由此,我们大概可以猜到React-Native的通信原理,画一张图来简单地描述一下:


2、Java层实现

之前说过,React-Native的重要设计思想是组件化,为了便于维护扩展和降低耦合,React-Native并没有为了实现某一具体的通信编写代码(比如上篇博文所讲的触摸事件传递),而是设计了一套标准用于组件化。这套标准是向开发者开放的,开发者可以自行编写需要的组件用来在NativeJavascript之间通信,虽然这并不是推荐的选择。

2.1 JavaScriptModule组件

React-Native官方实现了一定数量的组件,比如触摸事件组件,按键组件等,这些组件都位于CoreModulesPackage中,属于默认加载的。所有的组件都必须继承JavaScriptModule接口标准。JavaScriptModule位于com.facebook.react.bridge包下面:

<code class="hljs applescript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">/**
 * Interface denoting <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">that</span> a <span class="hljs-type" style="box-sizing: border-box;">class</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">is</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">the</span> interface <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">to</span> a module <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">with</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">the</span> same <span class="hljs-property" style="box-sizing: border-box;">name</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">in</span> JS. Calling
 * functions <span class="hljs-function_start" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">on</span></span> this interface will <span class="hljs-constant" style="box-sizing: border-box;">result</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">in</span> corresponding methods <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">in</span> JS being called.
 *
 * When extending JavaScriptModule <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">and</span> registering <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">it</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">with</span> a CatalystInstance,all public methods
 * are assumed <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">to</span> be implemented <span class="hljs-function_start" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">on</span></span> a JS module <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">as</span> this <span class="hljs-type" style="box-sizing: border-box;">class</span>. 
 *
 * NB: JavaScriptModule <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">does</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">not</span> allow method <span class="hljs-property" style="box-sizing: border-box;">name</span> overloading because JS <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">not</span> allow method <span class="hljs-property" style="box-sizing: border-box;">name</span>
 * overloading.
 */
@DoNotStrip
public interface JavaScriptModule {
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,221,221); list-style: none; text-align: right; background-color: rgb(238,238,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>

阅读一下注释,主要有三点信息:
1、所有组件必须继承JavaScriptModule,并注册在CatalystInstance中。
2、所有public方法与Javascript层保持同名并由后者具体实现。
3、由于Javascript不支持重载,所以Java中也不能有重载。

仔细的读者会发现,注释里有两个单词非常关键。extendingimplementedJavaextendJavascriptimplement,也就是说Java层只做接口定义,而实现由Javascript完成。所以,搜索一下JavaScriptModule的子类会发现它们都是接口,没有具体实现类。

有点晦涩但其实很好理解,举个简单的例子。去餐馆吃饭,顾客(Java)只要定义(interface)好想吃什么菜,然后和餐馆(Bridge)说,餐馆会通知自己的厨师(Javascript)把具体的菜做好。这就是一个简单的通信过程Java->Bridge->Javascript

2.2 JavaScriptModule组件的注册

上一篇文章讲的触摸事件的处理,里面提到一个RCTEventEmitter的类,用来将每个触摸事件都传递给Javascript层,这个组件就是继承于JavaScriptModule

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">RCTEventEmitter</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">extends</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">JavaScriptModule</span> {</span>
  <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">receiveEvent</span>(<span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">int</span> targetTag,String eventName,@Nullable WritableMap event);
  <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">receiveTouches</span>(
      String eventName,WritableArray touches,WritableArray changedIndices);
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul>

先前注释中第1点,所有JavaScriptModule组件都必须在CatalystInstance中注册,那我们来看一下注册的过程。

RCTEventEmitterfacebook官方定义的,组装在CoreModulesPackage中,而所有的package都是在com.facebook.react.ReactInstanceManagerImpl中处理的,看一下代码:

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">class ReactInstanceManagerImpl extends ReactInstanceManager {

  <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">...</span>

  private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,JSBundleLoader jsBundleLoader) {
     <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">...</span>
     <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">try</span> {
      CoreModulesPackage coreModulesPackage =
          new CoreModulesPackage(this,mBackBtnHandler,mUIImplementationProvider);
      processPackage(coreModulesPackage,reactContext,nativeRegistryBuilder,jsModulesBuilder);
    } finally {
      <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">...</span>
    }

    <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">for</span> (ReactPackage reactPackage : mPackages) {
      <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">...</span>
      <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">try</span> {
        processPackage(reactPackage,jsModulesBuilder);
      } finally {
        <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">...</span>
      }
    }

  }

  private void processPackage(ReactPackage reactPackage,ReactApplicationContext reactContext,NativeModuleRegistry.Builder nativeRegistryBuilder,JavaScriptModulesConfig.Builder jsModulesBuilder) {

    <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">...</span>

    <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">for</span> (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()){
      jsModulesBuilder.add(jsModuleClass);
    }
  }
  <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">...</span>

}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li></ul>

可以看到CoreModulesPackage和开发者扩展自定义的mPackages都是通过processPackage方法里添加到JavaScriptModulesConfig里注册的。

简单的建造者模式,我们直接看一下JavaScriptModulesConfig类,位于包com.facebook.react.bridge下。

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',136); box-sizing: border-box;">class</span> JavaScriptModulesConfig {

  <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">private</span> final List<JavaScriptModuleRegistration> mModules;

  <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">private</span> <span class="hljs-title" style="box-sizing: border-box;">JavaScriptModulesConfig</span>(List<JavaScriptModuleRegistration> modules) {
    mModules = modules;
  }

  <span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">/*package*/</span> List<JavaScriptModuleRegistration> getModuleDefinitions() {
    <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">return</span> mModules;
  }

  ...
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li></ul>

JavaScriptModule明显是通过构造函数传入,然后又通过一个getter方法提供出去了,看样子JavaScriptModulesConfig只起到了一个中间者的作用,并不是真正的注册类。

回看一下之前的ReactInstanceManagerImpl类代码,createReactContext中还有一段,如下:

 

看来最终javaScriptModulesConfig是用来构建CatalystInstance的,正如注释所讲,果然没有骗我。

CatalystInstance只是一个接口,实现类是CatalystInstanceImpl,同样位于包com.facebook.react.bridge下。Catalyst单词的中文意思是催化剂,化学中是用来促进化学物之间的反应,难道说CatalystInstance是用来催化NativeJavascript之间的反应?让我们来瞧一瞧真面目吧。

 

CatalystInstanceImpl构造方法里,jsModulesConfig又被用来初始化JavaScriptModuleRegistry,字面意思是JavaScriptModule注册表,看样子终于找到注册类了。

先不着急,继续往下看CatalystInstanceImpl中还初始化了ReactBridge,字面意思就是真正连接NativeJavascript的桥梁了。ReactBridge干了什么呢?调用了setGlobalVariable方法,参数里面的buildModulesConfigJSONProperty方法又用到了JavaScriptModulesConfig,顺便来看看。

 

这个方法最终的目的是生成一个JSON字符串,而字符串由什么构成呢?nativeModulejsModulesnativeModule先不管,jsModulesConfig调用了writeModuleDescriptions

回头看看刚才讲的JavaScriptModulesConfig这个中间类。

 

writeModuleDescriptions这个方法干了什么事呢?遍历所有JavaScriptModulepublic方法,然后通过methodID标识作为key存入JSON生成器中,用来最终生成JSON字符串。

这里稍微梳理一下。从initializeBridge->setGlobalVariable->buildModulesConfigJSONProperty->writeModuleDescriptions,整个过程作用是将所有JavaScriptModule的信息生成JSON字符串预先保存到Bridge中。至于为什么这么做,先挖个坑,研究到后面自然就明白了。

2.3 JavaScriptModule组件的调用

继续之前说到的NativeModuleRegistry注册表类,位于包com.facebook.react.bridge中。

<code class="hljs handlebars has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="xml" style="box-sizing: border-box;"><span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">/*package*/</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">JavaScriptModuleRegistry</span> {</span>

  <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">final</span> HashMap<<span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">Class</span><? <span class="hljs-keyword" style="color: rgb(0,102);">JavaScriptModule</span>>,<span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">JavaScriptModule</span>> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">mModuleInstances</span>;

  <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">public</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">JavaScriptModuleRegistry</span>(<span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">CatalystInstanceImpl</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">instance</span>,102);">JavaScriptModulesConfig</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">config</span>) {</span>
    mModuleInstances = <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">new</span> HashMap<>();
    <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">for</span> (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) {
      <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0,102);">moduleInterface</span> = <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">registration</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">getModuleInterface</span>();
      <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">JavaScriptModule</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">interfaceProxy</span> = (<span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">JavaScriptModule</span>) <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">Proxy</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">newProxyInstance</span>(
          <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">moduleInterface</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">getClassLoader</span>(),102);">new</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102,102);">Class</span>[]{</span>moduleInterface},<span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">new</span> JavaScriptModuleInvocationHandler(instance,registration));

      mModuleInstances.put(moduleInterface,interfaceProxy);
    }
  }

  ...
}</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li></ul>

当每次看到这段代码的时候,都有一种惊艳的感觉。前面说过JavaScriptModule组件都是接口定义,在Java端是没有实现类的,被注册的都是Class类,没有真正的实例,Java端又如何来调用呢?答案是:动态代理

这里使用动态代理除了创建JavaScriptModule组件的实例化类外,还有一个重要的作用,即JavaScriptModule所有的方法调用都会被invoke拦截,这样就可以统一处理所有从Java端向Javascript端的通信请求。

JavaScriptModuleInvocationHandlerJavaScriptModuleRegistry的一个内部类,动态代理的拦截类。

 

JavaScriptModule方法拦截invoke里调用了CatalystInstancecallFunction方法,主要传入了ModuleIdMethodIdArguments这三个重要参数(tracingName忽略)。

 

分析这里终于豁然开朗了,原来所有Java层向Javascript层的通信请求都是走的ReactBridge.callFunction

又有了一个问题,Javascript层具体怎么知道Java层的调用信息呢?

还是之前举的餐馆吃饭的例子,顾客(Java)把菜名告诉餐馆(Bridge),餐馆再通知厨师(Javascript),厨师自然就知道该做什么菜了。当然前提是要约定一个菜单了,菜单包含所有的菜名。

还记得之前挖的一个坑吗?就是CatalystInstanceImpl中初始化ReactBridge的时候,所有JavaScriptModule信息都被以moduleID+methodID形式生成的JSON字符串预先存入了ReactBridge中,这其实就是一个菜单索引表了。餐馆(Bridge)知道了菜名(moduleID+methodID)就能告诉厨师(Javascript)顾客(Java)想吃什么了,当然有时还少不了不放辣这种需求了(arguments)。

所以callFunction中有了moduleId+methodId+arguments,就可以调用到Javascript中的实现了。


3、Bridge层实现

通信模型图中要调用WebKit的实现,少不了Bridge这个桥梁,因为Java是不能直接调用WebKit,但是如果Java通过JNIJNI再调用WebKit不就OK了么?

继续前面说的ReactBridgesetGlobalVariablecallFunction方法。

 

果然是JNI调用,而JNI层的入口是react/jni/OnLoad.cpp,和常规的javah规则不同,它是通过RegisterNatives方式注册的,JNI_OnLoad里面注册了setGlobalVariablecallFunctionnative本地方法。

废话不多说,来看看c++setGlobalVariablecallFunction的实现吧。

<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',136); box-sizing: border-box;">namespace</span> bridge {

    <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">void</span> setGlobalVariable(JNIEnv* env,jobject obj,jstring propName,jstring jsonValue) {
    <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">auto</span> bridge = extractRefPtr<CountableBridge>(env,obj);
    bridge->setGlobalVariable(fromJString(env,propName),fromJString(env,jsonValue));
  }

   <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">void</span> callFunction(JNIEnv* env,JExecutorToken::jhybridobject jExecutorToken,jint moduleId,jint methodId,NativeArray::jhybridobject args,jstring tracingName) {
   <span class="hljs-keyword" style="color: rgb(0,obj);
   <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">auto</span> arguments = cthis(wrap_alias(args));
   <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">try</span> {
      bridge->callFunction(
      cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),folly::to<<span class="hljs-built_in" style="color: rgb(102,102); box-sizing: border-box;">std</span>::<span class="hljs-built_in" style="color: rgb(102,102); box-sizing: border-box;">string</span>>(moduleId),102); box-sizing: border-box;">string</span>>(methodId),<span class="hljs-built_in" style="color: rgb(102,102); box-sizing: border-box;">std</span>::move(arguments-><span class="hljs-built_in" style="color: rgb(102,102); box-sizing: border-box;">array</span>),tracingName)
    );
   } <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">catch</span> (...) {
     translatePendingCppExceptionToJavaException();
    }
  }

}
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li></ul>
 

OnLoad只是一个调用入口,最终走的还是CountableBridge,而CountableBridge继承的是Bridge,只是加了一个计数功能。实现代码在react/Bridge.cpp中。

 
 

两个方法调用的过程差不多,都是塞进runOnExecutorQueue执行队列里面等待调用,回调都是走的JSExecutor,所以还是要看JSExecutor了。

这边提一下,Bridge类构造的时候会初始化ExecutorQueue,通过JSCExecutorFactory创建JSExecutor,而JSExecutor的真正实现类是JSCExecutor。通过jni/react/JSCExecutor.h头文件能够验证这一点,此处略过不细讲。

绕来绕去,稍微有点晕,最后又跑到JSCExecutor.cpp里面了。

 

finally哈,前面Java层构造的JavaScriptModule信息JSON串,终于在这里被处理了,不用想也知道肯定是解析后存为一张映射表,然后等callFunction的时候映射调用,接下来看callFunction的处理。

 
 

callFunction里面执行的是executeJSCallWithJSC,而executeJSCallWithJSC里面将methodNamejsonArgs拼接成了一个applyJavascript执行语句,最后调用WebKitevaluateScript的来执行这个语句,完成BridgeJavascript的调用。

当然这里还有一个Bridge.cpp类的callNativeModules反向通信,意图是将Javascript语句执行结果通知回Native,这个过程留在以后的文章中慢慢研究,先行略过。

最后,总结一下Bridge层的调用过程:OnLoad.cpp->Bridge.cpp->JSCExecutor.cpp->evaluateScript(WebKit)


4、Javascript层实现

Javascript的通信,实质上是Weikit执行Javascript语句,调用流程是Bridge->WebKit->JavascriptWebKit中提供了许多与Javascript通信的API,比如evaluateScriptJSContextGetGlobalObjectJSObjectSetProperty等。

4.1、JavaScriptModule映射表

前面说过,所有JavaScriptModule信息是调用的setGlobalVariable方法生成一张映射表,这张映射表最终肯定是要保存在Javascript层的,回头仔细分析下jni/react/JSCExecutor.cpp的代码。

 

JSContextGetGlobalObjectWeiKit的方法,其目的是获取Global全局对象,jsPropertyName方法字面意思就是Javascript对象的属性名,参数propName是从Java层传递过来的,在CatalystInstanceImpl.java类中可以印证这一点,具体值有两个:__fbBatchedBridgeConfig__RCTProfileIsProfiling

<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">bridge<span class="hljs-preprocessor" style="color: rgb(68,68,68); box-sizing: border-box;">.setGlobalVariable</span>(
          <span class="hljs-string" style="color: rgb(0,jsModulesConfig))<span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">;</span>
      bridge<span class="hljs-preprocessor" style="color: rgb(68,Systrace<span class="hljs-preprocessor" style="color: rgb(68,68); box-sizing: border-box;">.isTracing</span>(Systrace<span class="hljs-preprocessor" style="color: rgb(68,68); box-sizing: border-box;">.TRACE</span>_TAG_REACT_APPS) ? <span class="hljs-string" style="color: rgb(0,0); box-sizing: border-box;">"false"</span>)<span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

我们所关注的是__fbBatchedBridgeConfig,这个值被传递到刚刚说的JSCExecutor::setGlobalVariable生成jsPropertyName对象,而jsonValue同样被JSValueMakeFromJSONString处理成一个jsValue对象,这样一来property-value就全都有了。最后JSObjectSetProperty方法,顾名思义,就是设置属性,使用的是Global全局对象,如果翻译成Javascript代码,大概应该是这样:

<code class="hljs fix has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-attribute" style="box-sizing: border-box;">global.__fbBatchedBridgeConfig </span>=<span class="hljs-string" style="color: rgb(0,0); box-sizing: border-box;"> jsonValue;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

或者

<code class="hljs vbnet has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-built_in" style="color: rgb(102,102); box-sizing: border-box;">Object</span>.defineProperty(<span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">global</span>,<span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">'__fbBatchedBridgeConfig',{ value: jsonValue});</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

作用其实是一样的。既然javascript接收到了关于JavaScriptModule的信息,那就要生成一张映射表了。

我们来看node_modulesreact-nativeLibrariesBatchedBridgeBatchedBridge.js的代码。

<code class="hljs javascript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',136); box-sizing: border-box;">const</span> MessageQueue = <span class="hljs-built_in" style="color: rgb(102,102); box-sizing: border-box;">require</span>(<span class="hljs-string" style="color: rgb(0,0); box-sizing: border-box;">'MessageQueue'</span>);

<span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">const</span> BatchedBridge = <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">new</span> MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,__fbBatchedBridgeConfig.localModulesConfig,);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

由于__fbBatchedBridgeConfig对象是被直接定义成Global全局对象的属性,就可以直接调用了,类似于window对象,__fbBatchedBridgeConfig对象里又有两个属性:remoteModuleConfiglocalModulesConfig

哪儿冒出来的呢?

有心的读者能猜到这两个属性都是定义在jsonValue里面的,为了验证这一点,我们再回头搜索下生成JSON串的地方,代码在CatalystInstanceImpl.java里面。

 

这段代码分析过,localModulesConfig里面存的就是JavaScriptModule的信息,果然没错!

再来看刚刚的BatchedBridge.js

 

JavaScriptModule的信息,又被传入MessageQueue的构造函数里面了,继续往MessageQueue里面看,代码在node_modulesreact-nativeLibrariesUtilitiesMessageQueue.js

 

localModules参数就是JavaScriptModule信息了,又被传进了_genLookupTables的方法里,同时还有两个参数_moduleTable_methodTable,猜测一下,应该就是我们找的映射表了,一张module映射表,一张method映射表。

<code class="hljs autohotkey has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">_genLookupTables(modulesConfig,moduleTable,methodTable) {
    modulesConfig.forEach((config,moduleID) => {
      this._genLookup(config,moduleID,methodTable)<span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">;</span>
    })<span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">;</span>
  }

  _genLookup(config,methodTable) {
    <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">if</span> (!config) {
      <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">return</span><span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">;</span>
    }

    let moduleName,methods<span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">;</span>
    <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">if</span> (moduleHasConstants(config)) {
      [moduleName,methods] = config<span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">;</span>
    } <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">else</span> {
      [moduleName,0); box-sizing: border-box;">;</span>
    }

    moduleTable[moduleID] = moduleName<span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">;</span>
    methodTable[moduleID] = Object.assign({},methods)<span class="hljs-comment" style="color: rgb(136,0); box-sizing: border-box;">;</span>
  }
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li></ul>

哈哈,和猜测的一样,生成了两张映射表,存放在了MessageQueue类里面。

4.2、callFunction的调用

回顾一下JSCExecutor.cpp中的最终callFunction调用过程。

 

executeJSCallWithJSC中有个生成语句的代码,methodName的值为callFunctionReturnFlushedQueue,所以拼装成的Javascript语句是:

 

首先,在Javascript的运行环境下,当前作用域条件下__fbBatchedBridge能被直接调用,必须是Global全局对象的属性。与4.1__fbBatchedBridgeConfig不同的是,jni并没有手动设置__fbBatchedBridge为全局对象的属性,那唯一的可能就是在Javascript里面通过Object.defineProperty来设置了。

搜索一下,在BatchedBridge.js中找到如下代码:

 

这段代码等价于

<code class="hljs lasso has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',102); box-sizing: border-box;">global</span><span class="hljs-built_in" style="color: rgb(102,102); box-sizing: border-box;">.</span>__fbBatchedBridge <span class="hljs-subst" style="color: rgb(0,0); box-sizing: border-box;">=</span> <span class="hljs-literal" style="color: rgb(0,102); box-sizing: border-box;">new</span> MessageQueue(<span class="hljs-attribute" style="box-sizing: border-box;">...</span>args);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

再次替换一下,callFuction调用的是:

 

Arguments参数再具体一下,就变成了:

<code class="hljs oxygene has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">MessageQueue.callFunctionReturnFlushedQueue.apply(null,136); box-sizing: border-box;">module</span>,<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">method</span>,<span class="hljs-title" style="box-sizing: border-box;">args</span>);</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

又回到MessageQueue.js了,前面才分析到它里面存放了两张映射表,现在第一件事当然是作匹配查找了,看MessageQueue.callFunctionReturnFlushedQueue的具体调用吧。

<code class="hljs coffeescript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">callFunctionReturnFlushedQueue(<span class="hljs-built_in" style="color: rgb(102,102); box-sizing: border-box;">module</span>,method,args) {
    guard<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-params" style="color: rgb(102,102); box-sizing: border-box;">(() => {
      <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">this</span>.__callFunction(<span class="hljs-built_in" style="box-sizing: border-box;">module</span>,args);
      <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">this</span>.__callImmediates();
    })</span>;

    <span class="hljs-title" style="box-sizing: border-box;">return</span> <span class="hljs-title" style="box-sizing: border-box;">this</span>.<span class="hljs-title" style="box-sizing: border-box;">flushedQueue</span><span class="hljs-params" style="color: rgb(102,102); box-sizing: border-box;">()</span>;
  }

<span class="hljs-title" style="box-sizing: border-box;">var</span> <span class="hljs-title" style="box-sizing: border-box;">guard</span> = <span class="hljs-params" style="color: rgb(102,102); box-sizing: border-box;">(fn)</span> =></span> {
  <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">try</span> {
    fn();
  } <span class="hljs-keyword" style="color: rgb(0,136); box-sizing: border-box;">catch</span> (error) {
    ErrorUtils.reportFatalError(error);
  }
};</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221,238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li></ul>

Lambda+闭包,代码很简洁,但阅读起来比较吃力,而React里面都是这种,强烈吐槽一下。

定义guard目的是为了统一捕获错误异常,忽略这一步,以上代码等价于:

 

this指的是当前MessageQueue对象,所以找到MessageQueue.__callFunction方法:

 

这里就是通过moduleIDmethodID来查询两张映射Table了,获取到了具体的moduleNamemethodName,接着肯定要做调用Javascript对应组件了。

如果在餐馆吃饭的例子中,场景应该是这样的:顾客点完菜,餐馆服务人员也已经把菜名通知到厨师了,厨师该做菜了吧。等等,其中还漏了一步,就是这个厨师会不会做这道菜,如果让川菜师傅去做粤菜肯定是不行的,所以厨师的能力里还应该有一张技能清单,做菜前厨师需要判断下自己的技能单子里面有没有这道菜。

代码同理,MessageQueue里面有一个_callableModules数组,它就是用来存放哪些Javascript组件是可以被调用的,正常情况下_callableModules的数据和JavaScriptModules的数据(包括方法名和参数)理应是完全对应的。

我们来瞧瞧_callableModules数据初始化的过程,同样是在MessageQueue.js中:

 

所有的Javascript组件都是通过registerCallableModule来注册的,比如触摸事件RCTEventEmitter.java对应的组件RCTEventEmitter.js,代码路径是
node_modulesreact-nativeLibrariesBatchedBridgeBatchedBridgedModulesRCTEventEmitter.js

 

BatchedBridge可以看成是MessageQueue,被注册的组件是ReactNativeEventEmitter,代码位于node_modulesreact-nativeLibrariesReactNativeReactNativeEventEmitter.js

 

仔细对照RCTEventEmitter .java比较,是不是完全一致,哈哈

 

继续__callFunction方法代码的最后一步

 

如果以RCTEventEmitterreceiveTouches方法调用为例,具体语句应该是这样:

 

结束!通信完成,大功告成!

5、总结

整个通信过程涉及到三种程序语言:JavaC++Javascript,这还只是单向的通信流程,如果是逆向则更加复杂,由于篇幅的关系,留到以后的博客里面研究。

最后总结一下几个关键点:

1、Java层

JavaScriptModule接口类定义通信方法,在ReactApplicationContext创建的时候存入注册表类JavaScriptModuleRegistry中,同时通过动态代理生成代理实例,并在代理拦截类JavaScriptModuleInvocationHandler中统一处理发向Javascript的所有通信请求。

CatalystInstanceImpl类内部的ReactBridge具体实现与Javascript的通信请求,它是调用Bridge Jni的出口。在ReactBridge被创建的时候会将JavaScriptModule信息表预先发给Javascript层用来生成映射表。

2、C++层

OnLoadjni层的调用入口,注册了所有的native方法,其内部调用又都是通过CountableBridge来完成的,CountableBridgeBridge的无实现子类,而在Bridge里面JSCExecutor才是真正的执行者。

JSCExecutor将所有来自Java层的通信请求封装成Javascript执行语句,交给WebKit内核完成向Javascript层的调用。

3、Javascript层

BatchedBridgeJavascript层的调用入口,而其又是MessageQueue的伪装者。MessageQueue预先注册了所有能够接收通信请求的组件_callableModules,同时也保存着来自JavaJavaScriptModule的两张映射表。

接收通信请求时,先通过映射表确认具体请求信息,再确认Javascript组件是否可以被调用,最后通过apply方式完成执行。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读