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

前端之家收集整理的这篇文章主要介绍了React-Native系列Android——Native与Javascript通信原理(一)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

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中还有一段,如下:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,JSBundleLoader jsBundleLoader)
     <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">...</span>

     JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();
     JavaScriptModulesConfig javaScriptModulesConfig;
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">try</span> {
      javaScriptModulesConfig = jsModulesBuilder.build();
    } 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;">...</span>
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
        .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
        .setJSExecutor(jsExecutor)
        .setRegistry(nativeModuleRegistry)
        .setJSModulesConfig(javaScriptModulesConfig)
        .setJSBundleLoader(jsBundleLoader)
        .setNativeModuleCallExceptionHandler(exceptionHandler);

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

    CatalystInstance catalystInstance;
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">try</span> {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      <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></ul>

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

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">public class CatalystInstanceImpl implements CatalystInstance {

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

     private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,final JavaScriptExecutor jsExecutor,final NativeModuleRegistry registry,final JavaScriptModulesConfig jsModulesConfig,final JSBundleLoader jsBundleLoader,NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {

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

    mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this,jsModulesConfig);

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">try</span> {
      mBridge = mReactQueueConfiguration.getJSQueueThread().callOnQueue(
          new Callable<ReactBridge>() {
            @Override
            public ReactBridge call() throws Exception {
              <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> {
                <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> initializeBridge(jsExecutor,jsModulesConfig);
              } finally {
                  <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">...</span>
              }
            }
          }).get();
    } catch (Exception t) {
      throw new RuntimeException(<span class="hljs-string" style="color: rgb(0,136,0); Box-sizing: border-Box;">"Failed to initialize bridge"</span>,t);
    }
  }

  private ReactBridge initializeBridge(
      JavaScriptExecutor jsExecutor,JavaScriptModulesConfig jsModulesConfig) {
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">...</span>
    ReactBridge bridge;
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">try</span> {
      bridge = new ReactBridge(
          jsExecutor,new NativeModulesReactCallback(),mReactQueueConfiguration.getNativeModulesQueueThread());
    } 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;">...</span>
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">try</span> {
      bridge.setGlobalVariable(
          <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"__fbBatchedBridgeConfig"</span>,buildModulesConfigJSONProperty(mJavaRegistry,jsModulesConfig));
      bridge.setGlobalVariable(
          <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"__RCTProfileIsProfiling"</span>,Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"true"</span> : <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"false"</span>);
    } finally {
        <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> bridge;
  }


   <span class="hljs-keyword" style="color: rgb(0,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><li style="Box-sizing: border-Box; padding: 0px 5px;">39</li><li style="Box-sizing: border-Box; padding: 0px 5px;">40</li><li style="Box-sizing: border-Box; padding: 0px 5px;">41</li><li style="Box-sizing: border-Box; padding: 0px 5px;">42</li><li style="Box-sizing: border-Box; padding: 0px 5px;">43</li><li style="Box-sizing: border-Box; padding: 0px 5px;">44</li><li style="Box-sizing: border-Box; padding: 0px 5px;">45</li><li style="Box-sizing: border-Box; padding: 0px 5px;">46</li><li style="Box-sizing: border-Box; padding: 0px 5px;">47</li><li style="Box-sizing: border-Box; padding: 0px 5px;">48</li><li style="Box-sizing: border-Box; padding: 0px 5px;">49</li><li style="Box-sizing: border-Box; padding: 0px 5px;">50</li><li style="Box-sizing: border-Box; padding: 0px 5px;">51</li><li style="Box-sizing: border-Box; padding: 0px 5px;">52</li><li style="Box-sizing: border-Box; padding: 0px 5px;">53</li><li style="Box-sizing: border-Box; padding: 0px 5px;">54</li><li style="Box-sizing: border-Box; padding: 0px 5px;">55</li><li style="Box-sizing: border-Box; padding: 0px 5px;">56</li><li style="Box-sizing: border-Box; padding: 0px 5px;">57</li><li style="Box-sizing: border-Box; padding: 0px 5px;">58</li><li style="Box-sizing: border-Box; padding: 0px 5px;">59</li><li style="Box-sizing: border-Box; padding: 0px 5px;">60</li><li style="Box-sizing: border-Box; padding: 0px 5px;">61</li><li style="Box-sizing: border-Box; padding: 0px 5px;">62</li><li style="Box-sizing: border-Box; padding: 0px 5px;">63</li><li style="Box-sizing: border-Box; padding: 0px 5px;">64</li><li style="Box-sizing: border-Box; padding: 0px 5px;">65</li><li style="Box-sizing: border-Box; padding: 0px 5px;">66</li><li style="Box-sizing: border-Box; padding: 0px 5px;">67</li></ul>

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

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; 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;">private</span> String <span class="hljs-title" style="Box-sizing: border-Box;">buildModulesConfigJSONProperty</span>(
      NativeModuleRegistry nativeModuleRegistry,JavaScriptModulesConfig jsModulesConfig) {
    JsonFactory jsonFactory = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">new</span> JsonFactory();
    StringWriter writer = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">new</span> StringWriter();
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">try</span> {
      JsonGenerator jg = jsonFactory.createGenerator(writer);
      jg.writeStartObject();
      jg.writeFieldName(<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"remoteModuleConfig"</span>);
      nativeModuleRegistry.writeModuleDescriptions(jg);
      jg.writeFieldName(<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"localModulesConfig"</span>);
      jsModulesConfig.writeModuleDescriptions(jg);
      jg.writeEndObject();
      jg.close();
    } <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">catch</span> (IOException ioe) {
      <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">throw</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">new</span> RuntimeException(<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"Unable to serialize JavaScript module declaration"</span>,ioe);
    }
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> writer.getBuffer().toString();
  }</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>

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

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">public class JavaScriptModulesConfig {

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

     void writeModuleDescriptions(JsonGenerator jg) throws IOException {
    jg.writeStartObject();
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">for</span> (JavaScriptModuleRegistration registration : mModules) {
      jg.writeObjectFieldStart(registration.getName());
      appendJSModuleToJSONObject(jg,registration);
      jg.writeEndObject();
    }
    jg.writeEndObject();
  }

  private void appendJSModuleToJSONObject(
      JsonGenerator jg,JavaScriptModuleRegistration registration) throws IOException {
    jg.writeObjectField(<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"moduleID"</span>,registration.getModuleId());
    jg.writeObjectFieldStart(<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"methods"</span>);
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">for</span> (Method method : registration.getMethods()) {
      jg.writeObjectFieldStart(method.getName());
      jg.writeObjectField(<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"methodID"</span>,registration.getMethodId(method));
      jg.writeEndObject();
    }
    jg.writeEndObject();
  }

   <span class="hljs-keyword" style="color: rgb(0,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></ul>

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的一个内部类,动态代理的拦截类。

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">static</span> <span class="hljs-class" style="Box-sizing: border-Box;"><span class="hljs-keyword" style="color: rgb(0,102);">JavaScriptModuleInvocationHandler</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">implements</span> <span class="hljs-title" style="Box-sizing: border-Box; color: rgb(102,102);">InvocationHandler</span> {</span>

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">final</span> CatalystInstanceImpl mCatalystInstance;
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">final</span> JavaScriptModuleRegistration mModuleRegistration;

    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">public</span> <span class="hljs-title" style="Box-sizing: border-Box;">JavaScriptModuleInvocationHandler</span>(
        CatalystInstanceImpl catalystInstance,JavaScriptModuleRegistration moduleRegistration) {
      mCatalystInstance = catalystInstance;
      mModuleRegistration = moduleRegistration;
    }

    <span class="hljs-annotation" style="color: rgb(155,133,157); Box-sizing: border-Box;">@Override</span>
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">public</span> @Nullable Object <span class="hljs-title" style="Box-sizing: border-Box;">invoke</span>(Object proxy,Method method,Object[] args) <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">throws</span> Throwable {
      String tracingName = mModuleRegistration.getTracingName(method);
      mCatalystInstance.callFunction(
          mModuleRegistration.getModuleId(),mModuleRegistration.getMethodId(method),Arguments.fromJavaArgs(args),tracingName);
      <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">null</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></ul>

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">...</span>

    private final ReactBridge mBridge;

    void callFunction(
      final int moduleId,final int methodId,final NativeArray arguments,final String tracingName) {

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

    mReactQueueConfiguration.getJSQueueThread().runOnQueue(
        new Runnable() {
          @Override
          public void run() {

            <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> {  
             Assertions.assertNotNull(mBridge).callFunction(moduleId,methodId,arguments);
            } finally {
               <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">...</span>
            }
          }
        });
  }

   <span class="hljs-keyword" style="color: rgb(0,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></ul>

分析这里终于豁然开朗了,原来所有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方法

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',102);">ReactBridge</span> <span class="hljs-keyword" style="color: rgb(0,102);">Countable</span> {</span>
  <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">static</span> {
    SoLoader.loadLibrary(REACT_NATIVE_LIB);
  }

   <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">native</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;">callFunction</span>(<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">int</span> moduleId,136); Box-sizing: border-Box;">int</span> methodId,NativeArray arguments);

  <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">void</span> <span class="hljs-title" style="Box-sizing: border-Box;">setGlobalVariable</span>(String propertyName,String jsonEncodedArgument);
}</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></ul>

果然是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>
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">struct</span> CountableBridge : Bridge,Countable {
  <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">using</span> Bridge::Bridge;
};</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></ul>

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">void</span> Bridge::setGlobalVariable(<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">const</span> <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">string</span>& propName,102); Box-sizing: border-Box;">string</span>& jsonValue) {
  runOnExecutorQueue(*m_mainExecutorToken,[=] (JSExecutor* executor) {
    executor->setGlobalVariable(propName,jsonValue);
  });
}</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></ul>
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">void Bridge::callFunction(
    ExecutorToken executorToken,const std::string& moduleId,const std::string& methodId,const folly::dynamic& arguments,const std::string& tracingName) {
  <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">#ifdef WITH_FBSYSTRACE</span>
  int systraceCookie = m_systraceCookie++;
  <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">...</span>
  <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">#endif</span>

  <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">#ifdef WITH_FBSYSTRACE</span>
  runOnExecutorQueue(executorToken,[moduleId,arguments,tracingName,systraceCookie] (JSExecutor* executor) {
  <span class="hljs-keyword" style="color: rgb(0,0); Box-sizing: border-Box;">#else</span>
  runOnExecutorQueue(executorToken,tracingName] (JSExecutor* executor) {
  <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">#endif</span>
    executor->callFunction(moduleId,arguments);
  });
}</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></ul>

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

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

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">void</span> JSCExecutor::setGlobalVariable(<span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">string</span>& jsonValue) {
  <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">auto</span> globalObject = JSContextGetGlobalObject(m_context);
  String jsPropertyName(propName.c_str());

  String jsValueJSON(jsonValue.c_str());
  <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">auto</span> valueToInject = JSValueMakeFromJSONString(m_context,jsValueJSON);

  JSObjectSetProperty(m_context,globalObject,jsPropertyName,valueToInject,<span class="hljs-number" style="color: rgb(0,102,102); Box-sizing: border-Box;">0</span>,NULL);
}</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></ul>

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">void</span> JSCExecutor::callFunction(<span class="hljs-keyword" style="color: rgb(0,102); Box-sizing: border-Box;">string</span>& moduleId,102); Box-sizing: border-Box;">string</span>& methodId,136); Box-sizing: border-Box;">const</span> folly::dynamic& arguments) {
  <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// TODO:  Make this a first class function instead of evaling. #9317773</span>
  <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">std</span>::<span class="hljs-stl_container" style="Box-sizing: border-Box;"><span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">vector</span><folly::dynamic></span> call{
    moduleId,102); Box-sizing: border-Box;">std</span>::move(arguments),};
  <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">string</span> calls = executeJSCallWithJSC(m_context,<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"callFunctionReturnFlushedQueue"</span>,102); Box-sizing: border-Box;">std</span>::move(call));
  m_bridge->callNativeModules(*<span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">this</span>,calls,136); Box-sizing: border-Box;">true</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></ul>
Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',136); Box-sizing: border-Box;">static</span> <span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">string</span> executeJSCallWithJSC(
    JSGlobalContextRef ctx,102); Box-sizing: border-Box;">string</span>& methodName,102); Box-sizing: border-Box;">vector</span><folly::dynamic></span>& arguments) {

  ...

  <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">// Evaluate script with JSC</span>
  folly::dynamic jsonArgs(arguments.begin(),arguments.end());
  <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">auto</span> js = folly::to<folly::fbstring>(
      <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">"__fbBatchedBridge."</span>,methodName,0); Box-sizing: border-Box;">".apply(null,"</span>,folly::toJson(jsonArgs),0); Box-sizing: border-Box;">")"</span>);
  <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">auto</span> result = evaluateScript(ctx,String(js.c_str()),136); Box-sizing: border-Box;">nullptr</span>);
  <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">return</span> Value(ctx,result).toJSONString();
}</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></ul>

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代码

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',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></ul>

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_modules\react-native\Libraries\BatchedBridge\BatchedBridge.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里面。

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',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>

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

再来看刚刚的BatchedBridge.js

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',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>

JavaScriptModule的信息,又被传入MessageQueue的构造函数里面了,继续往MessageQueue里面看,代码node_modules\react-native\Libraries\Utilities\MessageQueue.js

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">class MessageQueue {

  constructor(remoteModules,localModules) {
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">...</span>

    localModules && this._genLookupTables(
      this._genModulesConfig(localModules),this._moduleTable,this._methodTable
    );
}</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></ul>

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调用过程。

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',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></ul>

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">__fbBatchedBridge<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.callFunctionReturnFlushedQueue</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.apply</span>(null,jsonArgs)<span class="hljs-comment" style="color: rgb(136,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li></ul>

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

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">const MessageQueue = <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">require</span>(<span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">'MessageQueue'</span>);

const BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,);

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

Object.defineProperty(global,0); Box-sizing: border-Box;">'__fbBatchedBridge'</span>,{ value: BatchedBridge });

module.exports = BatchedBridge;
</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></ul>

这段代码等价于

<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调用的是:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">MessageQueue<span class="hljs-preprocessor" style="color: rgb(68,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li></ul>

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目的是为了统一捕获错误异常,忽略这一步,以上代码等价于:

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">callFunctionReturnFlushedQueue(<span class="hljs-keyword" style="color: rgb(0,<span class="hljs-title" style="Box-sizing: border-Box;">args</span>) <span class="hljs-comment" style="color: rgb(136,0); Box-sizing: border-Box;">{
    this.__callFunction(module,args);
    this.__callImmediates();
    return this.flushedQueue();
  }</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><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></ul>

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">__callFunction(module,args) {
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">if</span> (isFinite(module)) {
      method = this._methodTable[module][method];
      module = this._moduleTable[module];
    }
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">...</span>
    var moduleMethods = this._callableModules[module];
    invariant(
      !!moduleMethods,0); Box-sizing: border-Box;">'Module %s is not a registered callable module.'</span>,module
    );

    moduleMethods[method].apply(moduleMethods,args);
    <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></ul>

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

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

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

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">registerCallableModule(<span class="hljs-property" style="Box-sizing: border-Box;">name</span>,methods) {
    this._callableModules[<span class="hljs-property" style="Box-sizing: border-Box;">name</span>] = methods;
}</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></ul>

所有的Javascript组件都是通过registerCallableModule注册的,比如触摸事件RCTEventEmitter.java对应的组件RCTEventEmitter.js代码路径是
node_modules\react-native\Libraries\BatchedBridge\BatchedBridgedModules\RCTEventEmitter.js

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-reserved" style="Box-sizing: border-Box;">var</span> BatchedBridge = <span class="hljs-built_in" style="color: rgb(102,0); Box-sizing: border-Box;">'BatchedBridge'</span>);
<span class="hljs-reserved" style="Box-sizing: border-Box;">var</span> ReactNativeEventEmitter = <span class="hljs-built_in" style="color: rgb(102,0); Box-sizing: border-Box;">'ReactNativeEventEmitter'</span>);

BatchedBridge.registerCallableModule(
  <span class="hljs-string" style="color: rgb(0,0); Box-sizing: border-Box;">'RCTEventEmitter'</span>,ReactNativeEventEmitter
);

<span class="hljs-regexp" style="color: rgb(0,0); Box-sizing: border-Box;">//</span> Completely locally implemented - <span class="hljs-literal" style="color: rgb(0,102); Box-sizing: border-Box;">no</span> <span class="hljs-reserved" style="Box-sizing: border-Box;">native</span> hooks.
<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">module</span>.<span class="hljs-built_in" style="color: rgb(102,102); Box-sizing: border-Box;">exports</span> = ReactNativeEventEmitter;
</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></ul>

BatchedBridge可以看成是MessageQueue,被注册的组件是ReactNativeEventEmitter代码位于node_modules\react-native\Libraries\ReactNative\ReactNativeEventEmitter.js

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">receiveEvent: <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">function</span>(tag: number,topLevelType: string,nativeEventParam: Object) {
    <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">...</span>
},receiveTouches: <span class="hljs-keyword" style="color: rgb(0,136); Box-sizing: border-Box;">function</span>(eventTopLevelType: string,touches:Array<Object>,changedIndices: Array<number>) {
   <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></ul>

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',102);">JavaScriptModule</span> {</span>

  <span class="hljs-keyword" style="color: rgb(0,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 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></ul>

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">moduleMethods[<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;">apply</span><span class="hljs-params" style="color: rgb(102,102); Box-sizing: border-Box;">(moduleMethods,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>

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

Box-sizing: border-Box; margin-top: 0px; margin-bottom: 1.1em; font-family: 'Source Code Pro',monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">ReactNativeEventEmitter<span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.receiveTouches</span><span class="hljs-preprocessor" style="color: rgb(68,68); Box-sizing: border-Box;">.apply</span>(moduleMethods,eventTopLevelType,touches,changedIndices)<span class="hljs-comment" style="color: rgb(136,238);"><li style="Box-sizing: border-Box; padding: 0px 5px;">1</li></ul>

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

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方式完成执行。

猜你在找的React相关文章