重新認識React Native和Android的通訊原理

Marksss發表於2018-12-07

此文基於react natve的 September 2018 - revision 5 版本

在我的上一篇文章《帶你徹底看懂React Native和Android原生控制元件之間的對映關係》中,我已經完整地剖析了從RN元件到原生控制元件之間的對映關係,文中簡單地提到了一些通訊原理,本文我就來詳細地講解一下RN的通訊原理。

PS:網上講解RN通訊原理的相關文章很多,但良莠不齊,有的程式碼氾濫,邏輯混亂,有的過於簡單,結構不清,有的程式碼嚴重過時,已不是當前RN主流版本的原始碼。本文的目的就是想讓讀者對最新的RN通訊原理有一個清晰的認識。Let's get started!

原理簡述

RN通訊原理簡單地講就是,一方將其部分方法註冊成一個對映表,另一方再在這個對映表中查詢並呼叫相應的方法,而jsBridge擔當兩者間橋接的角色。

重新認識React Native和Android的通訊原理

原始碼解析

按原理簡述中的順序,我將本節分成兩部分,一是從native(java)出發的註冊過程,二是從js出發的呼叫過程,中間還穿插了部分jsBridge中的C++內容。

註冊過程

先看官方教程中的例子:

public class ToastModule extends ReactContextBaseJavaModule {
  public ToastModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  @Override
  public String getName() {
    return "ToastExample";
  }

  @ReactMethod
  public void show(String message, int duration) {
    // 可以被js呼叫的方法
    Toast.makeText(getReactApplicationContext(), message, duration).show();
  }
}
複製程式碼

這個例子我稍稍簡化了一下,功能很簡單,就是註冊了一個原生模組(NativeModule)供js呼叫後彈Toast。

public class CustomToastPackage implements ReactPackage {

  @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));// 被新增到ReactPackage中
    return modules;
  }

}
複製程式碼
// YourActivity.java
mReactInstanceManager = ReactInstanceManager.builder()
      .setApplication(getApplication())
      .setBundleAssetName("index.android.bundle")
      .setJSMainModulePath("index")
      .addPackage(new MainReactPackage())
      .addPackage(new CustomToastPackage()) // 傳入ReactInstanceManager中
      .setUseDeveloperSupport(BuildConfig.DEBUG)
      .setInitialLifecycleState(LifecycleState.RESUMED)
      .build()
複製程式碼

以上的程式碼都是官方教程中的程式碼。

由上面的程式碼可見NativeModule被新增到了ReactPackage中並被傳入了ReactInstanceManager中。寫過RN的人對ReactInstanceManager肯定不會陌生,寫RN所在的Activity時必然會例項化ReactInstanceManager,RN在Android端幾乎所有的通訊邏輯都在它內部完成。

接下來開始原始碼的分析:

// ReactInstanceManager.java

NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
   .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
   .setJSExecutor(jsExecutor)
   .setRegistry(nativeModuleRegistry)// NativeModuleRegistry 會在CatalystInstanceImpl中被呼叫
   .setJSBundleLoader(jsBundleLoader)
   .setNativeModuleCallExceptionHandler(exceptionHandler);

private NativeModuleRegistry processPackages(
    ReactApplicationContext reactContext,
    List<ReactPackage> packages,
    boolean checkAndUpdatePackageMembership) {
    NativeModuleRegistryBuilder nativeModuleRegistryBuilder = new NativeModuleRegistryBuilder(reactContext, this);
    ...
    for (ReactPackage reactPackage : packages) {
      // ReactPackage都傳入了NativeModuleRegistry
      processPackage(reactPackage, nativeModuleRegistryBuilder);
    }
    ...
    NativeModuleRegistry nativeModuleRegistry;
    nativeModuleRegistry = nativeModuleRegistryBuilder.build();
    ...
    return nativeModuleRegistry;
  }

  private void processPackage(
    ReactPackage reactPackage,
    NativeModuleRegistryBuilder nativeModuleRegistryBuilder) {
  ...
    nativeModuleRegistryBuilder.processPackage(reactPackage);
    ...
  }
複製程式碼

以上是ReactInstanceManager中的部分程式碼,可以看到,ReactPackage會被傳入NativeModuleRegistry中,NativeModuleRegistry內部就是一張對映表,所有註冊的NativeModule都會儲存在它內部供外部呼叫。而NativeModuleRegistry會在CatalystInstanceImpl中被呼叫。

看看CatalystInstanceImpl內部邏輯:

public class CatalystInstanceImpl implements CatalystInstance {
  static {
    // 初始化jni
    ReactBridge.staticInit();
  }

  private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec reactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry nativeModuleRegistry,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
    ...
    mNativeModuleRegistry = nativeModuleRegistry;
    // 將原生模組登錄檔傳給jsBridge
    initializeBridge(
      new BridgeCallback(this),
      jsExecutor,
      mReactQueueConfiguration.getJSQueueThread(),
      mNativeModulesQueueThread,
      mNativeModuleRegistry.getJavaModules(this),
      mNativeModuleRegistry.getCxxModules());
    ...
  }

  // C++中執行的方法
  private native void initializeBridge(
      ReactCallback callback,
      JavaScriptExecutor jsExecutor,
      MessageQueueThread jsQueue,
      MessageQueueThread moduleQueue,
      Collection<JavaModuleWrapper> javaModules,
      Collection<ModuleHolder> cxxModules);
  ...
}
複製程式碼

可見CatalystInstanceImpl已經和jsBridge(即ReactBridge)聯絡在一起了,它用C++函式initializeBridge將原生模組對映表傳到jsBridge中。

再看看CatalystInstanceImpl在C++中的實現:

// CatalystInstanceImpl.cpp
void CatalystInstanceImpl::initializeBridge(
    jni::alias_ref<ReactCallback::javaobject> callback,
    JavaScriptExecutorHolder* jseh,
    jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
    jni::alias_ref<JavaMessageQueueThread::javaobject> nativeModulesQueue,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
    jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {
  ...
  // 將原生模組對映表傳給ModuleRegistry.cpp
  moduleRegistry_ = std::make_shared<ModuleRegistry>(
      buildNativeModuleList(
         std::weak_ptr<Instance>(instance_),
         javaModules,
         cxxModules,
         moduleMessageQueue_));
  ...
}
複製程式碼

接下來是ModuleRegistry

// ModuleRegistry.cpp
ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules, ModuleNotFoundCallback callback)
    : modules_{std::move(modules)}, moduleNotFoundCallback_{callback} {}
...
void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
  ...
  // 原生模組登錄檔被呼叫處1
  modules_[moduleId]->invoke(methodId, std::move(params), callId);
}

MethodCallResult ModuleRegistry::callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params) {
  ...
  // 原生模組登錄檔被呼叫處2
  return modules_[moduleId]->callSerializableNativeHook(methodId, std::move(params));
}
複製程式碼

可見ModuleRegistry是原生模組對映表在C++中的位置,ModuleRegistry中暴露出了函式callNativeMethod供js呼叫。

註冊過程時序圖

原生模組的註冊過程就這樣分析完畢了。

呼叫過程

我們先看官方文件中的呼叫原生模組的方法:

import {NativeModules} from 'react-native';

NativeModules.ToastExample.show('Awesome', 1);
複製程式碼

這樣就呼叫了原生的Toast。它主要呼叫了NativeModules.jsToastExampleToastModulegetName方法返回的字串,而showToastModule中加了ReactMethod註解的方法。

接下來看看ReactMethod的原始碼:

function genModule(
  config: ?ModuleConfig,
  moduleID: number,
): ?{name: string, module?: Object} {
  const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
  ...
  // 獲取原生方法
  module[methodName] = genMethod(moduleID, methodID, methodType);
  ...
  return {name: moduleName, module};
}

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);
    };
  }
}

let NativeModules: {[moduleName: string]: Object} = {};
if (global.nativeModuleProxy) {
  NativeModules = global.nativeModuleProxy;
} else if (!global.nativeExtensions) {
  // 初始化jsBridge
  const bridgeConfig = global.__fbBatchedBridgeConfig;
  ...
  // 呼叫原生模組
  const info = genModule(config, moduleID);
  if (!info) {
    return;
  }

  if (info.module) {
    NativeModules[info.name] = info.module;
  }
  ...
}

module.exports = NativeModules;
複製程式碼

NativeModules通過genModule獲取到原生模組,又通過genMethod呼叫原生模組的方法。

呼叫原生方法分同步和非同步兩種方式。

以同步呼叫為例,它呼叫了global.nativeCallSyncHook,即JSIExecutor.cpp註冊的C++的方法:

// JSIExecutor.cpp

// 註冊了nativeCallSyncHook方法供js呼叫
runtime_->global().setProperty(
    *runtime_,
    "nativeCallSyncHook",
    Function::createFromHostFunction(
        *runtime_,
        PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
        1,
        [this](
            jsi::Runtime&,
            const jsi::Value&,
            const jsi::Value* args,
            size_t count) { return nativeCallSyncHook(args, count); }));

Value JSIExecutor::nativeCallSyncHook(const Value* args, size_t count) {
  ...
  // 呼叫委託,即ModuleRegistry,的callSerializableNativeHook函式
  MethodCallResult result = delegate_->callSerializableNativeHook(
      *this,
      static_cast<unsigned int>(args[0].getNumber()), 
      static_cast<unsigned int>(args[1].getNumber()), 
      dynamicFromValue(*runtime_, args[2])); 

  if (!result.hasValue()) {
    return Value::undefined();
  }
  return valueFromDynamic(*runtime_, result.value());
}
複製程式碼

JSIExecutor.cpp中是通過委託來實現的,最終呼叫的還是ModuleRegistry.cpp

// ModuleRegistry.cpp
MethodCallResult ModuleRegistry::callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params) {
  ...
  // 原生模組被呼叫處
  return modules_[moduleId]->callSerializableNativeHook(methodId, std::move(params));
}
複製程式碼

最後又到了ModuleRegistry,也就是註冊過程的終點,呼叫過程也就結束了。

呼叫過程的時序圖
至此,註冊過程和呼叫過程無縫銜接,一個完整的通訊過程已經躍然紙上。

相關文章