重新认识React Native和Android的通信原理

4,679 阅读5分钟

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

在我的上一篇文章《带你彻底看懂React Native和Android原生控件之间的映射关系》中,我已经完整地剖析了从RN组件到原生控件之间的映射关系,文中简单地提到了一些通信原理,本文我就来详细地讲解一下RN的通信原理。

PS:网上讲解RN通信原理的相关文章很多,但良莠不齐,有的代码泛滥,逻辑混乱,有的过于简单,结构不清,有的代码严重过时,已不是当前RN主流版本的源码。本文的目的就是想让读者对最新的RN通信原理有一个清晰的认识。Let's get started!

原理简述

RN通信原理简单地讲就是,一方将其部分方法注册成一个映射表,另一方再在这个映射表中查找并调用相应的方法,而jsBridge担当两者间桥接的角色。

源码解析

按原理简述中的顺序,我将本节分成两部分,一是从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,也就是注册过程的终点,调用过程也就结束了。

调用过程的时序图
至此,注册过程和调用过程无缝衔接,一个完整的通信过程已经跃然纸上。