ReactNative原始碼篇:通訊機制

蘇策發表於2019-03-04

關於作者

郭孝星,程式設計師,吉他手,主要從事Android平臺基礎架構方面的工作,歡迎交流技術方面的問題,可以去我的Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。

更多文章:github.com/guoxiaoxing…

本篇系列文章主要分析ReactNative原始碼,分析ReactNative的啟動流程、渲染原理、通訊機制與執行緒模型等方面內容。

通訊,指的是RN中Java與JS的通訊,即JS中的JSX程式碼如何轉化成Java層真實的View與事件的,以及JavaFile層又是如何呼叫JS來找出它需要的View與
事件的。

在上一篇文章ReactNative原始碼篇:啟動流程中,我們知道RN應用在啟動的時候會建立JavaScriptModule登錄檔(JavaScriptModuleRegistry)與NativeModule登錄檔(NativeModuleRegistry),RN中Java層
與JS層的通訊就是通過這兩張表來完成的,我們來詳細看一看。

在正式開始分析通訊機制之前,我們先了解和本篇文章相關的一些重要概念。

在正式介紹通訊機制之前,我們先來了解一些核心的概念。

JavaScript Module登錄檔

說起JavaScript Module登錄檔,我們需要先理解3個類/介面:JavaScriptModule、JavaScriptModuleRegistration、JavaScriptModuleRegistry。

JavaScriptModule

JavaScriptModule:這是一個介面,JS Module都會繼承此介面,它表示在JS層會有一個相同名字的js檔案,該js檔案實現了該介面定義的方法,JavaScriptModuleRegistry會利用
動態代理將這個介面生成代理類,並通過C++傳遞給JS層,進而呼叫JS層的方法。複製程式碼

JavaScriptModuleRegistration

JavaScriptModuleRegistration用來描述JavaScriptModule的相關資訊,它利用反射獲取介面裡定義的Method。複製程式碼

JavaScriptModuleRegistry

JavaScriptModuleRegistry:JS Module登錄檔,內部維護了一個HashMap:HashMap<Class<? extends JavaScriptModule>, JavaScriptModuleRegistration> mModuleRegistrations,
JavaScriptModuleRegistry利用動態代理生成介面JavaScriptModule對應的代理類,再通過C++傳遞到JS層,從而呼叫JS層的方法。複製程式碼

Java Module登錄檔

要理解Java Module登錄檔,我們同樣也需要理解3個類/介面:NativeModule、ModuleHolder、NativeModuleRegistry。

NativeModule

NativeModule:是一個介面,實現了該介面則可以被JS層呼叫,我們在為JS層提供Java API時通常會繼承BaseJavaModule/ReactContextBaseJavaModule,這兩個類就
實現了NativeModule介面。複製程式碼

ModuleHolder

ModuleHolder:NativeModule的一個Holder類,可以實現NativeModule的懶載入。複製程式碼

NativeModuleRegistry

NativeModuleRegistry:Java Module登錄檔,內部持有Map:Map<Class<? extends NativeModule>, ModuleHolder> mModules,NativeModuleRegistry可以遍歷
並返回Java Module供呼叫者使用。複製程式碼

好,瞭解了這些重要概念,我們開始分析整個RN的通訊機制。

一 配置表的實現

1.1 配置表的定義

函式配置表定義在NativeModule.h中。

struct MethodDescriptor {
  std::string name;
  // type is one of js MessageQueue.MethodTypes
  std::string type;

  MethodDescriptor(std::string n, std::string t)
      : name(std::move(n))
      , type(std::move(t)) {}
};複製程式碼

method的定義

public class JavaModuleWrapper {
  public class MethodDescriptor {
    @DoNotStrip
    Method method;
    @DoNotStrip
    String signature;
    @DoNotStrip
    String name;
    @DoNotStrip
    String type;
  }
}複製程式碼

1.2 配置表的建立

在文章ReactNative原始碼篇:啟動流程中,我們可以知道在ReactInstanceManager執行createReactContext()時
建立了JavaScriptModuleRegistry與NativeModuleRegistry這兩張表,我們跟蹤一下它們倆的建立流程,以及建立完成後各自的去向。

通過上面分析我們可知JavaScriptModuleRegistry建立完成後由CatalystInstanceImpl例項所持有,供後續Java呼叫JS時使用。相當於Java層此時持有了一張JS模組表,它以後可以通過這個表去呼叫JS。
而NativeModuleRegistry在CatalystInstanceImpl.initializeBridge()方法中被傳入C++層,最終可以被JS層所拿到。

public class CatalystInstanceImpl(
  private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry registry,
      final JavaScriptModuleRegistry jsModuleRegistry,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {

    ...

    initializeBridge(
      new BridgeCallback(this),
      jsExecutor,
      mReactQueueConfiguration.getJSQueueThread(),
      mReactQueueConfiguration.getNativeModulesQueueThread(),
      mJavaRegistry.getJavaModules(this),//傳入的是Collection<JavaModuleWrapper> ,JavaModuleWrapper是NativeHolder的一個Wrapper類,它對應了C++層JavaModuleWrapper.cpp.
      mJavaRegistry.getCxxModules());//傳入的是Collection<ModuleHolder> ,ModuleHolder是NativeModule的一個Holder類,可以實現NativeModule的懶載入。
    FLog.w(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge");
    mMainExecutorToken = getMainExecutorToken();
  }
)複製程式碼

這裡我們注意一下傳入C++的兩個集合

mJavaRegistry.getJavaModules(this):傳入的是Collection<JavaModuleWrapper> ,JavaModuleWrapper是NativeHolder的一個Wrapper類,它對應了C++層JavaModuleWrapper.cpp,
JS在Java的時候最終會呼叫到這個類的inovke()方法上。

mJavaRegistry.getCxxModules()):傳入的是Collection<ModuleHolder> ,ModuleHolder是NativeModule的一個Holder類,可以實現NativeModule的懶載入。複製程式碼

我們繼續跟蹤這兩個集合到了C++層之後的去向。

CatalystInstanceImpl.cpp

void CatalystInstanceImpl::initializeBridge(
    jni::alias_ref<ReactCallback::javaobject> callback,
    // This executor is actually a factory holder.
    JavaScriptExecutorHolder* jseh,
    jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
    jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
    jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {


  instance_->initializeBridge(folly::make_unique<JInstanceCallback>(callback),
                              jseh->getExecutorFactory(),
                              folly::make_unique<JMessageQueueThread>(jsQueue),
                              folly::make_unique<JMessageQueueThread>(moduleQueue),
                              buildModuleRegistry(std::weak_ptr<Instance>(instance_),
                                                  javaModules, cxxModules));
}複製程式碼

這兩個集合在CatalystInstanceImpl::initializeBridge()被打包成ModuleRegistry傳入Instance.cpp.、,如下所示:

ModuleRegistryBuilder.cpp

std::unique_ptr<ModuleRegistry> buildModuleRegistry(
    std::weak_ptr<Instance> winstance,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
    jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {

  std::vector<std::unique_ptr<NativeModule>> modules;
  for (const auto& jm : *javaModules) {
    modules.emplace_back(folly::make_unique<JavaNativeModule>(winstance, jm));
  }
  for (const auto& cm : *cxxModules) {
    modules.emplace_back(
      folly::make_unique<CxxNativeModule>(winstance, cm->getName(), cm->getProvider()));
  }
  if (modules.empty()) {
    return nullptr;
  } else {
    return folly::make_unique<ModuleRegistry>(std::move(modules));
  }
}複製程式碼

打包好的ModuleRegistry通過Instance::initializeBridge()傳入到NativeToJsBridge.cpp中,並在NativeToJsBridge的構造方法中傳給JsToNativeBridge,以後JS如果呼叫Java就可以通過
ModuleRegistry來進行呼叫。

二 通訊橋的實現

關於整個RN的通訊機制,可以用一句話來概括:

JNI作為C++與Java的橋樑,JSC作為C++與JavaScript的橋樑,而C++最終連線了Java與JavaScript。

RN應用通訊橋結構圖如下所示:

ReactNative原始碼篇:通訊機制

2.1 關於通訊橋在Java層中的實現

從文章ReactNative原始碼篇:啟動流程我們得知在

在CatalystInstanceImpl構造方法中做了初始化通訊橋的操作:

public class CatalystInstanceImpl implements CatalystInstance {

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

    ...

    //Native方法,呼叫initializeBridge()方法,並建立BridgeCallback例項,初始化Bridge。
    initializeBridge(
      new BridgeCallback(this),
      jsExecutor,
      mReactQueueConfiguration.getJSQueueThread(),
      mReactQueueConfiguration.getNativeModulesQueueThread(),
      mJavaRegistry.getJavaModules(this),
      mJavaRegistry.getCxxModules());
    FLog.w(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge");
    mMainExecutorToken = getMainExecutorToken();
  }

  //在C++層初始化通訊橋ReactBridge
  private native void initializeBridge(
      ReactCallback callback,
      JavaScriptExecutor jsExecutor,
      MessageQueueThread jsQueue,
      MessageQueueThread moduleQueue,
      Collection<JavaModuleWrapper> javaModules,
      Collection<ModuleHolder> cxxModules);
}複製程式碼

傳入的幾個引數:

ReactCallback callback:CatalystInstanceImpl的靜態內部類ReactCallback,負責介面回撥。
JavaScriptExecutor jsExecutor:JS執行器,將JS的呼叫傳遞給C++層。
MessageQueueThread jsQueue.getJSQueueThread():JS執行緒,通過mReactQueueConfiguration.getJSQueueThread()獲得,mReactQueueConfiguration通過ReactQueueConfigurationSpec.createDefault()建立。
MessageQueueThread moduleQueue:Native執行緒,通過mReactQueueConfiguration.getNativeModulesQueueThread()獲得,mReactQueueConfiguration通過ReactQueueConfigurationSpec.createDefault()建立。
Collection<JavaModuleWrapper> javaModules:java modules,來源於mJavaRegistry.getJavaModules(this)。
Collection<ModuleHolder> cxxModules):c++ modules,來源於mJavaRegistry.getCxxModules()。複製程式碼

我們去跟蹤它的函式呼叫鏈:

CatalystInstanceImpl.initializeBridge() -> CatalystInstanceImpl::initializeBridge() -> Instance::initializeBridge()複製程式碼

最終可以發現,Instance::initializeBridge()實現了ReactBridge(C++層對應JsToNativeBridge與NativeToJsBridge)的初始化。

Instance.cpp

void Instance::initializeBridge(
    std::unique_ptr<InstanceCallback> callback,
    std::shared_ptr<JSExecutorFactory> jsef,
    std::shared_ptr<MessageQueueThread> jsQueue,
    std::unique_ptr<MessageQueueThread> nativeQueue,
    std::shared_ptr<ModuleRegistry> moduleRegistry) {
  callback_ = std::move(callback);

  if (!nativeQueue) {
    // TODO pass down a thread/queue from java, instead of creating our own.

    auto queue = folly::make_unique<CxxMessageQueue>();
    std::thread t(queue->getUnregisteredRunLoop());
    t.detach();
    nativeQueue = std::move(queue);
  }

  jsQueue->runOnQueueSync(
    [this, &jsef, moduleRegistry, jsQueue,
     nativeQueue=folly::makeMoveWrapper(std::move(nativeQueue))] () mutable {
      //初始化NativeToJsBridge物件
      nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>(
          jsef.get(), moduleRegistry, jsQueue, nativeQueue.move(), callback_);

      std::lock_guard<std::mutex> lock(m_syncMutex);
      m_syncReady = true;
      m_syncCV.notify_all();
    });

  CHECK(nativeToJsBridge_);
}複製程式碼

我們接著去C++層看看JsToNativeBridge與NativeToJsBridge的實現。

2.2 關於通訊橋在C++層的實現

在C++層的Executor.h檔案中定義了整個Java呼叫JS,JS呼叫Java的邏輯。

Executor.h檔案中定義了抽象類ExecutorDelegate,定義了執行Native Module的方法,它是JS呼叫Java的橋樑,JsToNativeBridge實現了該類的純虛擬函式(抽象方法),該抽象
類還持有JSExecutor(用來執行JS)的引用。

ReactNative原始碼篇:通訊機制

class ExecutorDelegate {
 public:
  virtual ~ExecutorDelegate() {}

  virtual void registerExecutor(std::unique_ptr<JSExecutor> executor,
                                std::shared_ptr<MessageQueueThread> queue) = 0;
  virtual std::unique_ptr<JSExecutor> unregisterExecutor(JSExecutor& executor) = 0;

  virtual std::shared_ptr<ModuleRegistry> getModuleRegistry() = 0;

  virtual void callNativeModules(
    JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) = 0;
  virtual MethodCallResult callSerializableNativeHook(
    JSExecutor& executor, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args) = 0;
};複製程式碼

Executor.h檔案中定義了抽象類JSExecutor,它定義了執行JS Module的方法,用來解釋執行JS,JSCExecutor實現了該類中的純虛擬函式(抽象方法),另一個類
NativeToBridge與JsToNativeBridge相對應,它是Java呼叫JS的橋樑,NativeToBridge持有JSCExecutor的引用,如果NativeToBridge需要執行JS時就會
去呼叫JSCExecutor。

ReactNative原始碼篇:通訊機制


class JSExecutor {
public:
  /**
   * Execute an application script bundle in the JS context.
   */
  virtual void loadApplicationScript(std::unique_ptr<const JSBigString> script,
                                     std::string sourceURL) = 0;

  /**
   * Add an application "unbundle" file
   */
  virtual void setJSModulesUnbundle(std::unique_ptr<JSModulesUnbundle> bundle) = 0;

  /**
   * Executes BatchedBridge.callFunctionReturnFlushedQueue with the module ID,
   * method ID and optional additional arguments in JS. The executor is responsible
   * for using Bridge->callNativeModules to invoke any necessary native modules methods.
   */
  virtual void callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) = 0;

  /**
   * Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID,
   * and optional additional arguments in JS and returns the next queue. The executor
   * is responsible for using Bridge->callNativeModules to invoke any necessary
   * native modules methods.
   */
  virtual void invokeCallback(const double callbackId, const folly::dynamic& arguments) = 0;

  virtual void setGlobalVariable(std::string propName,
                                 std::unique_ptr<const JSBigString> jsonValue) = 0;
  virtual void* getJavaScriptContext() {
    return nullptr;
  }
  virtual bool supportsProfiling() {
    return false;
  }
  virtual void startProfiler(const std::string &titleString) {}
  virtual void stopProfiler(const std::string &titleString, const std::string &filename) {}
  virtual void handleMemoryPressureUiHidden() {}
  virtual void handleMemoryPressureModerate() {}
  virtual void handleMemoryPressureCritical() {
    handleMemoryPressureModerate();
  }
  virtual void destroy() {}
  virtual ~JSExecutor() {}
};複製程式碼

2.3 關於通訊橋在JS層的實現

JS層Libraries/BatchedBridge包下面有3個JS檔案:BatchedBridge.js、MessageQueue.js、NativeModules.js,它們封裝了通訊橋在JS層的實現。

BatchedBridge.js

`use strict`;

const MessageQueue = require(`MessageQueue`);
const BatchedBridge = new MessageQueue();

// Wire up the batched bridge on the global object so that we can call into it.
// Ideally, this would be the inverse relationship. I.e. the native environment
// provides this global directly with its script embedded. Then this module
// would export it. A possible fix would be to trim the dependencies in
// MessageQueue to its minimal features and embed that in the native runtime.

Object.defineProperty(global, `__fbBatchedBridge`, {
  configurable: true,
  value: BatchedBridge,
});

module.exports = BatchedBridge;複製程式碼

可以看到在BatchedBridge中建立了MessageQueue物件。

MessageQueue.js

MessageQueue的實現有點長,我們來一步步看它的實現。先看MessageQueue的構造方法。

class MessageQueue {

  //
  _callableModules: {[key: string]: Object};
  //佇列,分別存放要呼叫的模組陣列、方法陣列、引數陣列與陣列大小
  _queue: [Array<number>, Array<number>, Array<any>, number];
  //回撥函式陣列,與_quueue一一對應,每個_queue中呼叫的方法,如果有回撥方法,那麼就在這個陣列對應的下標上。
  _callbacks: [];
  //回撥ID,自動增加。
  _callbackID: number;
  //呼叫函式ID,自動增加。
  _callID: number;
  _lastFlush: number;
  //訊息佇列事件迴圈開始時間
  _eventLoopStartTime: number;

  _debugInfo: Object;
  //Module Table,用於Native Module
  _remoteModuleTable: Object;
  //Method Table,用於Native Module
  _remoteMethodTable: Object;

  __spy: ?(data: SpyData) => void;

  constructor() {
    this._callableModules = {};
    this._queue = [[], [], [], 0];
    this._callbacks = [];
    this._callbackID = 0;
    this._callID = 0;
    this._lastFlush = 0;
    this._eventLoopStartTime = new Date().getTime();

    if (__DEV__) {
      this._debugInfo = {};
      this._remoteModuleTable = {};
      this._remoteMethodTable = {};
    }

    //繫結函式,也就是建立一個函式,使這個函式不論怎麼呼叫都有同樣的this值。
    (this:any).callFunctionReturnFlushedQueue = this.callFunctionReturnFlushedQueue.bind(this);
    (this:any).callFunctionReturnResultAndFlushedQueue = this.callFunctionReturnResultAndFlushedQueue.bind(this);
    (this:any).flushedQueue = this.flushedQueue.bind(this);
    (this:any).invokeCallbackAndReturnFlushedQueue = this.invokeCallbackAndReturnFlushedQueue.bind(this);
  }
}複製程式碼

NativeModules.jS

NativeModules描述了Module的結構,用於解析並生成Module。

Module的結構定義如下所示:

type ModuleConfig = [
  string, /* name */
  ?Object, /* constants */
  Array<string>, /* functions */
  Array<number>, /* promise method IDs */
  Array<number>, /* sync method IDs */
];複製程式碼

舉例

{
  "remoteModuleConfig": {
    "Logger": {
      "constants": { /* If we had exported constants... */ },
      "moduleID": 1,
      "methods": {
        "requestPermissions": {
          "type": "remote",
          "methodID": 1
        }
      }
    }
  }
}
{
  `ToastAndroid`: {
    moduleId: 0,
    methods: {
      `show`: {
        methodID: 0
      }
    },
    constants: {
      `SHORT`: `0`,
      `LONG`: `1`
    }
  },
  `moduleB`: {
    moduleId: 0,
    methods: {
      `method1`: {
        methodID: 0
      }
    },
    `key1`: `value1`,
    `key2`: `value2`
  }
}複製程式碼

接下來,我們分析一下JS程式碼呼叫Java程式碼的流程。

三 JS層呼叫Java層

RN應用通訊機制流程圖(JS->Java)如下所示:

ReactNative原始碼篇:通訊機制

舉例

同樣的,我們先來看一個JS程式碼呼叫Java程式碼的例子,我們寫一個ToastModule供JS程式碼呼叫。

1 編寫ToastModule繼承於ReactContextBaseJavaModule,該ToastModule實現具體的功能供JS程式碼呼叫。

public class ToastModule extends ReactContextBaseJavaModule {

  private static final String DURATION_SHORT_KEY = "SHORT";
  private static final String DURATION_LONG_KEY = "LONG";

  public ToastModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  //返回模組名字供JS程式碼呼叫
  @Override
  public String getName() {
    return "ToastAndroid";
  }

  //返回常量供JS程式碼呼叫
  @Override
  public Map<String, Object> getConstants() {
    final Map<String, Object> constants = new HashMap<>();
    constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
    constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
    return constants;
  }

  //暴露給JS程式碼的方法,加@ReactMethod註解,且該方法總是void。
  @ReactMethod
  public void show(String message, int duration) {
    Toast.makeText(getReactApplicationContext(), message, duration).show();
  }
}複製程式碼

2 編寫類繼承ReactPackage,註冊ToastModule。

public class AnExampleReactPackage implements ReactPackage {

  @Override
  public List<Class<? extends JavaScriptModule>> createJSModules() {
    return Collections.emptyList();
  }

  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Collections.emptyList();
  }

  @Override
  public List<NativeModule> createNativeModules(
                              ReactApplicationContext reactContext) {
    List<NativeModule> modules = new ArrayList<>();

    modules.add(new ToastModule(reactContext));

    return modules;
  }

}複製程式碼
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new AnExampleReactPackage()); // <-- Add this line with your package name.
}複製程式碼

3 為了方便JS程式碼呼叫,編寫一個JS Module來包裝Native Module的功能。


`use strict`;
/**
 * This exposes the native ToastAndroid module as a JS module. This has a
 * function `show` which takes the following parameters:
 *
 * 1. String message: A string with the text to toast
 * 2. int duration: The duration of the toast. May be ToastAndroid.SHORT or
 *    ToastAndroid.LONG
 */
import { NativeModules } from `react-native`;
module.exports = NativeModules.ToastAndroid;複製程式碼

4 最後我們就可以直接在JS程式碼中進行呼叫。


import ToastAndroid from `./ToastAndroid`;

ToastAndroid.show(`Awesome`, ToastAndroid.SHORT);複製程式碼

以上便是JS程式碼呼叫Java程式碼的全部流程,我們來具體分析它的實現細節。

從上面例子中,我們可以看出,呼叫的第一步就是從JS層的NativeModule登錄檔中拿到對應Java層的Java Module。但是JS在呼叫Java並不是通過介面來進行的,而是對應的
引數moduleID、methodID都push到一個messageQueue中,等待Java層的事件來驅動它,當Java層的事件傳遞過來以後,JS層把messageQUeue中的所有資料返回到Java層,
再通過登錄檔JavaRegistry去呼叫方法。

JS層呼叫Java層實現流程:

1 JS將需要呼叫的方法的moduleID、methodID與arguments一塊push到MessageQueue中,等待Java層的事件驅動。
2複製程式碼

我們們再呼叫Java程式碼時都通過NativeModules.xxxModule.xxxMethod()的方式來呼叫,我們先來看看NativeModules.js的實現。

3.1 NativeModules.xxxModule.xxxMethod()

當我們用NativeModules.xxxModule.xxxMethod()這種方式去呼叫時,JS就會通過JS層的NativeModules去查詢相對應的Java Module。

NativeModules.js


let NativeModules : {[moduleName: string]: Object} = {};

  //可以看到Native被賦值為global.nativeModuleProxy,nativeModuleProxy是一個全域性變數,顧名思義,它是NativeModules.js在C++層的代理。
  NativeModules = global.nativeModuleProxy;
} else {
  const bridgeConfig = global.__fbBatchedBridgeConfig;
  invariant(bridgeConfig, `__fbBatchedBridgeConfig is not set, cannot invoke native modules`);

  (bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => {
    // Initially this config will only contain the module name when running in JSC. The actual
    // configuration of the module will be lazily loaded.
    const info = genModule(config, moduleID);
    if (!info) {
      return;
    }

    if (info.module) {
      NativeModules[info.name] = info.module;
    }
    // If there`s no module config, define a lazy getter
    else {
      defineLazyObjectProperty(NativeModules, info.name, {
        get: () => loadModule(info.name, moduleID)
      });
    }
  });
}

module.exports = NativeModules;複製程式碼

nativeModuleProxy實質上是在啟動流程中,JSCExecutor::JSCExecutor()在建立時通過installGlobalProxy(m_context, “nativeModuleProxy”, exceptionWrapMethod<&JSCExecutor::getNativeModule>())
建立的,所以當JS呼叫NativeModules時,實際上在呼叫JSCExecutor::getNativeModule()方法,我們來看一看該方法的實現。

3.2 JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName)


JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName) {
  if (JSC_JSStringIsEqualToUTF8CString(m_context, propertyName, "name")) {
    return Value(m_context, String(m_context, "NativeModules"));
  }
  //m_nativeModules的型別是JSCNativeModules
  return m_nativeModules.getModule(m_context, propertyName);
}複製程式碼

該方法進一步呼叫JSCNativeModules.cpp的getModule()方法,我們來看看它的實現。

3.3 JSCNativeModules::getModule(JSContextRef context, JSStringRef jsName)

JSCNativeModules.cpp

JSValueRef JSCNativeModules::getModule(JSContextRef context, JSStringRef jsName) {
  if (!m_moduleRegistry) {
    return Value::makeUndefined(context);
  }

  std::string moduleName = String::ref(context, jsName).str();

  const auto it = m_objects.find(moduleName);
  if (it != m_objects.end()) {
    return static_cast<JSObjectRef>(it->second);
  }

  //呼叫該方法,通過JSC獲取全域性設定的JS屬性,然後通過JNI查詢Java層登錄檔,再觸發JS層方法。
  auto module = createModule(moduleName, context);
  if (!module.hasValue()) {
    return Value::makeUndefined(context);
  }

  // Protect since we`ll be holding on to this value, even though JS may not
  module->makeProtected();

  auto result = m_objects.emplace(std::move(moduleName), std::move(*module)).first;
  return static_cast<JSObjectRef>(result->second);
}

folly::Optional<Object> JSCNativeModules::createModule(const std::string& name, JSContextRef context) {
  if (!m_genNativeModuleJS) {

    auto global = Object::getGlobalObject(context);
    //NativeModules.js中將全域性變數__fbGenNativeModule指向了函式NativeModules::gen()方法,此處
    //便是去獲取JSC去呼叫這個方法
    m_genNativeModuleJS = global.getProperty("__fbGenNativeModule").asObject();
    m_genNativeModuleJS->makeProtected();

    // Initialize the module name list, otherwise getModuleConfig won`t work
    // TODO (pieterdb): fix this in ModuleRegistry
    m_moduleRegistry->moduleNames();
  }
  //獲取Native配置表
  auto result = m_moduleRegistry->getConfig(name);
  if (!result.hasValue()) {
    return nullptr;
  }

  //呼叫NativeModules::gen()方法
  Value moduleInfo = m_genNativeModuleJS->callAsFunction({
    Value::fromDynamic(context, result->config),
    Value::makeNumber(context, result->index)
  });
  CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null";

  return moduleInfo.asObject().getProperty("module").asObject();
}複製程式碼

上面的方法實現的功能分為2步:

1 呼叫ModuleRegistry::getConfig()獲取ModuleConfig。
2 通過JSC呼叫NativeModules.js的genModule()方法複製程式碼

ModuleRegistry是從Java層傳遞過來額JavaModuleRegistry,ModuleRegistry::getConfig()查詢Java Module的配置表,然後傳送給NativeModule.js生成對應的JS Module。

ModuleRegistry.cpp


folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name) {
  SystraceSection s("getConfig", "module", name);
  auto it = modulesByName_.find(name);
  if (it == modulesByName_.end()) {
    return nullptr;
  }

  CHECK(it->second < modules_.size());
  //modules_列表來源於CatalystInstanceImpl::initializeBridge()
  //module實質上是ModuleRegistryHolder.cpp的建構函式彙總將Java層傳遞過來的Module包裝成CxxNativeModule與JavaModule,這兩個都是NativeModule的子類。
  NativeModule* module = modules_[it->second].get();

  //string name, object constants, array methodNames準備建立一個動態物件。
  // string name, object constants, array methodNames (methodId is index), [array promiseMethodIds], [array syncMethodIds]
  folly::dynamic config = folly::dynamic::array(name);

  {
    SystraceSection s("getConstants");
    //通過反射呼叫Java層的JavaModuleWrapper.getContants()shi方法。
    config.push_back(module->getConstants());
  }

  {
    SystraceSection s("getMethods");
    //通過反射呼叫Java層的JavaModuleWrapper.getMethods()方法,也就是BaseJavaModule.getMethods(),該方法內部會呼叫
    //findMethos()方法查詢帶有ReactMoethod註解的方法。
    std::vector<MethodDescriptor> methods = module->getMethods();

    folly::dynamic methodNames = folly::dynamic::array;
    folly::dynamic promiseMethodIds = folly::dynamic::array;
    folly::dynamic syncMethodIds = folly::dynamic::array;

    for (auto& descriptor : methods) {
      // TODO: #10487027 compare tags instead of doing string comparison?
      methodNames.push_back(std::move(descriptor.name));
      if (descriptor.type == "promise") {
        promiseMethodIds.push_back(methodNames.size() - 1);
      } else if (descriptor.type == "sync") {
        syncMethodIds.push_back(methodNames.size() - 1);
      }
    }

    if (!methodNames.empty()) {
      config.push_back(std::move(methodNames));
      if (!promiseMethodIds.empty() || !syncMethodIds.empty()) {
        config.push_back(std::move(promiseMethodIds));
        if (!syncMethodIds.empty()) {
          config.push_back(std::move(syncMethodIds));
        }
      }
    }
  }

  if (config.size() == 1) {
    // no constants or methods
    return nullptr;
  } else {
    return ModuleConfig({it->second, config});
  }
}複製程式碼

獲取到相應ModuleConfig就會去呼叫NativeModules.js的genModule()生成JS將要呼叫對應的JS Module。

3.4 NativeModules.genModule(config: ?ModuleConfig, moduleID: number): ?{name: string, module?: Object}

NativeModules.js


// export this method as a global so we can call it from native
global.__fbGenNativeModule = genModule;

function genModule(config: ?ModuleConfig, moduleID: number): ?{name: string, module?: Object} {
  if (!config) {
    return null;
  }

  //通過JSC拿到C++中從Java層獲取的Java Module的登錄檔
  const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
  invariant(!moduleName.startsWith(`RCT`) && !moduleName.startsWith(`RK`),
    `Module name prefixes should`ve been stripped by the native side ` +
    `but wasn`t for ` + moduleName);

  if (!constants && !methods) {
    // Module contents will be filled in lazily later
    return { name: moduleName };
  }

  const module = {};
  //遍歷構建Module的屬性與方法
  methods && methods.forEach((methodName, methodID) => {
    const isPromise = promiseMethods && arrayContains(promiseMethods, methodID);
    const isSync = syncMethods && arrayContains(syncMethods, methodID);
    invariant(!isPromise || !isSync, `Cannot have a method that is both async and a sync hook`);
    const methodType = isPromise ? `promise` : isSync ? `sync` : `async`;
    //生成Module的函式方法
    module[methodName] = genMethod(moduleID, methodID, methodType);
  });
  Object.assign(module, constants);

  if (__DEV__) {
    BatchedBridge.createDebugLookup(moduleID, moduleName, methods);
  }

  return { name: moduleName, module };
}複製程式碼

該方法通過JSC拿到C++中從Java層獲取的Java Module的登錄檔,遍歷構建Module的屬性與方法,並呼叫genMethod()生成Module的函式方法,我們呼叫ToastAndroid.show(‘Awesome’, ToastAndroid.SHORT)時
實際上就是在呼叫genMethod()生成的方法,我們來看一看它的實現。

NativeModules.js

//該函式會根據函式型別的不同做不同的處理,但最終都會呼叫BatchedBridge.enqueueNativeCall()方法。
function genMethod(moduleID: number, methodID: number, type: MethodType) {
  let fn = null;
  if (type === `promise`) {
    fn = function(...args: Array<any>) {
      return new Promise((resolve, reject) => {
        BatchedBridge.enqueueNativeCall(moduleID, methodID, args,
          (data) => resolve(data),
          (errorData) => reject(createErrorFromErrorData(errorData)));
      });
    };
  } else if (type === `sync`) {
    fn = function(...args: Array<any>) {
      return global.nativeCallSyncHook(moduleID, methodID, args);
    };
  } else {
    fn = function(...args: Array<any>) {
      const lastArg = args.length > 0 ? args[args.length - 1] : null;
      const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
      const hasSuccessCallback = typeof lastArg === `function`;
      const hasErrorCallback = typeof secondLastArg === `function`;
      hasErrorCallback && invariant(
        hasSuccessCallback,
        `Cannot have a non-function arg after a function arg.`
      );
      const onSuccess = hasSuccessCallback ? lastArg : null;
      const onFail = hasErrorCallback ? secondLastArg : null;
      const callbackCount = hasSuccessCallback + hasErrorCallback;
      args = args.slice(0, args.length - callbackCount);
      BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess);
    };
  }
  fn.type = type;
  return fn;
}複製程式碼

該函式會根據函式型別的不同做不同的處理,但最終都會呼叫BatchedBridge.enqueueNativeCall()方法,我們來看看它的實現。

3.5 MessageQueue.enqueueNativeCall(moduleID: number, methodID: number, params: Array, onFail: ?Function, onSucc: ?Function)

MessageQueue.js


  enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
    if (onFail || onSucc) {
      if (__DEV__) {
        const callId = this._callbackID >> 1;
        this._debugInfo[callId] = [moduleID, methodID];
        if (callId > DEBUG_INFO_LIMIT) {
          delete this._debugInfo[callId - DEBUG_INFO_LIMIT];
        }
      }
      onFail && params.push(this._callbackID);
      /* $FlowFixMe(>=0.38.0 site=react_native_fb,react_native_oss) - Flow error
       * detected during the deployment of v0.38.0. To see the error, remove
       * this comment and run flow */
      this._callbacks[this._callbackID++] = onFail;
      onSucc && params.push(this._callbackID);
      /* $FlowFixMe(>=0.38.0 site=react_native_fb,react_native_oss) - Flow error
       * detected during the deployment of v0.38.0. To see the error, remove
       * this comment and run flow */
      this._callbacks[this._callbackID++] = onSucc;
    }

    if (__DEV__) {
      global.nativeTraceBeginAsyncFlow &&
        global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, `native`, this._callID);
    }
    this._callID++;

    //_queue是個佇列,用來存放呼叫的模組、方法與引數資訊。
    this._queue[MODULE_IDS].push(moduleID);
    this._queue[METHOD_IDS].push(methodID);

    if (__DEV__) {
      // Any params sent over the bridge should be encodable as JSON
      JSON.stringify(params);

      // The params object should not be mutated after being queued
      deepFreezeAndThrowOnMutationInDev((params:any));
    }
    this._queue[PARAMS].push(params);

    const now = new Date().getTime();
    //如果5ms內有多個方法呼叫則先待在佇列裡,防止過高頻率的方法呼叫,否則則呼叫JSCExecutor::nativeFlushQueueImmediate(size_t argumentCount, const JSValueRef arguments[]) 方法。
    if (global.nativeFlushQueueImmediate &&
        now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
      global.nativeFlushQueueImmediate(this._queue);
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
    }
    Systrace.counterEvent(`pending_js_to_native_queue`, this._queue[0].length);
    if (__DEV__ && this.__spy && isFinite(moduleID)) {
      this.__spy(
        { type: TO_NATIVE,
          module: this._remoteModuleTable[moduleID],
          method: this._remoteMethodTable[moduleID][methodID],
          args: params }
      );
    } else if (this.__spy) {
      this.__spy({type: TO_NATIVE, module: moduleID + ``, method: methodID, args: params});
    }
  }複製程式碼

流程走到這個方法的時候,JS層的呼叫流程就結束了,JS層主要通過JSC橋接獲取Java Module的登錄檔,並把它轉換為對應的JS Native Module(屬性、方法轉換),當JS
通過NativeModule.xxxMethod()準備呼叫Java方法時,會把xxxMethod()放進一個JS佇列,在佇列中:

1 如果如果5m(MIN_TIME_BETWEEN_FLUSHES_MS)以內,則繼續在佇列中等待Java層的事件來驅動它。
2 如果5m(MIN_TIME_BETWEEN_FLUSHES_MS)以內,則直接觸發的 JSCExecutor::nativeFlushQueueImmediate(size_t argumentCount, const JSValueRef arguments[]) 方法去呼叫對應的Java方法。複製程式碼

事實上,在佇列中,如果是Java方法呼叫JS方法,則會把之前佇列裡存的方法通過JSCExecutor::flush()進行處理。

我們再來看看JSCExecutor::nativeFlushQueueImmediate(size_t argumentCount, const JSValueRef arguments[]) 的實現。

3.6 JSValueRef JSCExecutor::nativeFlushQueueImmediate(size_t argumentCount, const JSValueRef arguments[])

JSCExecutor.cpp

JSValueRef JSCExecutor::nativeFlushQueueImmediate(
    size_t argumentCount,
    const JSValueRef arguments[]) {
  if (argumentCount != 1) {
    throw std::invalid_argument("Got wrong number of args");
  }

  flushQueueImmediate(Value(m_context, arguments[0]));
  return Value::makeUndefined(m_context);
}

void JSCExecutor::flushQueueImmediate(Value&& queue) {
  auto queueStr = queue.toJSONString();
  //呼叫JsToNativeBridge.cpp的callNativeModules(),傳入的isEndOfBatch=false
  m_delegate->callNativeModules(*this, folly::parseJson(queueStr), false);
}複製程式碼

可以看出nativeFlushQueueImmediate()會進一步呼叫flushQueueImmediate()方法,m_delegate的型別是ExecutorDelegate,事實上它呼叫的是ExecutorDelegate的子類
JsToNativeBridge.cpp的callNativeModules()方法,我們回想一下上面我們分析Java程式碼呼叫JS程式碼第7步的實現,它也同樣走到了這個方法,只是傳入的isEndOfBatch=true。

3.7 JsToNativeBridge::callNativeModules()

JsToNativeBridge.cpp

  void callNativeModules()(
      JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {

    CHECK(m_registry || calls.empty()) <<
      "native module calls cannot be completed with no native modules";
    ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
    //在Native佇列中執行
    m_nativeQueue->runOnQueue([this, token, calls=std::move(calls), isEndOfBatch] () mutable {

      //遍歷來自JS佇列的呼叫方法列表

      // An exception anywhere in here stops processing of the batch.  This
      // was the behavior of the Android bridge, and since exception handling
      // terminates the whole bridge, there`s not much point in continuing.
      for (auto& call : react::parseMethodCalls(std::move(calls))) {
        //m_registry的型別是ModuleRegistry,
        m_registry->callNativeMethod(
          token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
      }
      if (isEndOfBatch) {
        //標記回撥Java狀態
        m_callback->onBatchComplete();
        m_callback->decrementPendingJSCalls();
      }
    });
  }複製程式碼

在該方法中取出JS佇列中的JS呼叫Java的所有方法,並通過ModuleRegistry::callNativeMethod()方法去遍歷呼叫,我們來看看這個方法的實現。

3.8 ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId)

void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
                                      folly::dynamic&& params, int callId) {
  if (moduleId >= modules_.size()) {
    throw std::runtime_error(
      folly::to<std::string>("moduleId ", moduleId,
                             " out of range [0..", modules_.size(), ")"));
  }

#ifdef WITH_FBSYSTRACE
  if (callId != -1) {
    fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
  }
#endif

  //modules_是建立ModuleRegistryHolder時根據Java層ModuleRegistryHolder建立的C++ NativeModule
  //moduleId為模組在當前列表的索引值
  modules_[moduleId]->invoke(token, methodId, std::move(params));
}複製程式碼

modules_的型別是std::vector> modules_,NativeModule是C++層針對Java Module的一種包裝,NativeModule的子類是JavaNativeModule,
我們去看看它的呼叫方法invoke().

3.9 JavaNativeModule::invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params)

抽象類NativeModule定義的純虛擬函式(抽象方法)

NativeModule.cpp

class NativeModule {
 public:
  virtual ~NativeModule() {}
  virtual std::string getName() = 0;
  virtual std::vector<MethodDescriptor> getMethods() = 0;
  virtual folly::dynamic getConstants() = 0;
  virtual bool supportsWebWorkers() = 0;
  // TODO mhorowitz: do we need initialize()/onCatalystInstanceDestroy() in C++
  // or only Java?
  virtual void invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) = 0;
  virtual MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& args) = 0;
};

}
}複製程式碼

NativeModule有2個子類,它的類圖如下所示:

ReactNative原始碼篇:通訊機制

class JavaNativeModule : public NativeModule {

  void JavaNativeModule::invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) {

  //wrapper_-的型別是JavaModuleWrapper.cpp,對映Java層的JavaModuleWrapper.java,在ModuleRegistryHolder.cpp構造方法中由Java傳入的Java Module被C++包裝成
  //JavaModuleWrapper物件。通過反射呼叫Java層的JavaModuleWrapper.java的invoke()方法,同時把mothodId和引數傳過去。
  static auto invokeMethod =
    wrapper_->getClass()->getMethod<void(JExecutorToken::javaobject, jint, ReadableNativeArray::javaobject)>("invoke");
  invokeMethod(wrapper_, JExecutorToken::extractJavaPartFromToken(token).get(), static_cast<jint>(reactMethodId),
               ReadableNativeArray::newObjectCxxArgs(std::move(params)).get());
}
}複製程式碼

該方法呼叫通過反射呼叫Java層的JavaModuleWrapper.java的invoke()方法,同時把mothodId和引數傳過去。我們來看看JavaModuleWrapper.java的invoke()方法的實現。

3.10 JavaModuleWrapper.invoke(ExecutorToken token, int methodId, ReadableNativeArray parameters)


//NativeModules是一個介面,我們知道要實現供JS呼叫的Java API我們需要編寫類繼承BaseJavaModule/ReactContextBaseJavaModule,BaseJavaModule/ReactContextBaseJavaModule就
//實現了NativeModule介面,
 private final ArrayList<NativeModule.NativeMethod> mMethods;

@DoNotStrip
public class JavaModuleWrapper {
  @DoNotStrip
  public void invoke(ExecutorToken token, int methodId, ReadableNativeArray parameters) {
    if (mMethods == null || methodId >= mMethods.size()) {
      return;
    }
    //mMethods為所有繼承BaseJavaModule類的BaseJavaModule.JavaMethod物件,也就是被ReactMethod註解標記的方法。
    mMethods.get(methodId).invoke(mJSInstance, token, parameters);
  }
}複製程式碼

JavaModuleWrapper對應C++層的NativeModule,該類針對Java BaseJavaModule進行了包裝,是的C++層可以更加方便的通過JNI呼叫Java Module。

四 Java層呼叫JS層

RN應用通訊機制流程圖(Java->JS)如下所示:

ReactNative原始碼篇:通訊機制

舉例

在文章ReactNative原始碼篇:啟動流程中,我們在ReactInstanceManager.onAttachedToReactInstance()方法中呼叫APPRegistry.jS的runApplication()來
啟動RN應用,這就是一個典型的Java層呼叫JS層的例子,我們來具體分析一下這個例子的實現方式。

1 首先定義了介面AppRegistry,該介面繼承於JavaScriptModule,如下所示:

/**
 * JS module interface - main entry point for launching React application for a given key.
 */
public interface AppRegistry extends JavaScriptModule {

  void runApplication(String appKey, WritableMap appParameters);
  void unmountApplicationComponentAtRootTag(int rootNodeTag);
  void startHeadlessTask(int taskId, String taskKey, WritableMap data);
}複製程式碼

2 然後在CoreModulesPackage.createJSModules()將它新增到JavaScriptModule列表中,這個列表最終會被新增到JavaScriptModuleRegistry中。

class CoreModulesPackage extends LazyReactPackage implements ReactPackageLogger {

  @Override
  public List<Class<? extends JavaScriptModule>> createJSModules() {
    List<Class<? extends JavaScriptModule>> jsModules = new ArrayList<>(Arrays.asList(
        DeviceEventManagerModule.RCTDeviceEventEmitter.class,
        JSTimersExecution.class,
        RCTEventEmitter.class,
        RCTNativeAppEventEmitter.class,
        AppRegistry.class,
        com.facebook.react.bridge.Systrace.class,
        HMRClient.class));

    if (ReactBuildConfig.DEBUG) {
      jsModules.add(DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
      jsModules.add(JSCHeapCapture.HeapCapture.class);
      jsModules.add(JSCSamplingProfiler.SamplingProfiler.class);
    }

    return jsModules;
  }
}複製程式碼

3 通過Java層呼叫AppRegistry.js的runApplication()方法,如下所示:

//啟動流程入口:由Java層呼叫啟動
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);複製程式碼

Java層呼叫JS層實現流程:

Java層

1 把要實現的功能編寫成介面並繼承JavaScriptModule,並交由ReactPackage管理,最終會在RN初始化的時候新增到JavaScriptModuleRegistry登錄檔中。
2 通過ReactContext或者CatalystInstanceImpl獲取JavaScriptModule,它們最終會通過JavaScriptModuleRegistry.getJavaScriptModule()獲取對應的JavaScriptModule。
3 JavaScriptModuleRegistry通過動態代理生成對應的JavaScriptModule,然後通過invoke()呼叫相應的JS方法,該方法會進一步去呼叫CatalystInstanceImpl.callJSFunction()
該方法會呼叫native方法CatalystInstanceImpl.jniCallJSFunction()方法將相關引數傳遞到C++層,至此,整個流程便轉入C++層。複製程式碼

C++層

4 CatalystInstanceImpl在C++層對應的是類CatalystInstanceImpl.cpp。CatalystInstanceImpl.cpp是RN針對Android平臺的包裝類,具體功能由Instance.cpp來完成,
即Instance.cpp的callJSFunction()方法。
5 Instance.cpp的callJSFunction()方法按照呼叫鏈:Instance.callJSFunction()->NativeToJsBridge.callFunction()->JSCExecutor.callFunction()最終將
功能交由JSCExecutor.cpp的callFunction()方法來完成。
6 JSCExecutor.cpp的callFunction()方法通過Webkit JSC呼叫JS層的MessageQueue.js裡的callFunctionReturnFlushedQueue()方法,自此整個流程轉入JavaScript層。複製程式碼

JS層

7 MessageQueue.js裡的callFunctionReturnFlushedQueue()方法,該方法按照呼叫鏈:MessageQueue.callFunctionReturnFlushedQueue()->MessageQueue.__callFunction()
在JS層裡的JavaScriptModule登錄檔裡產找對應的JavaScriptModule及方法。複製程式碼

我們來分析上述程式碼的呼叫方式。

可以看出AppRegistry繼承於JavaScriptModule,AppRegistry作為核心邏輯之一被新增到CoreModulesPackage中,我們知道在ReactInstanceManager.createReactContext()方法
中,CoreModulesPackage作為ReactPackage被新增進了JavaScriptModuleRegistry中,JavaScriptModuleRegistry被CatalystInstanceImpl來管理。

所以才有了Java層呼叫JS層程式碼的通用格式:

CatalystInstanceImpl.getJSModule(xxx.class).method(params, params, ...);複製程式碼

當然,如果使我們呼叫自己的JS Module,我們是用ReactContext.getJSModule(),因為ReactContext持有CatalystInstanceImpl的例項,CatalystInstanceImpl並不直接對外公開。

Java層程式碼呼叫JS層程式碼,需要將JavaScriptModule註冊到JavaScriptModuleRegistry中,然後通過動態代理獲取方法的各種引數,再將引數通過引數通過C++層傳遞到JS層從而完成呼叫,我們
先來看看CatalystInstanceImpl是如何拿到JavaScriptModule的。

CatalystInstanceImpl.getJSModule()呼叫JavaScriptModuleRegistry.getJavaScriptModule()去查詢JavaScriptModule。

ReactNative原始碼篇:通訊機制

4.1 JavaScriptModuleRegistry.getJavaScriptModule(CatalystInstance instance, ExecutorToken executorToken, Class moduleInterface)

它的實現如下所示:

public class JavaScriptModuleRegistry {

  public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
    CatalystInstance instance,
    ExecutorToken executorToken,
    Class<T> moduleInterface) {

    //如果JavaScriptModule載入一次,就儲存在快取中,第二次載入時直接從快取中取。
    HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> instancesForContext =
        mModuleInstances.get(executorToken);
    if (instancesForContext == null) {
      instancesForContext = new HashMap<>();
      mModuleInstances.put(executorToken, instancesForContext);
    }
    JavaScriptModule module = instancesForContext.get(moduleInterface);
    if (module != null) {
      return (T) module;
    }

    //利用動態代理獲取JavaScriptModule物件

    //JavaScriptModuleRegistration是對JavaScriptModule的包裝,檢查實現JavaScriptModule介面的類是否存在過載,因為JS不支援過載。
    JavaScriptModuleRegistration registration =
        Assertions.assertNotNull(
            mModuleRegistrations.get(moduleInterface),
            "JS module " + moduleInterface.getSimpleName() + " hasn`t been registered!");
    JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
        moduleInterface.getClassLoader(),
        new Class[]{moduleInterface},
        new JavaScriptModuleInvocationHandler(executorToken, instance, registration));
    instancesForContext.put(moduleInterface, interfaceProxy);
    return (T) interfaceProxy;
  }

  private static class JavaScriptModuleInvocationHandler implements InvocationHandler {

    private final WeakReference<ExecutorToken> mExecutorToken;
    private final CatalystInstance mCatalystInstance;
    private final JavaScriptModuleRegistration mModuleRegistration;

    public JavaScriptModuleInvocationHandler(
        ExecutorToken executorToken,
        CatalystInstance catalystInstance,
        JavaScriptModuleRegistration moduleRegistration) {
      mExecutorToken = new WeakReference<>(executorToken);
      mCatalystInstance = catalystInstance;
      mModuleRegistration = moduleRegistration;
    }

    @Override
    public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
      ExecutorToken executorToken = mExecutorToken.get();
      if (executorToken == null) {
        FLog.w(ReactConstants.TAG, "Dropping JS call, ExecutorToken went away...");
        return null;
      }
      NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray();
      //呼叫CatalystInstanceImpl.callFunction()方法。
      mCatalystInstance.callFunction()方法。(
        executorToken,
        mModuleRegistration.getName(),
        method.getName(),
        jsArgs
      );
      return null;
    }
  }
}複製程式碼

可以看出,在JavaScriptModuleRegistry通過動態代理的方式獲取JavaScriptModule,對Java動態代理不熟悉的同學,這裡我們先簡單回憶一下Java動態代理相關內容。

Java動態代理

Java動態代理主要涉及兩個類:

java.lang.reflect.Proxy:用來生成代理類。
java.lang.reflect.InvocationHandler:呼叫處理器,我們需要自己定義一個類來指定動態生成的代理類需要完成的具體內容。

Proxy的主要方法:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)//建立代理物件  

ClassLoader loader:類載入器,指定使用哪個類載入器將代理類載入到JVM的方法區。
Class<?>[] interfaces:代理類需要實現的介面。
InvocationHandler handler:呼叫處理器例項,指定代理類具體要做什麼。

實現Java動態代理需要以下3步:

1 定義一個委託類和公共介面。
2 定義呼叫處理器類實現InvocationHandler介面,指定代理類具體要完成的任務。
3 生成代理物件

一個代理物件對應一個委託類對應一個呼叫處理器類複製程式碼

JavaScriptModuleInvocationHandler.invoke()方法獲取了moduleID,methodID,並去呼叫CatalystInstanceImpl.callFunction();

4.2 CatalystInstanceImpl.callFunction(ExecutorToken executorToken, final String module, final String method, final NativeArray arguments)


public class CatalystInstanceImpl{

  @Override
  public void callFunction(
      ExecutorToken executorToken,
      final String module,
      final String method,
      final NativeArray arguments) {
    if (mDestroyed) {
      FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
      return;
    }
    if (!mAcceptCalls) {
      // Most of the time the instance is initialized and we don`t need to acquire the lock
      synchronized (mJSCallsPendingInitLock) {
        if (!mAcceptCalls) {
          mJSCallsPendingInit.add(new PendingJSCall(executorToken, module, method, arguments));
          return;
        }
      }
    }

    jniCallJSFunction(executorToken, module, method, arguments);
  }

  private native void jniCallJSCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments);
}複製程式碼

方法走到這裡,實現邏輯已經由Java層轉到C++層,我們去C++層看看具體的實現。

ReactNative原始碼篇:通訊機制

CatalystInstanceImpl.java在C++層有個對應的類CatalystInstanceImpl.cpp。

4.3 CatalystInstanceImpl.jniCallJSFunction(JExecutorToken token, std::string module, std::string method, NativeArray arguments)

CatalystInstanceImpl.cpp

void CatalystInstanceImpl::jniCallJSFunction(

    JExecutorToken* token, std::string module, std::string method, NativeArray* arguments) {
  // We want to share the C++ code, and on iOS, modules pass module/method
  // names as strings all the way through to JS, and there`s no way to do
  // string -> id mapping on the objc side.  So on Android, we convert the
  // number to a string, here which gets passed as-is to JS.  There, they they
  // used as ids if isFinite(), which handles this case, and looked up as
  // strings otherwise.  Eventually, we`ll probably want to modify the stack
  // from the JS proxy through here to use strings, too.
  instance_->callJSFunction(token->getExecutorToken(nullptr),
                            std::move(module),
                            std::move(method),
                            arguments->consume());
}複製程式碼

可以發現CatalystInstanceImpl.cpp的jniCallJSFunction()方法又會去呼叫Instance.cpp裡的callJSFunction()方法,其實CatalystInstanceImpl.cpp只是RN針對
Android平臺適配的封裝,主要做了寫引數型別轉換,本質上它對應了ReactCommon包裡的Instance.cpp,真正的實現在Instance.cpp中完成。我們來看一看Instance.cpp中的實現。

4.4 Instance.callJSFunction(ExecutorToken token, std::string&& module, std::string&& method, folly::dynamic&& params)

Instance.cpp

void Instance::callJSFunction(ExecutorToken token, std::string&& module, std::string&& method,
                              folly::dynamic&& params) {
  callback_->incrementPendingJSCalls();
  nativeToJsBridge_->callFunction(token, std::move(module), std::move(method), std::move(params));
}複製程式碼

Instance.cpp的callJSFunction()進一步去呼叫NativeToJsBridge.cpp的callFunction()方法,我們來看看它的實現。

4.5 NativeToJsBridge.callFunction(ExecutorToken executorToken, std::string&& module, std::string&& method, folly::dynamic&& arguments)

NativeToJsBridge.cpp

void NativeToJsBridge::callFunction(
    ExecutorToken executorToken,
    std::string&& module,
    std::string&& method,
    folly::dynamic&& arguments) {

  int systraceCookie = -1;
  #ifdef WITH_FBSYSTRACE
  systraceCookie = m_systraceCookie++;
  std::string tracingName = fbsystrace_is_tracing(TRACE_TAG_REACT_CXX_BRIDGE) ?
    folly::to<std::string>("JSCall__", module, `_`, method) : std::string();
  SystraceSection s(tracingName.c_str());
  FbSystraceAsyncFlow::begin(
      TRACE_TAG_REACT_CXX_BRIDGE,
      tracingName.c_str(),
      systraceCookie);
  #else
  std::string tracingName;
  #endif

  runOnExecutorQueue(executorToken, [module = std::move(module), method = std::move(method), arguments = std::move(arguments), tracingName = std::move(tracingName), systraceCookie] (JSExecutor* executor) {
    #ifdef WITH_FBSYSTRACE
    FbSystraceAsyncFlow::end(
        TRACE_TAG_REACT_CXX_BRIDGE,
        tracingName.c_str(),
        systraceCookie);
    SystraceSection s(tracingName.c_str());
    #endif

    //呼叫JSCExecutor.cppd的callFunction()
    // This is safe because we are running on the executor`s thread: it won`t
    // destruct until after it`s been unregistered (which we check above) and
    // that will happen on this thread
    executor->callFunction(module, method, arguments);
  });
}複製程式碼

NativeToJsBridge.cpp的callFunction()進一步去呼叫JSCExecutor.cppd的callFunction()方法,我們來看看它的實現。

4.6 JSCExecutor.callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments)

JSCExecutor.cpp

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
  SystraceSection s("JSCExecutor::callFunction");
  // This weird pattern is because Value is not default constructible.
  // The lambda is inlined, so there`s no overhead.

  auto result = [&] {
    try {
      // See flush()
      CHECK(m_callFunctionReturnFlushedQueueJS)
        << "Attempting to call native methods without loading BatchedBridge.js";
      return m_callFunctionReturnFlushedQueueJS->callAsFunction({
        Value(m_context, String::createExpectingAscii(m_context, moduleId)),
        Value(m_context, String::createExpectingAscii(m_context, methodId)),
        Value::fromDynamic(m_context, std::move(arguments))
      });
    } catch (...) {
      std::throw_with_nested(
        std::runtime_error("Error calling " + moduleId + "." + methodId));
    }
  }();

  //將呼叫結果返回給Java層
  callNativeModules(std::move(result));
}複製程式碼

可以看出,該函式進一步呼叫m_callFunctionReturnFlushedQueueJS->callAsFunction(),我們先來解釋下m_callFunctionReturnFlushedQueueJS這個變數的由來,它在JSCExecutor::bindBridge()裡
初始化,本質上就是通過Webkit JSC拿到JS層程式碼相關物件和方法引用,m_callFunctionReturnFlushedQueueJS就是MessageQueue.js裡的callFunctionReturnFlushedQueue()方法的引用。

void JSCExecutor::bindBridge() throw(JSException) {

  ...

 m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject();

  ...
}複製程式碼

ReactNative原始碼篇:通訊機制

4.7 MessageQueue.callFunctionReturnFlushedQueue(module: string, method: string, args: Array)

MessageQueue.callFunctionReturnFlushedQueue()方法的實現如下所示:

MessageQueue.js

  callFunctionReturnFlushedQueue(module: string, method: string, args: Array<any>) {
    guard(() => {
      this.__callFunction(module, method, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }

  __callFunction(module: string, method: string, args: Array<any>) {
    this._lastFlush = new Date().getTime();
    this._eventLoopStartTime = this._lastFlush;
    Systrace.beginEvent(`${module}.${method}()`);
    if (this.__spy) {
      this.__spy({ type: TO_JS, module, method, args});
    }

    //從JS層的JavaScriptModule登錄檔中查詢到AppRegistry.js
    const moduleMethods = this._callableModules[module];
    invariant(
      !!moduleMethods,
      `Module %s is not a registered callable module (calling %s)`,
      module, method
    );
    invariant(
      !!moduleMethods[method],
      `Method %s does not exist on module %s`,
      method, module
    );
    //取到Java層呼叫的JS層方法,例如:AppRegistry.js的runApplication()方法
    const result = moduleMethods[method].apply(moduleMethods, args);
    Systrace.endEvent();
    return result;
  }複製程式碼

從JS層的JavaScriptModule登錄檔中查詢到AppRegistry.js,最終呼叫AppRegistry.js的runApplication()方法。

好,以上就是Java層程式碼呼叫JS層程式碼的全部流程,我們再來總結一下:

Java層

1 把要實現的功能編寫成介面並繼承JavaScriptModule,並交由ReactPackage管理,最終會在RN初始化的時候新增到JavaScriptModuleRegistry登錄檔中。
2 通過ReactContext或者CatalystInstanceImpl獲取JavaScriptModule,它們最終會通過JavaScriptModuleRegistry.getJavaScriptModule()獲取對應的JavaScriptModule。
3 JavaScriptModuleRegistry通過動態代理生成對應的JavaScriptModule,然後通過invoke()呼叫相應的JS方法,該方法會進一步去呼叫CatalystInstanceImpl.callJSFunction()
該方法會呼叫native方法CatalystInstanceImpl.jniCallJSFunction()方法將相關引數傳遞到C++層,至此,整個流程便轉入C++層。複製程式碼

C++層

4 CatalystInstanceImpl在C++層對應的是類CatalystInstanceImpl.cpp。CatalystInstanceImpl.cpp是RN針對Android平臺的包裝類,具體功能由Instance.cpp來完成,
即Instance.cpp的callJSFunction()方法。
5 Instance.cpp的callJSFunction()方法按照呼叫鏈:Instance.callJSFunction()->NativeToJsBridge.callFunction()->JSCExecutor.callFunction()最終將
功能交由JSCExecutor.cpp的callFunction()方法來完成。
6 JSCExecutor.cpp的callFunction()方法通過Webkit JSC呼叫JS層的MessageQueue.js裡的callFunctionReturnFlushedQueue()方法,自此整個流程轉入JavaScript層。複製程式碼

JavaScript層

7 MessageQueue.js裡的callFunctionReturnFlushedQueue()方法,該方法按照呼叫鏈:MessageQueue.callFunctionReturnFlushedQueue()->MessageQueue.__callFunction()
在JS層裡的JavaScriptModule登錄檔裡產找對應的JavaScriptModule及方法。複製程式碼

相關文章