[ReactNative笔记]my-mod适配CxxModule

249 阅读2分钟

参考SampleCxxModule.hSampleCxxModule.cpp

准备环境

  • volta
$ volta ls
⚡️ Currently active tools:

    Node: v16.20.2 (default)
    Yarn: v1.22.19 (default)
    Tool binaries available:
        nrm (default)
  • nrm
$ nrm ls
* npm ---------- https://registry.npmjs.org/
  yarn --------- https://registry.yarnpkg.com/
  tencent ------ https://mirrors.cloud.tencent.com/npm/
  cnpm --------- https://r.cnpmjs.org/
  taobao ------- https://registry.npmmirror.com/
  npmMirror ---- https://skimdb.npmjs.com/registry/
  • react-native
$ npx react-native info
...
  react-native:
    installed: 0.72.6
...
  • create-react-native-library
$ npx create-react-native-library --version
0.34.2

创建my-mod

  • 创建my-mod
$ npx create-react-native-library my-mod
✔ What is the name of the npm package? … react-native-my-mod
✔ What is the description for the package? … My module with C++
✔ What is the name of package author? … Sunbreak
✔ What is the email address for the package author? … sunbreak.wang@gmail.com
✔ What is the URL for the package author? … https://github.com/Sunbreak
✔ What is the URL for the repository? … https://github.com/Sunbreak/react-native-my-mod
✔ What type of library do you want to develop? › Native module
✔ Which languages do you want to use? › C++ for Android & iOS
✔ Project created successfully at my-mod!
  • 初始化my-mod
$ cd my-mod
$ yarn
  • Android运行my-mod
$ yarn example start &
$ yarn example android
  • iOS运行my-mode
$ yarn example start &
$ yarn example ios

origin-nojsi.png

适配CxxModule

适配JS/TS

  • 修改index.tsx
 export function multiply(a: number, b: number): Promise<number> {
-  return MyMod.multiply(a, b);
+  return new Promise((resolve) => MyMod.multiply(a, b, resolve));
 }

适配C++

  • 修改react-native-my-mod.h
#ifndef MYMOD_H
#define MYMOD_H

#include <cxxreact/CxxModule.h>

using namespace facebook::xplat::module;

namespace mymod {

class MyModCxxModule : public CxxModule {
 public:
  MyModCxxModule() = default;

  inline std::string getName() override {
    return "MyMod";
  };

  auto getMethods() -> std::vector<CxxModule::Method> override;
};

}

extern "C" CxxModule* createMyModCxxModule();

#endif /* MYMOD_H */
  • 修改react-native-my-mod.cpp
#include "react-native-my-mod.h"
#include <cxxreact/JsArgumentHelpers.h>

using namespace facebook::xplat;

namespace mymod {

std::vector<CxxModule::Method> MyModCxxModule::getMethods() {
  return {
      CxxModule::Method(
          "multiply",
          [](folly::dynamic args, Callback cb) {
            cb({jsArgAsDouble(args, 0) * jsArgAsDouble(args, 1)});
          }),
  };
}

}

extern "C" CxxModule* createMyModCxxModule() {
  return new mymod::MyModCxxModule();
}

适配Android

  • 修改build.gradle
  defaultConfig {
     ...
     externalNativeBuild {
       cmake {
+        arguments "-DANDROID_STL=c++_shared",
+                  // FIXME https://github.com/facebook/react-native/pull/37451
+                  "-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON"
         cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all"
         abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
       }
     }
   }

+  packagingOptions {
+    exclude "**/libfolly_runtime.so"
+    exclude "**/libreact_nativemodule_core.so"
+  }
+
+  buildFeatures {
+    prefab true
+  }
  • 修改CMakelists.txt
cmake_minimum_required(VERSION 3.13)

set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_STANDARD 17)

# Define the library name here.
project(cpp)

include(Android-prebuilt.cmake)

add_library(${CMAKE_PROJECT_NAME}
    SHARED
        ../cpp/react-native-my-mod.cpp
)

# Specifies a path to native header files.
target_include_directories(${CMAKE_PROJECT_NAME}
    PRIVATE
        ../cpp
)

target_link_libraries(${CMAKE_PROJECT_NAME}
        folly_runtime
        common_flags
        react_nativemodule_core
)

target_compile_options(${CMAKE_PROJECT_NAME}
    PRIVATE
        -Wpedantic
        -Wno-gnu-zero-variadic-macro-arguments
)
  • 新增Android-prebuilt.cmake
# Prefab packages from React Native
find_package(ReactAndroid REQUIRED CONFIG)
add_library(react_render_debug ALIAS ReactAndroid::react_render_debug)
add_library(turbomodulejsijni ALIAS ReactAndroid::turbomodulejsijni)
add_library(runtimeexecutor ALIAS ReactAndroid::runtimeexecutor)
add_library(react_codegen_rncore ALIAS ReactAndroid::react_codegen_rncore)
add_library(react_debug ALIAS ReactAndroid::react_debug)
add_library(react_render_componentregistry ALIAS ReactAndroid::react_render_componentregistry)
add_library(react_newarchdefaults ALIAS ReactAndroid::react_newarchdefaults)
add_library(react_render_core ALIAS ReactAndroid::react_render_core)
add_library(react_render_graphics ALIAS ReactAndroid::react_render_graphics)
add_library(rrc_view ALIAS ReactAndroid::rrc_view)
add_library(jsi ALIAS ReactAndroid::jsi)
add_library(glog ALIAS ReactAndroid::glog)
add_library(fabricjni ALIAS ReactAndroid::fabricjni)
add_library(react_render_mapbuffer ALIAS ReactAndroid::react_render_mapbuffer)
add_library(yoga ALIAS ReactAndroid::yoga)
add_library(folly_runtime ALIAS ReactAndroid::folly_runtime)
add_library(react_nativemodule_core ALIAS ReactAndroid::react_nativemodule_core)
add_library(react_render_imagemanager ALIAS ReactAndroid::react_render_imagemanager)
add_library(rrc_image ALIAS ReactAndroid::rrc_image)
add_library(rrc_legacyviewmanagerinterop ALIAS ReactAndroid::rrc_legacyviewmanagerinterop)

find_package(fbjni REQUIRED CONFIG)
add_library(fbjni ALIAS fbjni::fbjni)

# This CMake file exposes the Folly Flags that all the libraries should use when
# compiling/linking against a dependency which requires folly.
SET(folly_FLAGS
        -DFOLLY_NO_CONFIG=1
        -DFOLLY_HAVE_CLOCK_GETTIME=1
        -DFOLLY_USE_LIBCPP=1
        -DFOLLY_CFG_NO_COROUTINES=1
        -DFOLLY_MOBILE=1
        -DFOLLY_HAVE_RECVMMSG=1
        -DFOLLY_HAVE_PTHREAD=1
        # Once we target android-23 above, we can comment
        # the following line. NDK uses GNU style stderror_r() after API 23.
        -DFOLLY_HAVE_XSI_STRERROR_R=1
        )

# We use an interface target to propagate flags to all the generated targets
# such as the folly flags or others.
add_library(common_flags INTERFACE)
target_compile_options(common_flags INTERFACE ${folly_FLAGS})
  • 删除cpp-adapter.cpp
-#include <jni.h>
-#include "react-native-my-mod.h"
-
-extern "C"
-JNIEXPORT jdouble JNICALL
-Java_com_mymod_MyModModule_nativeMultiply(JNIEnv *env, jclass type, jdouble a, jdouble b) {
-    return mymod::multiply(a, b);
-}
  • 删除MyModModule.java
-package com.mymod;
-
-import androidx.annotation.NonNull;
-
-import com.facebook.react.bridge.Promise;
-import com.facebook.react.bridge.ReactApplicationContext;
-import com.facebook.react.bridge.ReactContextBaseJavaModule;
-import com.facebook.react.bridge.ReactMethod;
-import com.facebook.react.module.annotations.ReactModule;
-
-@ReactModule(name = MyModModule.NAME)
-public class MyModModule extends ReactContextBaseJavaModule {
-  public static final String NAME = "MyMod";
-
-  public MyModModule(ReactApplicationContext reactContext) {
-    super(reactContext);
-  }
-
-  @Override
-  @NonNull
-  public String getName() {
-    return NAME;
-  }
-
-  static {
-    System.loadLibrary("cpp");
-  }
-
-  private static native double nativeMultiply(double a, double b);
-
-  // Example method
-  // See https://reactnative.dev/docs/native-modules-android
-  @ReactMethod
-  public void multiply(double a, double b, Promise promise) {
-    promise.resolve(nativeMultiply(a, b));
-  }
-}
  • 修改MyModPackage.java
 import com.facebook.react.ReactPackage;
+import com.facebook.react.bridge.CxxModuleWrapper;
 import com.facebook.react.bridge.NativeModule;
 import com.facebook.react.bridge.ReactApplicationContext;
 import com.facebook.react.uimanager.ViewManager;
...
 public class MyModPackage implements ReactPackage {
   @Override
   public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
     List<NativeModule> modules = new ArrayList<>();
-    modules.add(new MyModModule(reactContext));
+    modules.add(CxxModuleWrapper.makeDso("cpp", "createMyModCxxModule"));
     return modules;
   }
  • 修改AndroidManifest.xml
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.mymod">
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.mymod">
+
+    <application>
+        <!-- for CxxModuleWrapper.makeDso -->
+        <meta-data
+            tools:replace="android:value"
+            android:name="com.facebook.soloader.enabled"
+            android:value="true" />
+    </application>
 </manifest>
  • 修改AndroidManifestNew.xml
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <application>
+        <!-- for CxxModuleWrapper.makeDso -->
+        <meta-data
+            tools:replace="android:value"
+            android:name="com.facebook.soloader.enabled"
+            android:value="true" />
+    </application>
 </manifest>

适配iOS

  • 修改MyMod.h
 #import "react-native-my-mod.h"
 #endif

-#ifdef RCT_NEW_ARCH_ENABLED
-#import "RNMyModSpec.h"
+#import <React/RCTCxxModule.h>

-@interface MyMod : NSObject <NativeMyModSpec>
-#else
-#import <React/RCTBridgeModule.h>
+@interface MyMod : RCTCxxModule

-@interface MyMod : NSObject <RCTBridgeModule>
-#endif
+- (std::unique_ptr<facebook::xplat::module::CxxModule>)createModule;

 @end
  • 修改MyMod.mm
 @implementation MyMod
 RCT_EXPORT_MODULE()

-// Example method
-// See // https://reactnative.dev/docs/native-modules-ios
-RCT_EXPORT_METHOD(multiply:(double)a
-                  b:(double)b
-                  resolve:(RCTPromiseResolveBlock)resolve
-                  reject:(RCTPromiseRejectBlock)reject)
-{
-    NSNumber *result = @(mymod::multiply(a, b));
-
-    resolve(result);
+- (std::unique_ptr<facebook::xplat::module::CxxModule>)createModule {
+  return std::make_unique<mymod::MyModCxxModule>();
 }

-
 @end
  • 修改react-native-my-mod.podspec
   s.platforms    = { :ios => "11.0" }
   s.source       = { :git => "https://github.com/Sunbreak/react-native-my-mod.git", :tag => "#{s.version}" }
+  s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/React-Core\"" }

   s.source_files = "ios/**/*.{h,m,mm}", "cpp/**/*.{hpp,cpp,c,h}"

...
   if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
     s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
     s.pod_target_xcconfig    = {
-        "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
+        "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/React-Core\"",
         "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
         "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
     }

测试适配结果

  • Android运行my-mod

参考“创建my-mod”

  • iOS运行my-mod
$ pushd example/ios && pod install && popd
$ yarn example start &
$ yarn example ios

JS/C++的类型/方法转换

参考SampleCxxModule.hSampleCxxModule.cpp

auto SampleCxxModule::getMethods() -> std::vector<Method> {
  return {
      Method("hello", [this] { sample_->hello(); }),
      Method(
          "add",
          [this](dynamic args, Callback cb) {
            LOG(WARNING) << "Sample: add => "
                         << sample_->add(
                                jsArgAsDouble(args, 0), jsArgAsDouble(args, 1));
            cb({sample_->add(jsArgAsDouble(args, 0), jsArgAsDouble(args, 1))});
          }),
      Method(
          "concat",
          [this](dynamic args, Callback cb) {
            cb({sample_->concat(
                jsArgAsString(args, 0), jsArgAsString(args, 1))});
          }),
      Method(
          "repeat",
          [this](dynamic args, Callback cb) {
            cb({sample_->repeat(
                (int)jsArgAsInt(args, 0), jsArgAsString(args, 1))});
          }),
      Method("save", this, &SampleCxxModule::save),
      Method("load", this, &SampleCxxModule::load),
      Method(
          "call_later",
          [this](dynamic args, Callback cb) {
            sample_->call_later((int)jsArgAsInt(args, 0), [cb] { cb({}); });
          }),
      Method("except", [this] { sample_->except(); }),
      Method(
          "twice",
          [this](dynamic args) -> dynamic {
            return sample_->twice(jsArgAsDouble(args, 0));
          },
          SyncTag),
      Method(
          "syncHello",
          [this]() -> dynamic {
            sample_->hello();
            return nullptr;
          },
          SyncTag),
      Method(
          "addIfPositiveAsPromise",
          [](dynamic args, Callback cb, Callback cbError) {
            auto a = jsArgAsDouble(args, 0);
            auto b = jsArgAsDouble(args, 1);
            if (a < 0 || b < 0) {
              cbError({"Negative number!"});
            } else {
              cb({a + b});
            }
          }),
      Method(
          "addIfPositiveAsAsync",
          [](dynamic args, Callback cb, Callback cbError) {
            auto a = jsArgAsDouble(args, 0);
            auto b = jsArgAsDouble(args, 1);
            if (a < 0 || b < 0) {
              cbError({"Negative number!"});
            } else {
              cb({a + b});
            }
          },
          AsyncTag),

  };
}