React Native 代码阅读(五):ReactPackage 与 Native Module 详解

2019-02-18

前言

在创建 NativeModule 或 ViewManager 时,我们都得将它们添加到某个 ReactPackage 下。这是怎样的一种设计思想呢?

我们先来看 ReactPackage 的注释:

ReactPackage 自身是一个接口,它的作用是向 Catalyst 框架提供额外的功能特性,主要包括:

  1. 注册新的原生模块
  2. 注册新的 JS 模块,能从原生模块中访问
  3. 注册自定义原生视图(View Manager),以及自定义事件类型
  4. 注册原生包资源,供 JS 调用

在 ReactPackage 中实现了功能 1 和 3.

从本节开始,我们由浅入深地分析 React Native 是如何使用 ReactPackage 的。

分析入口

首先我们从第一篇中讲到的,在 Android 中继承 React Native,MainActivity 的写法:

public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler {

    private ReactInstanceManager mReactInstanceManager;
    private ReactRootView mReactRootView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mReactRootView = new ReactRootView(this);

        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setBundleAssetName("index.android.bundle")
                .setJSMainModuleName("index.android")
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();

        mReactRootView.startReactApplication(mReactInstanceManager, "Basic", null);

        setContentView(mReactRootView);
    }

其中:

  • 这一步我们应当很熟悉了。如果不熟悉的话,请学习系列的第一篇文章
  • 我们关注其中的 ReactInstanceManager.builder 这一段
  • 在 .addPackage(new MainReactPackage()) 中我们添加了 MainReactPackage 这个包
  • MainReactPackage 是 React Native 必须添加的 Package

ReactInstanceManager 内置包

ReactInstanceManager 通过 Builder 模式构建,我们对它已经很熟悉了,因此跳过 ReactInstanceManagerBuilder 直接来看 ReactInstanceManager。

在 ReactInstanceManager 中,ReactPackage 存放在成员变量中:

  • List<ReactPackage> mPackages;

在 ReactInstanceManager 的构造函数中,ReactInstanceManager 也会自己向 mPackages 中添加几个包:

  • CoreModulesPackage
  • DebugCorePackage(可选)

加上我们外面添加的 MainReactPackage 包,这样至少就有两个包会被一定添加的。

NativeModuleRegistry

介绍

前面都是向 mPackages 添加 Package,添加的包什么时候被使用呢?

答案是在 ReactInstanceManager 的 createReactContext 方法中:

NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);

其中:

  • NativeModuleRegistry 是什么呢?它的作用是存储 Native Modules
  • 实际方法都在 processPackages 中

processPackages

因此接下来我们来看 processPackages 方法。

首先方法签名:

private NativeModuleRegistry processPackages(
    ReactApplicationContext reactContext,
    List<ReactPackage> packages,             // 我们的 mPackages
    boolean checkAndUpdatePackageMembership) // false

NativeModuleRegistry 也是通过 Builder 方法构建的(NativeModuleRegistryBuilder):

NativeModuleRegistryBuilder nativeModuleRegistryBuilder = new NativeModuleRegistryBuilder(
    reactContext,
    this,
    mLazyNativeModulesEnabled);

之后会对 packages 进行遍历,对每个 package,在内部会调用另一个同名方法:

processPackage(reactPackage, nativeModuleRegistryBuilder);

我们进入这个方法来看,是如何处理这个包的。在方法内部,核心调用方法是:

nativeModuleRegistryBuilder.processPackage(reactPackage);

我们看出,关键的逻辑都写在了 NativeModuleRegistryBuilder 中。因此进入 NativeModuleRegistryBuilder 的 processPackage 方法:

NativeModuleRegistryBuilder 对 Package 有两种加载方式,一种是懒加载,一种是直接加载。在 processPackage 中通过一个 if else 结构来分别处理。

在这里,我们来看直接加载的方式:

List<NativeModule> nativeModules;
if (reactPackage instanceof ReactInstancePackage) {
    ReactInstancePackage reactInstancePackage = (ReactInstancePackage) reactPackage;
    nativeModules = reactInstancePackage.createNativeModules(
        mReactApplicationContext,
        mReactInstanceManager);
} else {
    nativeModules = reactPackage.createNativeModules(mReactApplicationContext);
}
for (NativeModule nativeModule : nativeModules) {
    addNativeModule(nativeModule);
}

其中:

  • 主要分为两步
    • 调用 createNativeModules 创建 Native Module 实例
    • 调用 addNativeModule

在 addNativeModule 中:

mModules 成员用于存放 Native Modules,它的类型是:

Map<Class<? extends NativeModule>, ModuleHolder> mModules = new HashMap<>();

首先 addNativeModule 会进行一个去重操作,之后:

namesToType.put(name, type);
ModuleHolder moduleHolder = new ModuleHolder(nativeModule);
mModules.put(type, moduleHolder);

这里的 ModuleHolder 是什么呢?它主要是用于懒加载功能的。在这里我们不做详细介绍,等以后有机会在 Native Module 的懒加载机制中再来介绍这一块。现在只要知道 ModuleHolder 中含有 Native Module 实例即可。

最后将 nativeModule 按照类型 put 进 mModules 中。

再次回到 createReactContext

再次回到 createReactContext,如今我们已经有了 NativeModuleRegistry 实例:

NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);

CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
    .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
    .setJSExecutor(jsExecutor)
    .setRegistry(nativeModuleRegistry)
    .setJSBundleLoader(jsBundleLoader)
    .setNativeModuleCallExceptionHandler(exceptionHandler);

其中:

  • nativeModuleRegistry 传入了 CatalystInstanceImpl.Builder 的 Builder() 中。

CatalystInstanceImpl

介绍

CatalystInstanceImpl.Builder 很简单,我们直接看 CatalystInstanceImpl。

nativeModuleRegistry 通过构造函数传入,保存在成员中:

private final NativeModuleRegistry mNativeModuleRegistry;

initializeBridge

在构造函数中,将 mNativeModuleRegistry 传入 initializeBridge 方法中:

initializeBridge(
    new BridgeCallback(this),
    jsExecutor,
    mReactQueueConfiguration.getJSQueueThread(),
    mNativeModulesQueueThread,
    mNativeModuleRegistry.getJavaModules(this),
    mNativeModuleRegistry.getCxxModules());

initializeBridge 的签名如下:

private native void initializeBridge(
    ReactCallback callback,
    JavaScriptExecutor jsExecutor,
    MessageQueueThread jsQueue,
    MessageQueueThread moduleQueue,
    Collection<JavaModuleWrapper> javaModules,
    Collection<ModuleHolder> cxxModules);

它是一个 JNI 方法,因此我们转入 C++ 层的 ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp 的 CatalystInstanceImpl::initializeBridge。

它的方法签名如下:

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> nativeModulesQueue,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
    jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {

我们可以对照一下,同一个对象,在 Java 侧与 C++ 的对应关系。

这个方法首先对 nativeModulesQueue 建立一份在 C++ 侧的引用:

moduleMessageQueue_ = std::make_shared<JMessageQueueThread>(nativeModulesQueue);

之后调用:

moduleRegistry_ = std::make_shared<ModuleRegistry>(
    buildNativeModuleList(
        std::weak_ptr<Instance>(instance_),
        javaModules,
        cxxModules,
        moduleMessageQueue_));

其中:

  • NativeModule(定义在 ReactCommon/cxxreact/NativeModule.h) 是 C++ 侧存放 Native Modules 的类.
  • 基于 NativeModule 有派生类:
    • JavaNativeModule:ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.h
    • CxxNativeModule:ReactCommon/cxxreact/CxxNativeModule.h

下面来看 buildNativeModuleList:

std::vector<std::unique_ptr<NativeModule>> buildNativeModuleList(
    std::weak_ptr<Instance> winstance,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
    jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules,
    std::shared_ptr<MessageQueueThread> moduleMessageQueue) {
  std::vector<std::unique_ptr<NativeModule>> modules;
  if (javaModules) {
    for (const auto& jm : *javaModules) {
      modules.emplace_back(folly::make_unique<JavaNativeModule>(
                     winstance, jm, moduleMessageQueue));
    }
  }
  if (cxxModules) {
    for (const auto& cm : *cxxModules) {
      modules.emplace_back(folly::make_unique<CxxNativeModule>(
                             winstance, cm->getName(), cm->getProvider(), moduleMessageQueue));
    }
  }
  return modules;
}

其中:

  • modules 是一个 Vector,用于存放 NativeModule
  • CxxModule 和 JavaModule 虽然是分开从 Java 层传过来的,但是在这里他们只是初始化方式不同,最终有合并放进了 modules 中

我们主要来看对 javaModules 的初始化部分,也就是这一部分:

for (const auto& jm : *javaModules) {
    modules.emplace_back(folly::make_unique<JavaNativeModule>(
                    winstance, jm, moduleMessageQueue));
}

在这里,我们将 JavaModuleWrapper::javaobject (jm) 作为参数传入 JavaNativeModule 的构造方法中。

JavaNativeModule

介绍

JavaNativeModule 声明在在 ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.h。

我们来看它的成员变量:

std::weak_ptr<Instance> instance_;
jni::global_ref<JavaModuleWrapper::javaobject> wrapper_;
std::shared_ptr<MessageQueueThread> messageQueueThread_;
std::vector<folly::Optional<MethodInvoker>> syncMethods_;

其中:

  • 虽然不是完全明白,但是能看出个大概
  • instance_ 这个一定是 React Native 在 C++ 层的核心类
  • wrapper_ 这是我们的 Native Module(从 Java 层一层一层传进来不容易啊……)
  • messageQueueThread_ 对队列也有引用
  • syncMethods_ 暂时不知道是啥

构造函数如下,保存各个成员:

JavaNativeModule(
    std::weak_ptr<Instance> instance,
    jni::alias_ref<JavaModuleWrapper::javaobject> wrapper,
    std::shared_ptr<MessageQueueThread> messageQueueThread)
        : instance_(std::move(instance))
        , wrapper_(make_global(wrapper))
        , messageQueueThread_(std::move(messageQueueThread)) {}

invoke

JavaModule 中在 Java 侧定义的方法是如何触发的呢?

答案在 JavaNativeModule::invoke 中(ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp):

void JavaNativeModule::invoke(unsigned int reactMethodId, folly::dynamic&& params, int callId) {
  messageQueueThread_->runOnQueue([this, reactMethodId, params=std::move(params), callId] {
    static auto invokeMethod = wrapper_->getClass()->getMethod<void(jint, ReadableNativeArray::javaobject)>("invoke");
    #ifdef WITH_FBSYSTRACE
    if (callId != -1) {
      fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
    }
    #endif
    invokeMethod(
      wrapper_,
      static_cast<jint>(reactMethodId),
      ReadableNativeArray::newObjectCxxArgs(std::move(params)).get());
  });
}

其中:

  • 从这里,我们知道了函数调用不是直接的,而是向线程队列中扔一个闭包
  • reactMethodId 表示方法 id
  • callId 表示调用 id
  • params 表示参数
  • 我们来看闭包内部:
    • 首先通过 wrapper_=( =jni::global_ref<JavaModuleWrapper::javaobject> )获取方法(invoke)
    • 之后触发 invokeMethod

我们再从 C++ 层网上看,触发 invokeMethod 最终执行的是 ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.java 的 invoke:

public void invoke(int methodId, ReadableNativeArray parameters) {
    if (mMethods == null || methodId >= mMethods.size()) {
        return;
    }

    mMethods.get(methodId).invoke(mJSInstance, parameters);
}

其中:

  • JSInstance 表示 JS 实例

下面来到了 NativeModule.NativeMethod 的 invoke 方法:

它是一个接口:

public interface NativeModule {
  interface NativeMethod {
    void invoke(JSInstance jsInstance, ReadableNativeArray parameters);
    String getType();
  }

JavaMethodWrapper 实现了这个接口。它的 invoke 方法核心逻辑如下:

@Override
public void invoke(JSInstance jsInstance, ReadableNativeArray parameters) {
    processArguments(); // 处理参数
    mMethod.invoke(mModuleWrapper.getModule(), mArguments); // 调用方法,这是一个反射方法

其中:

  • 这个过程中的参数处理具体还挺复杂的,这里暂不展开
  • 简单来说就是两步,处理参数,之后通过反射触发方法
  • 使用反射可能会有性能问题

至此,我们就梳理完成了,Native Module 中所定义的方法,是如何在 React Native 底层保存并调用的。

问题:ReactMethodId 是哪来的?

介绍

在前面有一个问题,就是我们在触发 Native Module 方法时是通过 reactMethodId 来调用的,这个 id 是怎么生成的呢?

JavaModuleWrapper.findMethods

Native Module 中的方法都使用 @ReactMethod 进行标注。

JavaModuleWrapper.findMethods 中会处理这个注解。

具体做法出,拿出被注解方法的 Method(我们前面 invoke 的那个)。

最终封装成一个 JavaMethodWrapper,放进 JavaModuleWrapper 的 mMethods 中:

private final ArrayList<NativeModule.NativeMethod> mMethods;

我们在回头来看 JavaModuleWrapper 的 invoke:

  public void invoke(int methodId, ReadableNativeArray parameters) {
    if (mMethods == null || methodId >= mMethods.size()) {
      return;
    }

    mMethods.get(methodId).invoke(mJSInstance, parameters);
  }
}

所谓的 reactMethodId 实际上是方法在 mMethods 中的 index。

这种实现的问题

NativeModule 中所提供的方法(被 @ReactMethod 注解的方法),是在运行时通过反射扫描的。

这样会导致性能降低,不过好在 NativeModule 在整个生命周期内只加载一次,因此问题不大。

但是也架不住量大,如果 Package 太多,最起码在冷启动时也会稍微卡一下。

我想这也是为啥,React Native 给 Native Module 做了懒加载,能环节不少这种的问题。

如果要彻底优化消除这里反射的性能影响的话,可以通过注解处理器在编译时就生成好 mMethods,运行时就不用再扫描了,直接拿来用就行。

小结

在本文中,我们从 ReactPackage 入手,分析了 React Native 是如何处理 ReactPackage 的。

同时以 ReactPackage 中的 Native Module 为准,分析了 Native Module 在 React Native 中是如何注册,如何在底层使用,以及如何被底层触发的。