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

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


一、通讯模型php

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

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

因此由此,咱们大概可以猜到React-Native的通讯原理,画一张图来简单地描写叙述一下:node


二、Java层实现react

以前说过。React-Native的重要设计思想是组件化,为了便于维护扩展和减小耦合,React-Native并无为了实现某一详细的通讯编写代码(比方上篇博文所讲的触摸事件传递),而是设计了一套标准用于组件化。c++

这套标准是向开发人员开放的,开发人员可以自行编写需要的组件用来在NativeJavascript之间通讯,虽然这并不是推荐的选择。json

2.1 JavaScriptModule组件react-native

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

/**
 * Interface denoting that a class is the interface to a module with the same name in JS. Calling
 * functions on this interface will result in corresponding methods in JS being called.
 *
 * When extending JavaScriptModule and registering it with a CatalystInstance, all public methods
 * are assumed to be implemented on a JS module with the same name as this class. 
 *
 * NB: JavaScriptModule does not allow method name overloading because JS does not allow method name
 * overloading.
 */
@DoNotStrip
public interface JavaScriptModule {
}

阅读一下凝视,主要有三点信息:
一、所有组件必须继承JavaScriptModule,并注冊在CatalystInstance中。
二、所有public方法与Javascript层保持同名并由后者详细实现。


三、由于Javascript不支持重载。因此Java中也不能有重载。

细致的读者会发现,凝视里有两个单词很是关键。extendingimplementedJavaextendJavascriptimplement,也就是说Java层仅仅作接口定义。而实现由Javascript完毕。因此。搜索一下JavaScriptModule的子类会发现它们都是接口。没有详细实现类。

有点晦涩但事实上很是好理解,举个简单的样例。去餐馆吃饭,顾客(Java)仅仅要定义(interface)好想吃什么菜,而后和餐馆(Bridge)说。餐馆会通知本身的厨师(Javascript)把详细的菜作好。这就是一个简单的通讯过程Java->Bridge->Javascript

2.2 JavaScriptModule组件的注冊

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

public interface RCTEventEmitter extends JavaScriptModule {
  public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap event);
  public void receiveTouches(
      String eventName,
      WritableArray touches,
      WritableArray changedIndices);
}

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

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

class ReactInstanceManagerImpl extends ReactInstanceManager {

  ...

  private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
     ...
     try {
      CoreModulesPackage coreModulesPackage =
          new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
      processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
    } finally {
      ...
    }

    for (ReactPackage reactPackage : mPackages) {
      ...
      try {
        processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
      } finally {
        ...
      }
    }

  }

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

    ...

    for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()){
      jsModulesBuilder.add(jsModuleClass);
    }
  }
  ...

}

可以看到CoreModulesPackage和开发人员扩展本身定义的mPackages都是经过processPackage方法里加入到JavaScriptModulesConfig里注冊的。

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

public class JavaScriptModulesConfig {

  private final List<JavaScriptModuleRegistration> mModules;

  private JavaScriptModulesConfig(List<JavaScriptModuleRegistration> modules) {
    mModules = modules;
  }

  /*package*/ List<JavaScriptModuleRegistration> getModuleDefinitions() {
    return mModules;
  }

  ...
}

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

回看一下以前的ReactInstanceManagerImpl类代码,createReactContext中另外一段。例如如下:

private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader)
     ...

     JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();
     JavaScriptModulesConfig javaScriptModulesConfig;
    try {
      javaScriptModulesConfig = jsModulesBuilder.build();
    } finally {
      ...
    }
     ...
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
        .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
        .setJSExecutor(jsExecutor)
        .setRegistry(nativeModuleRegistry)
        .setJSModulesConfig(javaScriptModulesConfig)
        .setJSBundleLoader(jsBundleLoader)
        .setNativeModuleCallExceptionHandler(exceptionHandler);

    ...

    CatalystInstance catalystInstance;
    try {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      ...
    }

    ...
}

看来终于javaScriptModulesConfig是用来构建CatalystInstance的,正如凝视所讲。果真没有骗我。

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

public class CatalystInstanceImpl implements CatalystInstance { ... private CatalystInstanceImpl( final ReactQueueConfigurationSpec ReactQueueConfigurationSpec, final JavaScriptExecutor jsExecutor, final NativeModuleRegistry registry, final JavaScriptModulesConfig jsModulesConfig, final JSBundleLoader jsBundleLoader, NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) { ... mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig); ... try { mBridge = mReactQueueConfiguration.getJSQueueThread().callOnQueue( new Callable<ReactBridge>() { @Override public ReactBridge call() throws Exception { ... try { return initializeBridge(jsExecutor, jsModulesConfig); } finally { ... } } }).get(); } catch (Exception t) { throw new RuntimeException("Failed to initialize bridge", t); } } private ReactBridge initializeBridge( JavaScriptExecutor jsExecutor, JavaScriptModulesConfig jsModulesConfig) { ... ReactBridge bridge; try { bridge = new ReactBridge( jsExecutor, new NativeModulesReactCallback(), mReactQueueConfiguration.getNativeModulesQueueThread()); } finally { ... } ... try { bridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); bridge.setGlobalVariable( "__RCTProfileIsProfiling", Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ?

"true" : "false"); } finally { ... } return bridge; } ... }

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

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

private String buildModulesConfigJSONProperty(
      NativeModuleRegistry nativeModuleRegistry,
      JavaScriptModulesConfig jsModulesConfig) {
    JsonFactory jsonFactory = new JsonFactory();
    StringWriter writer = new StringWriter();
    try {
      JsonGenerator jg = jsonFactory.createGenerator(writer);
      jg.writeStartObject();
      jg.writeFieldName("remoteModuleConfig");
      nativeModuleRegistry.writeModuleDescriptions(jg);
      jg.writeFieldName("localModulesConfig");
      jsModulesConfig.writeModuleDescriptions(jg);
      jg.writeEndObject();
      jg.close();
    } catch (IOException ioe) {
      throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
    }
    return writer.getBuffer().toString();
  }

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

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

public class JavaScriptModulesConfig {

   ...

     void writeModuleDescriptions(JsonGenerator jg) throws IOException {
    jg.writeStartObject();
    for (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("moduleID", registration.getModuleId());
    jg.writeObjectFieldStart("methods");
    for (Method method : registration.getMethods()) {
      jg.writeObjectFieldStart(method.getName());
      jg.writeObjectField("methodID", registration.getMethodId(method));
      jg.writeEndObject();
    }
    jg.writeEndObject();
  }

   ...
}

writeModuleDescriptions这种方法干了什么事呢?遍历所有JavaScriptModulepublic方法,而后经过methodID标识做为key存入JSON生成器中,用来终于生成JSON字符串。

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

2.3 JavaScriptModule组件的调用

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

/*package*/ class JavaScriptModuleRegistry { private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances; public JavaScriptModuleRegistry(CatalystInstanceImpl instance, JavaScriptModulesConfig config) { mModuleInstances = new HashMap<>(); for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) { Class<? extends JavaScriptModule> moduleInterface = registration.getModuleInterface(); JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance( moduleInterface.getClassLoader(), new Class[]{moduleInterface}, new JavaScriptModuleInvocationHandler(instance, registration)); mModuleInstances.put(moduleInterface, interfaceProxy); } } ... }

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

这里使用动态代理除了建立JavaScriptModule组件的实例化类外。另外一个关键的数据,即JavaScriptModule所有的方法调用都会被invoke拦截,这样就可以统一处理所有从Java端向Javascript端的通讯请求。

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

private static class JavaScriptModuleInvocationHandler implements InvocationHandler {

    private final CatalystInstanceImpl mCatalystInstance;
    private final JavaScriptModuleRegistration mModuleRegistration;

    public JavaScriptModuleInvocationHandler(
        CatalystInstanceImpl catalystInstance,
        JavaScriptModuleRegistration moduleRegistration) {
      mCatalystInstance = catalystInstance;
      mModuleRegistration = moduleRegistration;
    }

    @Override
    public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      String tracingName = mModuleRegistration.getTracingName(method);
      mCatalystInstance.callFunction(
          mModuleRegistration.getModuleId(),
          mModuleRegistration.getMethodId(method),
          Arguments.fromJavaArgs(args),
          tracingName);
      return null;
    }
  }

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

public class CatalystInstanceImpl implements CatalystInstance {

   ...

    private final ReactBridge mBridge;

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

    ...

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

            ...

            try {  
             Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId,arguments);
            } finally {
               ...
            }
          }
        });
  }

   ...

}

分析这里终于豁然开朗了,原来所有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中的实现了。


三、Bridge层实现

通讯模型图中要调用WebKit的实现,少不了Bridge这个桥梁。由于Java是不能直接调用WebKit,但是假设Java经过JNIJNI再调用WebKit不就OK了么?

继续前面说的ReactBridgesetGlobalVariablecallFunction方法。

public class ReactBridge extends Countable {
  static {
    SoLoader.loadLibrary(REACT_NATIVE_LIB);
  }

   public native void callFunction(int moduleId, int methodId, NativeArray arguments);

  public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
}

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

废话很少说。来看看c++setGlobalVariablecallFunction的实现吧。

namespace bridge {

    static void setGlobalVariable(JNIEnv* env, jobject obj, jstring propName, jstring jsonValue) {
    auto bridge = extractRefPtr<CountableBridge>(env, obj);
    bridge->setGlobalVariable(fromJString(env, propName), fromJString(env, jsonValue));
  }

   static void callFunction(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject jExecutorToken, jint moduleId, jint methodId,
                         NativeArray::jhybridobject args, jstring tracingName) {
   auto bridge = extractRefPtr<CountableBridge>(env, obj);
   auto arguments = cthis(wrap_alias(args));
   try {
      bridge->callFunction(
      cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),
      folly::to<std::string>(moduleId),
      folly::to<std::string>(methodId),
      std::move(arguments->array),
      fromJString(env, tracingName)
    );
   } catch (...) {
     translatePendingCppExceptionToJavaException();
    }
  }

}
struct CountableBridge : Bridge, Countable {
  using Bridge::Bridge;
};

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

void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
  runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
    executor->setGlobalVariable(propName, jsonValue);
  });
}
void Bridge::callFunction(
    ExecutorToken executorToken,
    const std::string& moduleId,
    const std::string& methodId,
    const folly::dynamic& arguments,
    const std::string& tracingName) {
  #ifdef WITH_FBSYSTRACE
  int systraceCookie = m_systraceCookie++;
  ...
  #endif

  #ifdef WITH_FBSYSTRACE
  runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName, systraceCookie] (JSExecutor* executor) {
  ...
  #else
  runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName] (JSExecutor* executor) {
  #endif
    executor->callFunction(moduleId, methodId, arguments);
  });
}

两个方法调用的过程几乎相同,都是塞进runOnExecutorQueue运行队列里面等待调用,回调都是走的JSExecutor。因此仍是要看JSExecutor了。

这边提一下,Bridge类构造的时候会初始化ExecutorQueue,经过JSCExecutorFactory建立JSExecutor,而JSExecutor的真正实现类是JSCExecutor

经过jni/react/JSCExecutor.h头文件可以验证这一点,此处略过不细讲。

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

void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
  auto globalObject = JSContextGetGlobalObject(m_context);
  String jsPropertyName(propName.c_str());

  String jsValueJSON(jsonValue.c_str());
  auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);

  JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
}

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

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
  // TODO: Make this a first class function instead of evaling. #9317773
  std::vector<folly::dynamic> call{
    moduleId,
    methodId,
    std::move(arguments),
  };
  std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
  m_bridge->callNativeModules(*this, calls, true);
}
static std::string executeJSCallWithJSC(
    JSGlobalContextRef ctx,
    const std::string& methodName,
    const std::vector<folly::dynamic>& arguments) {

  ...

  // Evaluate script with JSC
  folly::dynamic jsonArgs(arguments.begin(), arguments.end());
  auto js = folly::to<folly::fbstring>(
      "__fbBatchedBridge.", methodName, ".apply(null, ",
      folly::toJson(jsonArgs), ")");
  auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
  return Value(ctx, result).toJSONString();
}

callFunction里面运行的是executeJSCallWithJSC。而executeJSCallWithJSC里面将methodNamejsonArgs拼接成了一个applyJavascript运行语句。最后调用jni/react/JSCHelpers.cppevaluateScript的来运行这个语句,完毕BridgeJavascript的调用。(JSCHelpersWebKit的一些API作了封装,暂不深究,仅仅要知道它负责终于调用WebKit便可了)

固然JSCExecutor::callFunction方法最后另外一个Bridge.cpp类的callNativeModules反向通讯,意图是将Javascript语句运行结果通知回Native,这个过程留在之后的文章中慢慢研究,先行略过。

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


四、Javascript层实现

Javascript的通讯,实质上是Weikit运行Javascript语句,调用流程是Bridge->WebKit->JavascriptWebKit中提供了不少与Javascript通讯的API。比方evaluateScriptJSContextGetGlobalObjectJSObjectSetProperty等。

4.一、JavaScriptModule映射表

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

void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
  auto globalObject = JSContextGetGlobalObject(m_context);
  String jsPropertyName(propName.c_str());

  String jsValueJSON(jsonValue.c_str());
  auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);

  JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
}

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

bridge.setGlobalVariable( "__fbBatchedBridgeConfig", buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)); bridge.setGlobalVariable( "__RCTProfileIsProfiling", Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ?

"true" : "false");

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

global.__fbBatchedBridgeConfig = jsonValue;

或者

Object.defineProperty(global, '__fbBatchedBridgeConfig', { value: jsonValue});

做用事实上是同样的。

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

咱们来看node_modules\react-native\Libraries\BatchedBridge\BatchedBridge.js的代码。

const MessageQueue = require('MessageQueue');

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

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

哪儿冒出来的呢?

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

private String buildModulesConfigJSONProperty(
      NativeModuleRegistry nativeModuleRegistry,
      JavaScriptModulesConfig jsModulesConfig) {
    JsonFactory jsonFactory = new JsonFactory();
    StringWriter writer = new StringWriter();
    try {
      JsonGenerator jg = jsonFactory.createGenerator(writer);
      jg.writeStartObject();
      jg.writeFieldName("remoteModuleConfig");
      nativeModuleRegistry.writeModuleDescriptions(jg);
      jg.writeFieldName("localModulesConfig");
      jsModulesConfig.writeModuleDescriptions(jg);
      jg.writeEndObject();
      jg.close();
    } catch (IOException ioe) {
      throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
    }
    return writer.getBuffer().toString();
  }

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

再来看刚刚的BatchedBridge.js

const MessageQueue = require('MessageQueue');

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

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

class MessageQueue {

  constructor(remoteModules, localModules) {
    ...

    localModules && this._genLookupTables(
      this._genModulesConfig(localModules),this._moduleTable, this._methodTable
    );
}

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

_genLookupTables(modulesConfig, moduleTable, methodTable) {
    modulesConfig.forEach((config, moduleID) => {
      this._genLookup(config, moduleID, moduleTable, methodTable);
    });
  }

  _genLookup(config, moduleID, moduleTable, methodTable) {
    if (!config) {
      return;
    }

    let moduleName, methods;
    if (moduleHasConstants(config)) {
      [moduleName, , methods] = config;
    } else {
      [moduleName, methods] = config;
    }

    moduleTable[moduleID] = moduleName;
    methodTable[moduleID] = Object.assign({}, methods);
  }

哈哈,和推測的同样,生成了两张映射表,存放在了MessageQueue类里面。

4.二、callFunction的调用

回想一下JSCExecutor.cpp中的终于callFunction调用过程。

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
  // TODO: Make this a first class function instead of evaling. #9317773
  std::vector<folly::dynamic> call{
    moduleId,
    methodId,
    std::move(arguments),
  };
  std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
  m_bridge->callNativeModules(*this, calls, true);
}
static std::string executeJSCallWithJSC(
    JSGlobalContextRef ctx,
    const std::string& methodName,
    const std::vector<folly::dynamic>& arguments) {

  ...

  // Evaluate script with JSC
  folly::dynamic jsonArgs(arguments.begin(), arguments.end());
  auto js = folly::to<folly::fbstring>(
      "__fbBatchedBridge.", methodName, ".apply(null, ",
      folly::toJson(jsonArgs), ")");
  auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
  return Value(ctx, result).toJSONString();
}

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

__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null, jsonArgs);

首先,在Javascript的运行环境下,当前做用域条件下__fbBatchedBridge能被直接调用。必须是Global全局对象的属性。

4.1__fbBatchedBridgeConfig不一样的是,jni并无手动设置__fbBatchedBridge为全局对象的属性,那惟一的可能就是在Javascript里面经过Object.defineProperty来设置了。

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

const MessageQueue = require('MessageQueue');

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

...

Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });

module.exports = BatchedBridge;

这段代码等价于

global.__fbBatchedBridge = new MessageQueue(...args);

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

MessageQueue.callFunctionReturnFlushedQueue.apply(null, jsonArgs);

Arguments參数再详细一下。就变成了:

MessageQueue.callFunctionReturnFlushedQueue.apply(null, module, method, args);

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

callFunctionReturnFlushedQueue(module, method, args) {
    guard(() => { this.__callFunction(module, method, args); this.__callImmediates(); }); return this.flushedQueue(); } var guard = (fn) => {
  try {
    fn();
  } catch (error) {
    ErrorUtils.reportFatalError(error);
  }
};

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

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

callFunctionReturnFlushedQueue(module, method, args) { this.__callFunction(module, method, args); this.__callImmediates(); return this.flushedQueue(); }

this指的是当前MessageQueue 对象。因此找到MessageQueue.__callFunction方法:

__callFunction(module, method, args) {
    ...
    if (isFinite(module)) {
      method = this._methodTable[module][method];
      module = this._moduleTable[module];
    }
    ...
    var moduleMethods = this._callableModules[module];
    invariant(
      !!moduleMethods,
      'Module %s is not a registered callable module.',
      module
    );

    moduleMethods[method].apply(moduleMethods, args);
    ...
  }

这里就是经过moduleIDmethodID来查询两张映射Table了。获取到了详细的moduleNamemethodName,接着确定要作调用Javascript相应组件了。

假设在餐馆吃饭的样例中,场景应该是这样的:顾客点完菜。餐馆服务人员也已经把菜名通知到厨师了,厨师该作菜了吧。等等。当中还漏了一步,就是这个厨师会不会作这道菜。假设让川菜师傅去作粤菜确定是不行的,因此厨师的能力里还应该有一张技能清单,作菜前厨师需要推断下本身的技能单子里面有没有这道菜。

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

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

registerCallableModule(name, methods) {
    this._callableModules[name] = methods;
}

所有的Javascript组件都是经过registerCallableModule来注冊的,比方触摸事件RCTEventEmitter.java相应的组件RCTEventEmitter.js,代码路径是
node_modules\react-native\Libraries\BatchedBridge\BatchedBridgedModules\RCTEventEmitter.js

var BatchedBridge = require('BatchedBridge');
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');

BatchedBridge.registerCallableModule(
  'RCTEventEmitter',
  ReactNativeEventEmitter
);

// Completely locally implemented - no native hooks.
module.exports = ReactNativeEventEmitter;

BatchedBridge可以当作是MessageQueue。被注冊的组件是ReactNativeEventEmitter。代码位于node_modules\react-native\Libraries\ReactNative\ReactNativeEventEmitter.js

receiveEvent: function(tag: number, topLevelType: string, nativeEventParam: Object) {
    ...
},

receiveTouches: function(eventTopLevelType: string,  touches:Array<Object>, changedIndices: Array<number>) {
   ...
}

细致对比RCTEventEmitter .java比較,是否是全然一致,哈哈

public interface RCTEventEmitter extends JavaScriptModule {

  public void receiveEvent(int targetTag, String eventName,  WritableMap event);
  public void receiveTouches(String eventName, WritableArray touches, WritableArray changedIndices);

}

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

moduleMethods[method].apply(moduleMethods, args)

假设以RCTEventEmitterreceiveTouches方法调用为例。详细语句应该是这样:

ReactNativeEventEmitter.receiveTouches.apply(moduleMethods, eventTopLevelType, touches, changedIndices);

结束!通讯完毕。大功告成!

五、总结

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

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

一、Java层

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

CatalystInstanceImpl类内部的ReactBridge详细实现与Javascript的通讯请求,它是调用Bridge Jni 的出口。

ReactBridge被建立的时候会将JavaScriptModule信息表预先发给Javascript层用来生成映射表。

二、C++层

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

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

三、Javascript层

BatchedBridgeJavascript层的调用入口,而其又是MessageQueue的假装者。MessageQueue预先注冊了所有可以接收通讯请求的组件_callableModules 。同一时候也保存着来自JavaJavaScriptModule的两张映射表。

接收通讯请求时,先经过映射表确认详细请求信息,再确认Javascript组件可否够被调用,最后经过apply方式完毕运行。

整个通讯过程流程例如如下图:


本博客不按期持续更新,欢迎关注和交流:

http://blog.csdn.net/megatronkings

相关文章
相关标签/搜索