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

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

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

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

原理簡述

RN通訊原理簡單地講就是,一方將其部分方法註冊成一個映射表,另外一方再在這個映射表中查找並調用相應的方法,而jsBridge擔當二者間橋接的角色。 git

源碼解析

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

註冊過程

先看官方教程中的例子:react-native

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。promise

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()
複製代碼

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

由上面的代碼可見NativeModule被添加到了ReactPackage中並被傳入了ReactInstanceManager中。寫過RN的人對ReactInstanceManager確定不會陌生,寫RN所在的Activity時必然會實例化ReactInstanceManager,RN在Android端幾乎全部的通訊邏輯都在它內部完成。app

接下來開始源碼的分析:異步

// 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,也就是註冊過程的終點,調用過程也就結束了。

調用過程的時序圖
至此,註冊過程和調用過程無縫銜接,一個完整的通訊過程已經躍然紙上。
相關文章
相關標籤/搜索