跨平台原生模块 (C++)
使用 C++ 编写模块是在 Android 和 iOS 之间共享平台无关代码的最佳方式。使用纯 C++ 模块,您可以只编写一次逻辑,并立即在所有平台上重用它,而无需编写特定于平台的代码。
在本指南中,我们将逐步了解如何创建一个纯 C++ Turbo Native 模块。
- 创建 JS 规范
- 配置 Codegen 生成脚手架
- 实现原生逻辑
- 在 Android 和 iOS 应用程序中注册模块
- 在 JS 中测试您的更改
本指南的其余部分假设您已创建应用程序并运行以下命令
npx @react-native-community/cli@latest init SampleApp --version 0.76.0
1. 创建 JS 规范
纯 C++ Turbo Native 模块是 Turbo Native 模块。它们需要一个规范文件(也称为规范文件),以便 Codegen 为我们创建脚手架代码。规范文件也是我们在 JS 中访问 Turbo Native 模块时使用的文件。
规范文件需要使用类型化的 JS 方言编写。React Native 目前支持 Flow 或 TypeScript。
- 在应用程序的根文件夹中,创建一个名为
specs
的新文件夹。 - 创建一个名为
NativeSampleModule.ts
的新文件,其中包含以下代码
所有 Native Turbo 模块规范文件必须以 Native
为前缀,否则 Codegen 将忽略它们。
- TypeScript
- Flow
// @flow
import type {TurboModule} from 'react-native'
import { TurboModuleRegistry } from "react-native";
export interface Spec extends TurboModule {
+reverseString: (input: string) => string;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
"NativeSampleModule"
): Spec);
import {TurboModule, TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);
2. 配置 Codegen
下一步是配置 Codegen 在您的 package.json
中。更新文件以包含
"start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpecs",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.sampleapp.specs"
}
},
"dependencies": {
此配置告诉 Codegen 在 specs
文件夹中查找规范文件。它还指示 Codegen 仅为 modules
生成代码,并将生成的代码命名空间为 AppSpecs
。
3. 编写原生代码
编写 C++ Turbo Native 模块允许您在 Android 和 iOS 之间共享代码。因此,我们将编写一次代码,然后我们将研究需要对平台进行哪些更改才能提取 C++ 代码。
-
在
android
和ios
文件夹的同一级别创建一个名为shared
的文件夹。 -
在
shared
文件夹中,创建一个名为NativeSampleModule.h
的新文件。shared/NativeSampleModule.h#pragma once
#include <AppSpecsJSI.h>
#include <memory>
#include <string>
namespace facebook::react {
class NativeSampleModule : public NativeSampleModuleCxxSpec<NativeSampleModule> {
public:
NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker);
std::string reverseString(jsi::Runtime& rt, std::string input);
};
} // namespace facebook::react -
在
shared
文件夹中,创建一个名为NativeSampleModule.cpp
的新文件。shared/NativeSampleModule.cpp#include "NativeSampleModule.h"
namespace facebook::react {
NativeSampleModule::NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker)
: NativeSampleModuleCxxSpec(std::move(jsInvoker)) {}
std::string NativeSampleModule::reverseString(jsi::Runtime& rt, std::string input) {
return std::string(input.rbegin(), input.rend());
}
} // namespace facebook::react
让我们看看我们创建的这两个文件
NativeSampleModule.h
文件是纯 C++ TurboModule 的头文件。include
语句确保我们包含 Codegen 将创建的规范,其中包含我们需要实现的接口和基类。- 该模块位于
facebook::react
命名空间中,以便访问该命名空间中的所有类型。 - 类
NativeSampleModule
是实际的 Turbo Native 模块类,它扩展了NativeSampleModuleCxxSpec
类,该类包含一些粘合代码和样板代码,使此类能够作为 Turbo Native 模块。 - 最后,我们有构造函数,它接受指向
CallInvoker
的指针,以在需要时与 JS 通信以及我们必须实现的函数原型。
NativeSampleModule.cpp
文件是我们 Turbo Native 模块的实际实现,并实现了我们在规范中声明的构造函数和方法。
4. 在平台中注册模块
接下来的步骤将让我们在平台中注册模块。这是将原生代码公开给 JS 的步骤,以便 React Native 应用程序最终可以从 JS 层调用原生方法。
这是我们唯一需要编写一些特定于平台的代码的地方。
Android
为了确保 Android 应用程序可以有效地构建 C++ Turbo Native 模块,我们需要
- 创建
CMakeLists.txt
以访问我们的 C++ 代码。 - 修改
build.gradle
以指向新创建的CMakeLists.txt
文件。 - 在我们的 Android 应用程序中创建一个
OnLoad.cpp
文件以注册新的 Turbo Native 模块。
1. 创建 CMakeLists.txt
文件
Android 使用 CMake 进行构建。CMake 需要访问我们在共享文件夹中定义的文件,才能构建它们。
- 创建一个新的文件夹
SampleApp/android/app/src/main/jni
。jni
文件夹是 Android 的 C++ 端所在的位置。 - 创建一个
CMakeLists.txt
文件并添加此上下文
cmake_minimum_required(VERSION 3.13)
# Define the library name here.
project(appmodules)
# This file includes all the necessary to let you build your React Native application
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)
# Define where the additional source code lives. We need to crawl back the jni, main, src, app, android folders
target_sources(${CMAKE_PROJECT_NAME} PRIVATE ../../../../../shared/NativeSampleModule.cpp)
# Define where CMake can find the additional header files. We need to crawl back the jni, main, src, app, android folders
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ../../../../../shared)
CMake 文件执行以下操作
- 定义
appmodules
库,其中将包含所有应用程序的 C++ 代码。 - 加载基本的 React Native 的 CMake 文件。
- 添加我们需要使用
target_sources
指令构建的模块 C++ 源代码。默认情况下,React Native 已经使用默认源填充了appmodules
库,这里我们包含了我们自定义的源代码。您可以看到我们需要从jni
文件夹回溯到shared
文件夹,我们的 C++ Turbo 模块位于该文件夹中。 - 指定 CMake 在哪里可以找到模块头文件。在这种情况下,我们也需要从
jni
文件夹回溯。
2. 修改 build.gradle
以包含自定义 C++ 代码
Gradle 是协调 Android 构建的工具。我们需要告诉它在哪里可以找到 CMake
文件来构建 Turbo Native 模块。
- 打开
SampleApp/android/app/build.gradle
文件。 - 将以下块添加到 Gradle 文件中,位于现有的
android
块内
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.net.cn/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
+ externalNativeBuild {
+ cmake {
+ path "src/main/jni/CMakeLists.txt"
+ }
+ }
}
此块告诉 Gradle 文件在哪里查找 CMake 文件。该路径相对于 build.gradle
文件所在文件夹,因此我们需要将路径添加到 jni
文件夹中的 CMakeLists.txt
文件。
3. 注册新的 Turbo Native 模块
最后一步是在运行时注册新的 C++ Turbo Native 模块,以便当 JS 需要 C++ Turbo Native 模块时,应用程序知道在哪里可以找到它并返回它。
- 从
SampleApp/android/app/src/main/jni
文件夹中,运行以下命令
curl -O https://raw.githubusercontent.com/facebook/react-native/v0.76.0/packages/react-native/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp
- 然后,修改此文件,如下所示
#include <DefaultComponentsRegistry.h>
#include <DefaultTurboModuleManagerDelegate.h>
#include <autolinking.h>
#include <fbjni/fbjni.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <rncore.h>
+ // Include the NativeSampleModule header
+ #include <NativeSampleModule.h>
//...
std::shared_ptr<TurboModule> cxxModuleProvider(
const std::string& name,
const std::shared_ptr<CallInvoker>& jsInvoker) {
// Here you can provide your CXX Turbo Modules coming from
// either your application or from external libraries. The approach to follow
// is similar to the following (for a module called `NativeCxxModuleExample`):
//
// if (name == NativeCxxModuleExample::kModuleName) {
// return std::make_shared<NativeCxxModuleExample>(jsInvoker);
// }
+ // This code register the module so that when the JS side asks for it, the app can return it
+ if (name == NativeSampleModule::kModuleName) {
+ return std::make_shared<NativeSampleModule>(jsInvoker);
+ }
// And we fallback to the CXX module providers autolinked
return autolinking_cxxModuleProvider(name, jsInvoker);
}
// leave the rest of the file
这些步骤从 React Native 下载原始的 OnLoad.cpp
文件,以便我们可以安全地覆盖它以在应用程序中加载 C++ Turbo Native 模块。
下载文件后,我们可以通过以下方式修改它:
- 包含指向我们模块的头文件
- 注册 Turbo Native 模块,以便当 JS 需要它时,应用程序可以返回它。
现在,您可以从项目根目录运行 yarn android
以查看您的应用程序是否已成功构建。
iOS
为了确保 iOS 应用程序可以有效地构建 C++ Turbo Native 模块,我们需要
- 安装 pod 并运行 Codegen。
- 将
shared
文件夹添加到我们的 iOS 项目中。 - 在应用程序中注册 C++ Turbo Native 模块。
1. 安装 Pods 并运行 Codegen。
我们需要运行的第一步是我们每次必须准备 iOS 应用程序时都会运行的常规步骤。CocoaPods 是我们用于设置和安装 React Native 依赖项的工具,并且作为该过程的一部分,它还将为我们运行 Codegen。
cd ios
bundle install
bundle exec pod install
2. 将共享文件夹添加到 iOS 项目
此步骤将 shared
文件夹添加到项目中,使其对 Xcode 可见。
- 打开 CocoPods 生成的 Xcode 工作区。
cd ios
open SampleApp.xcworkspace
- 单击左侧的
SampleApp
项目,然后选择将文件添加到“Sample App”...
。
- 选择
shared
文件夹,然后单击添加
。
如果一切正确,您左侧的项目应如下所示
3. 在您的应用中注册 Cxx Turbo Native 模块
通过最后一步,我们将告诉 iOS 应用在哪里查找纯 C++ Turbo Native 模块。
在 Xcode 中,打开 AppDelegate.mm
文件并进行如下修改
#import <React/RCTBundleURLProvider.h>
+ #import <RCTAppDelegate+Protected.h>
+ #import "NativeSampleModule.h"
// ...
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
+- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
+ jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
+{
+ if (name == "NativeSampleModule") {
+ return std::make_shared<facebook::react::NativeSampleModule>(jsInvoker);
+ }
+
+ return [super getTurboModule:name jsInvoker:jsInvoker];
+}
@end
这些更改做了一些事情
- 导入
RCTAppDelegate+Protected
头文件,使 AppDelegate 可见它符合RCTTurboModuleManagerDelegate
协议。 - 导入纯 C++ Native Turbo 模块接口
NativeSampleModule.h
- 重写 C++ 模块的
getTurboModule
方法,以便当 JS 端请求名为NativeSampleModule
的模块时,应用知道要返回哪个模块。
如果您现在从 Xcode 构建您的应用程序,您应该能够成功构建。
5. 测试您的代码
现在是时候从 JS 访问我们的 C++ Turbo Native 模块了。为此,我们必须修改 App.tsx
文件以导入 Turbo Native 模块并在我们的代码中调用它。
- 打开
App.tsx
文件。 - 将模板内容替换为以下代码
import React from 'react';
import {
Button,
SafeAreaView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import SampleTurboModule from './specs/NativeSampleModule';
function App(): React.JSX.Element {
const [value, setValue] = React.useState('');
const [reversedValue, setReversedValue] = React.useState('');
const onPress = () => {
const revString = SampleTurboModule.reverseString(value);
setReversedValue(revString);
};
return (
<SafeAreaView style={styles.container}>
<View>
<Text style={styles.title}>
Welcome to C++ Turbo Native Module Example
</Text>
<Text>Write down here he text you want to revert</Text>
<TextInput
style={styles.textInput}
placeholder="Write your text here"
onChangeText={setValue}
value={value}
/>
<Button title="Reverse" onPress={onPress} />
<Text>Reversed text: {reversedValue}</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 18,
marginBottom: 20,
},
textInput: {
borderColor: 'black',
borderWidth: 1,
borderRadius: 5,
padding: 10,
marginTop: 10,
},
});
export default App;
此应用中有趣的行是
import SampleTurboModule from './specs/NativeSampleModule';
:此行在应用中导入 Turbo Native 模块,const revString = SampleTurboModule.reverseString(value);
在onPress
回调中:这就是您在应用中使用 Turbo Native 模块的方式。
为了这个例子,并尽可能简短,我们直接在我们的应用中导入了规范文件。在这种情况下,最佳实践是创建一个单独的文件来包装规范并将其用于您的应用程序。这允许您为规范准备输入,并让您更好地控制 JS 中的规范。
恭喜,您编写了第一个 C++ Turbo Native 模块!