跨平台原生模块 (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 模块。它们需要一个规范文件(也称为 spec 文件),以便 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
下一步是在 package.json
中配置 Codegen。更新文件以包含
"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
文件夹返回到我们 C++ Turbo 模块所在的shared
文件夹。 - 指定 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. 将 shared 文件夹添加到 iOS 项目
此步骤将 shared
文件夹添加到项目中,使其对 Xcode 可见。
- 打开 CocoPods 生成的 Xcode Workspace。
cd ios
open SampleApp.xcworkspace
- 点击左侧的
SampleApp
项目,然后选择Add files to "Sample App"...
。
- 选择
shared
文件夹,然后点击Add
。
如果一切操作正确,左侧的项目应该看起来像这样
3. 在应用程序中注册 Cxx Turbo Native 模块
要在应用程序中注册纯 Cxx Turbo Native 模块,您需要
- 为原生模块创建一个
ModuleProvider
- 配置
package.json
以将 JS 模块名称与 ModuleProvider 类关联起来。
ModuleProvider 是一个 Objective-C++ 类,它将纯 C++ 模块与您的 iOS 应用程序的其余部分粘合在一起。
3.1 创建 ModuleProvider
- 在 Xcode 中,选择
SampleApp
项目并按下 ⌘ + N 创建一个新文件。 - 选择
Cocoa Touch Class
模板 - 添加名称
SampleNativeModuleProvider
(其他字段保持为Subclass of: NSObject
和Language: Objective-C
) - 点击 Next 生成文件。
- 将
SampleNativeModuleProvider.m
重命名为SampleNativeModuleProvider.mm
。mm
扩展名表示一个 Objective-C++ 文件。 - 使用以下内容实现
SampleNativeModuleProvider.h
的内容
#import <Foundation/Foundation.h>
#import <ReactCommon/RCTTurboModule.h>
NS_ASSUME_NONNULL_BEGIN
@interface NativeSampleModuleProvider : NSObject <RCTModuleProvider>
@end
NS_ASSUME_NONNULL_END
这声明了一个符合 RCTModuleProvider
协议的 NativeSampleModuleProvider
对象。
- 使用以下内容实现
SampleNativeModuleProvider.mm
的内容
#import "NativeSampleModuleProvider.h"
#import <ReactCommon/CallInvoker.h>
#import <ReactCommon/TurboModule.h>
#import "NativeSampleModule.h"
@implementation NativeSampleModuleProvider
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeSampleModule>(params.jsInvoker);
}
@end
此代码通过在调用 getTurboModule:
方法时创建纯 C++ NativeSampleModule
来实现 RCTModuleProvider
协议。
3.2 更新 package.json
最后一步是更新 package.json
,以告知 React Native 原生模块的 JS 规范与这些规范在原生代码中的具体实现之间的关联。
按如下方式修改 package.json
"start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpecs",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.sampleapp.specs"
},
"ios": {
"modulesProvider": {
"NativeSampleModule": "NativeSampleModuleProvider"
}
}
},
"dependencies": {
此时,您需要重新安装 pod 以确保 codegen 再次运行并生成新文件
# from the ios folder
bundle exec pod install
open SampleApp.xcworkspace
如果您现在从 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 模块!
![]() | ![]() |