准备环境
- 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
适配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++的类型/方法转换
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),
};
}