如何编写 React Native 的 CxxModule

2019-04-11

前言

本文的原作者是 Kudo Chien,文章源链接为 How to write a React Native CxxModule 我将其翻译成中文。

CxxModule 是 React Native 中一个很少被提起的特性,你可以直接使用 C++ 编写 Native Module。

特别是对于 Android,给 C++ 写 JNI 封装供 Java 调用非常痛苦。

除此之外,CxxModule 的性能也更高,因为 Bridge 调用只在 JSVM 和 C++ 中发生,没有 JVM 的参与。

如果你想基于现有 C++ 代码开发 Native Module,你一定要试试 CxxModule。

在本文中,我会介绍 Android 的 CxxModule,由于 C++ 的跨平台特性,在 Obj-C 下也是一样的。

如何编写一个 CxxModule

在 React Native 的源代码中包含一个 CxxModule 的范例。我(原作者)也写了一个 GitHub 示例

通过重载 getMethods() 方法声明导出给 JavaScript 的方法:

auto HelloCxxModule::getMethods() -> std::vector<Method> {
  return {
      Method("foo", [](folly::dynamic args, Callback cb) { cb({"foo"}); }),
  };
}

其中:

  • 这向 JavaScript 导入了一个名为 foo 的方法
  • 它接收一个 Callback 参数

通过重载 getConstants() 方法声明导出给 JavaScript 的常量:

auto HelloCxxModule::getConstants() -> std::map<std::string, folly::dynamic> {
  return {
      {"one", 1}, {"two", 2}, {"animal", "fox"},
  };
}

从 C++ 发送 JS Event,通过 callJSFunction() 调用 RCTDeviceEventEmitter.emit():

auto HelloCxxModule::getMethods() -> std::vector<Method> {
  return {
      Method("bar",
             [this]() {
               if (auto reactInstance = getInstance().lock()) {
                 reactInstance->callJSFunction(
                     "RCTDeviceEventEmitter", "emit",
                     folly::dynamic::array(
                         "appStateDidChange",
                         folly::dynamic::object("app_state", "active")));
               }
             }),
  };
}

更多信息可以参见 CxxModule.h,比如如何导出一个返回 Promise 的方法。

现在已经知道了如何编写 CxxModule,但是还有一个重要部分在网络上没有人提到过——如何将 CxxModule 注册到 package 上面?

有一个 CxxModuleWrapper.java 类,它是注册用纯 C++ 编写的 Native Module 的最简单方法。

否则的话,你需要按照 hybrid 场景来,即代码将被 JS 和 Java 两者调用,请参考 Hybrid class

让我们回到 CxxModuleWrapper.makeDso(),它接收两个参数:

  • 一个是动态库的文件名
  • 一个是被暴露的入口方法,来创建一个 CxxModule 实例

代码如下(export.cpp):

extern "C" HelloCxxModule* createHelloCxxModule() {
  return new HelloCxxModule();
}

HelloCxxPackage.java:

public final class HelloCxxPackage implements ReactPackage {
  @Override
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    return Arrays.<NativeModule>asList(
        // I have librnpackage-hellocxx.so the exported createHelloCxxModule() above.
        CxxModuleWrapper.makeDso("rnpackage-hellocxx", "createHelloCxxModule")
    );
  }
  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Collections.emptyList();
  }
}

现在你已经学会了 CxxModule 的基础用法,下面让我们进入最困难的部分。

构建过程

我认为编译开源的 React Native 来接纳 CxxModule 这一部分是最难的。

这可能也是为什么没人讨论 CxxModule 的使用。

在上面的代码中,你会看到 CxxModule 严重依赖 Folly,这意味着必须使用 Folly 来构建出动态库(在 Android 上是 lib*.so)。

Folly 还依赖一些第三方库,例如 boost 和 glog。

尽管我们不需要重新构建这些库,但是我们仍然需要它们的头文件来构建我们的 CxxModule。

从根本上,这个过程非常类似于从代码构建 React Native 的过程。

我不会详细讲如何构建。请参见示例工程的 build.gradlendkbuild Android.mk

为了将 CxxModule 链接到 React Native 中预先构建的库,我抽取了 react-native AAR 的 JNI。请参见 Gradle 的 prepareExtractedLibs

总结

总体上,你可以参见 HelloCxxModule 的提交记录。

实际上还有一些额外工作要做。

如果你的现有代码使用 C++ 编写的,为了复用这些代码,这些额外工作还是值得的。

在我们公司里,当我们做 Puffin Windows 版浏览器的时候,由于 React Native Windows 不支持 CxxModule,我们编写了大量封装代码。

例如,为了导出一个 native module 方法,我们需要:

  • 将 C++ 代码封装成 C 代码
  • 封装 C# 代码,并进行 PInvoke 到 DLL 中的 C 函数
  • 可选择地将方法从 JavaScript 封装到 NativeModules.foo

如果你正在开发 Android APP,JNI 封装还是挺麻烦的,CxxModule 能省去你大量的封装样板代码。

另外,在 React Native 的新架构中,有一个新的 JSI 的设计,它貌似支持直接在 JS VM 中注册 JS 方法。我才到时候会更加容易。

最后,请让我宣传我们的产品——Puffin Browser on Windows。它非常快,打开大量标签也没有过多性能损耗。