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

综合技术 2018-12-08 阅读原文

此文基于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 ,也就是注册过程的终点,调用过程也就结束了。

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

稀土掘金

责编内容by:稀土掘金阅读原文】。感谢您的支持!

您可能感兴趣的

Android Slices: Getting Started At Google I/O 2018 , Google announced a new way to present UI templates that can display rich, dynamic and inter...
Android 多线程之 HandlerThread 源码解析 想要了解 HandlerThread 的工作原理需要先对 Android 系统中以 Handler、Looper、MessageQueue 组成的异步消息处理机制有所了解,如果你还没有这方面的知识,可以先看我写的另一篇文章: ...
Android database insertion colloquium This question already has an answer here: Unfortunately MyApp has stopped. How can I solve this? 15 answers ...
带你一步步实现ReactNative的项目 构建环境 AndroidStudio2.0以上 Python nodejs3.xx python的下载地址 nodejs的下载地址 安装完成后 直接创建一个android的项...
深入理解Flutter Platform Channel 作者:闲鱼技术-皓黯 ​ 相信读者们在阅读了我们之前的文章后,对Platform Channel有了一定的理解和认识。但是由于篇幅有限,上文并未对Platform Channel的工作原理进行详细的讲解。Platform Channel...