在 Android 上启用 TurboModule
本文档内容仍处于实验阶段,内容会随着版本的迭代进行修改。您可随时在我们的工作组的讨论区发送反馈。 此外,本文档还包含了若干需手动配置的步骤,但这不代表新架构稳定版的最终开发体验。我们仍在开发相关的工具、模板和第三方库,帮助你更快地迁移到新架构上,而非从头开始配置环境。
Make sure your application meets all the prerequisites.
1. Enable NDK and the native build
In this iteration of the guide we’re setting up the project to let you build from source. You might notice an increase in your build time because of this. You can mitigate this by following the approach described in Speeding up your Build phase guide.
The Codegen will output some Java and some C++ code that now we need to build.
Let’s edit your module-level build.gradle
to include the two externalNativeBuild
blocks detailed below inside the android{}
block:
android {
defaultConfig {
applicationId "com.awesomeproject"
// ...
// Add this block
externalNativeBuild {
ndkBuild {
arguments "APP_PLATFORM=android-21",
"APP_STL=c++_shared",
"NDK_TOOLCHAIN_VERSION=clang",
"GENERATED_SRC_DIR=$buildDir/generated/source",
"PROJECT_BUILD_DIR=$buildDir",
"REACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid",
"REACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build",
"NODE_MODULES_DIR=$rootDir/../node_modules"
cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1"
cppFlags "-std=c++17"
targets "myapplication_appmodules"
// Fix for windows limit on number of character in file paths and in command lines
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
arguments "NDK_APP_SHORT_COMMANDS=true"
}
}
}
}
// Add this block
externalNativeBuild {
ndkBuild {
path "$projectDir/src/main/jni/Android.mk"
}
}
}
In the same build.gradle
file, inside the same android{}
let’s add also the following section:
android {
// ...
def reactAndroidProjectDir = project(':ReactAndroid').projectDir
def packageReactNdkLibs = tasks.register("packageReactNdkLibs", Copy) {
dependsOn(":ReactAndroid:packageReactNdkLibsForBuck")
dependsOn("generateCodegenArtifactsFromSchema")
from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
into("$buildDir/react-ndk/exported")
}
afterEvaluate {
preBuild.dependsOn(packageReactNdkLibs)
configureNdkBuildDebug.dependsOn(preBuild)
configureNdkBuildRelease.dependsOn(preBuild)
}
packagingOptions {
pickFirst '**/libhermes.so'
pickFirst '**/libjsc.so'
}
}
Finally, we need to create a Makefile inside the src/main/jni
folder called Android.mk
with the following content:
THIS_DIR := $(call my-dir)
include $(REACT_ANDROID_DIR)/Android-prebuilt.mk
# If you wish to add a custom TurboModule or Fabric component in your app you
# will have to include the following autogenerated makefile.
# include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk
# Includes the MK file for autolinked libraries
include $(PROJECT_BUILD_DIR)/generated/rncli/src/main/jni/Android-rncli.mk
include $(CLEAR_VARS)
LOCAL_PATH := $(THIS_DIR)
# You can customize the name of your application .so file here.
LOCAL_MODULE := awesomeapp_appmodules
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(PROJECT_BUILD_DIR)/generated/rncli/src/main/jni
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) $(wildcard $(PROJECT_BUILD_DIR)/generated/rncli/src/main/jni/*.cpp)
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) $(PROJECT_BUILD_DIR)/generated/rncli/src/main/jni
# If you wish to add a custom TurboModule or Fabric component in your app you
# will have to uncomment those lines to include the generated source
# files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni)
#
# LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
# LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp)
# LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
# Here you should add any native library you wish to depend on.
LOCAL_SHARED_LIBRARIES := \
libfabricjni \
libfbjni \
libfolly_runtime \
libglog \
libjsi \
libreact_codegen_rncore \
libreact_debug \
libreact_nativemodule_core \
libreact_render_componentregistry \
libreact_render_core \
libreact_render_debug \
libreact_render_graphics \
librrc_view \
libruntimeexecutor \
libturbomodulejsijni \
libyoga
# Autolinked libraries
LOCAL_SHARED_LIBRARIES += $(call import-codegen-modules)
LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17
include $(BUILD_SHARED_LIBRARY)
This setup will run a native build on your project and will compile the C++ files that have been generated by the codegen. You will see the native build running with the Gradle task :app:externalNativeBuildDebug
You can now verify that everything works correctly by running your android app:
yarn react-native run-android
2. Java/Kotlin - Provide a ReactPackageTurboModuleManagerDelegate
Now is time to actually use the TurboModule.
First, we will need to create a ReactPackageTurboModuleManagerDelegate
subclass, like the following:
- Java
- Kotlin
package com.awesomeproject;
import com.facebook.jni.HybridData;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactPackageTurboModuleManagerDelegate;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.soloader.SoLoader;
import java.util.List;
public class MyApplicationTurboModuleManagerDelegate extends ReactPackageTurboModuleManagerDelegate {
private static volatile boolean sIsSoLibraryLoaded;
protected MyApplicationTurboModuleManagerDelegate(ReactApplicationContext reactApplicationContext, List<ReactPackage> packages) {
super(reactApplicationContext, packages);
}
protected native HybridData initHybrid();
public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder {
protected MyApplicationTurboModuleManagerDelegate build(
ReactApplicationContext context, List<ReactPackage> packages) {
return new MyApplicationTurboModuleManagerDelegate(context, packages);
}
}
@Override
protected synchronized void maybeLoadOtherSoLibraries() {
// Prevents issues with initializer interruptions.
if (!sIsSoLibraryLoaded) {
SoLoader.loadLibrary("myapplication_appmodules");
sIsSoLibraryLoaded = true;
}
}
}
package com.awesomeproject
import com.facebook.jni.HybridData
import com.facebook.react.ReactPackage
import com.facebook.react.ReactPackageTurboModuleManagerDelegate
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.soloader.SoLoader
class MyApplicationTurboModuleManagerDelegate
protected constructor(
reactApplicationContext: ReactApplicationContext,
packages: List<ReactPackage>
) : ReactPackageTurboModuleManagerDelegate(reactApplicationContext, packages) {
override protected external fun initHybrid(): HybridData?
class Builder : ReactPackageTurboModuleManagerDelegate.Builder() {
override protected fun build(
context: ReactApplicationContext,
packages: List<ReactPackage>
): MyApplicationTurboModuleManagerDelegate =
MyApplicationTurboModuleManagerDelegate(context, packages)
}
@Synchronized
override protected fun maybeLoadOtherSoLibraries() {
// Prevents issues with initializer interruptions.
if (!isSoLibraryLoaded) {
SoLoader.loadLibrary("myapplication_appmodules")
isSoLibraryLoaded = true
}
}
companion object {
@Volatile private var isSoLibraryLoaded = false
}
}
Please note that the SoLoader.loadLibrary
parameter (in this case "myapplication_appmodules")
should be the same as the one specified for LOCAL_MODULE :=
inside the Android.mk
file you created before.
This class will then be responsible of loading the TurboModules and will take care of loading the native library build with the NDK at runtime.
3. Adapt your ReactNativeHost
to use the ReactPackageTurboModuleManagerDelegate
Then, you can provide the class you created to your ReactNativeHost
. You can locate your ReactNativeHost
by searching for the getReactNativeHost()
. The ReactNativeHost
is usually located inside your Application
class.
Once you located it, you need to add the getReactPackageTurboModuleManagerDelegateBuilder
method as from the snippet below:
- Java
- Kotlin
public class MyApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() { /* ... */ }
@Override
protected List<ReactPackage> getPackages() { /* ... */ }
@Override
protected String getJSMainModuleName() {/* ... */ }
@NonNull
@Override
protected ReactPackageTurboModuleManagerDelegate.Builder getReactPackageTurboModuleManagerDelegateBuilder() {
return new MyApplicationTurboModuleManagerDelegate.Builder();
}
};
}
class MyApplication : Application(), ReactApplication {
private val reactNativeHost: ReactNativeHost =
object : ReactNativeHost(this) {
override fun getUseDeveloperSupport(): Boolean {
/* ... */
}
override fun getPackages(): List<ReactPackage?>? {
/* ... */
}
override fun getJSMainModuleName(): String? {
/* ... */
}
@NonNull
override fun getReactPackageTurboModuleManagerDelegateBuilder() =
ReactPackageTurboModuleManagerDelegate.Builder()
}
}
4. Extend the getPackages()
from your ReactNativeHost
to use the TurboModule
Still on the ReactNativeHost
, we need to extend the the getPackages()
method to include the newly created TurboModule. Update the method to include the following:
- Java
- Kotlin
public class MyApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() { /* ... */ }
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// Add those lines
packages.add(new TurboReactPackage() {
@Nullable
@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
if (name.equals(NativeAwesomeManager.NAME)) {
return new NativeAwesomeManager(reactContext);
} else {
return null;
}
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return () -> {
final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
moduleInfos.put(
NativeAwesomeManager.NAME,
new ReactModuleInfo(
NativeAwesomeManager.NAME,
"NativeAwesomeManager",
false, // canOverrideExistingModule
false, // needsEagerInit
true, // hasConstants
false, // isCxxModule
true // isTurboModule
)
);
return moduleInfos;
};
}
});
return packages;
}
@Override
protected String getJSMainModuleName() {/* ... */ }
@NonNull
@Override
protected ReactPackageTurboModuleManagerDelegate.Builder getReactPackageTurboModuleManagerDelegateBuilder() {
return new MyApplicationTurboModuleManagerDelegate.Builder();
}
};
}
class MyApplication() : Application(), ReactApplication {
private val reactNativeHost: ReactNativeHost =
object : ReactNativeHost(this) {
override fun getUseDeveloperSupport(): Boolean {
/* ... */
}
override protected fun getPackages(): List<ReactPackage>? {
val packages: MutableList<ReactPackage> = PackageList(this).getPackages()
// Add those lines
packages.add(
object : TurboReactPackage() {
@Nullable
override fun getModule(
name: String,
reactContext: ReactApplicationContext?
): NativeModule? =
if ((name == NativeAwesomeManager.NAME)) {
NativeAwesomeManager(reactContext)
} else {
null
}
override fun getReactModuleInfoProvider() =
mutableMapOf<String, ReactModuleInfo>(
NativeAwesomeManager.NAME,
ReactModuleInfo(
NativeAwesomeManager.NAME,
"NativeAwesomeManager",
false, // canOverrideExistingModule
false, // needsEagerInit
true, // hasConstants
false, // isCxxModule
true // isTurboModule
)
)
}
)
return packages
}
override protected fun getJSMainModuleName(): String? {
/* ... */
}
@NonNull
override protected fun getReactPackageTurboModuleManagerDelegateBuilder():
ReactPackageTurboModuleManagerDelegate.Builder? {
return Builder()
}
}
}
5. C++ Provide a native implementation for the methods in your *TurboModuleDelegate
class
If you take a closer look at the class MyApplicationTurboModuleManagerDelegate
that you created before, you will notice how some of the methods are native
.
Therefore, you need to provide some C++ classes to implement those methods. Specifically you will need those files, to be added inside the src/main/jni
folder:
MyApplicationTurboModuleManagerDelegate.h
An header file for the TurboModule Delegate.MyApplicationTurboModuleManagerDelegate.cpp
The implementation of the aforementioned header file.MyApplicationModuleProvider.h
A header file for the TurboModule provider, where you can specify which TurboModules you want to load.MyApplicationModuleProvider.cpp
The implementation for the aforementioned header file.OnLoad.cpp
Where the initialisation code will be placed. Specifically TurboModule uses FBJNI so the initialisation for such library lives there.
The content of those files should be the following:
MyApplicationTurboModuleManagerDelegate.h
Please note that the kJavaDescriptor
should be adapted to follow the package name you picked for your project.
#include <memory>
#include <string>
#include <ReactCommon/TurboModuleManagerDelegate.h>
#include <fbjni/fbjni.h>
namespace facebook {
namespace react {
class MyApplicationTurboModuleManagerDelegate : public jni::HybridClass<MyApplicationTurboModuleManagerDelegate, TurboModuleManagerDelegate> {
public:
// Adapt it to the package you used for your Java class.
static constexpr auto kJavaDescriptor =
"Lcom/awesomeproject/MyApplicationTurboModuleManagerDelegate;";
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject>);
static void registerNatives();
std::shared_ptr<TurboModule> getTurboModule(const std::string name, const std::shared_ptr<CallInvoker> jsInvoker) override;
std::shared_ptr<TurboModule> getTurboModule(const std::string name, const JavaTurboModule::InitParams ¶ms) override;
private:
friend HybridBase;
using HybridBase::HybridBase;
};
} // namespace react
} // namespace facebook
MyApplicationTurboModuleManagerDelegate.cpp
#include "MyApplicationTurboModuleManagerDelegate.h"
#include "MyApplicationModuleProvider.h"
namespace facebook {
namespace react {
jni::local_ref<MyApplicationTurboModuleManagerDelegate::jhybriddata> MyApplicationTurboModuleManagerDelegate::initHybrid(jni::alias_ref<jhybridobject>) {
return makeCxxInstance();
}
void MyApplicationTurboModuleManagerDelegate::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", MyApplicationTurboModuleManagerDelegate::initHybrid),
});
}
std::shared_ptr<TurboModule> MyApplicationTurboModuleManagerDelegate::getTurboModule(const std::string name, const std::shared_ptr<CallInvoker> jsInvoker) {
// Not implemented yet: provide pure-C++ NativeModules here.
return nullptr;
}
std::shared_ptr<TurboModule> MyApplicationTurboModuleManagerDelegate::getTurboModule(const std::string name, const JavaTurboModule::InitParams ¶ms) {
return MyApplicationModuleProvider(name, params);
}
} // namespace react
} // namespace facebook
MyApplicationModuleProvider.h
#pragma once
#include <memory>
#include <string>
#include <ReactCommon/JavaTurboModule.h>
namespace facebook {
namespace react {
std::shared_ptr<TurboModule> MyApplicationModuleProvider(const std::string moduleName, const JavaTurboModule::InitParams ¶ms);
} // namespace react
} // namespace facebook
MyApplicationModuleProvider.cpp
Please adapt the samplelibrary.h
import to match the same library name you provided when building the apps.
This is the C++ generated file that is created by the codegen.
Here you can also specify more than one provider, should you have more than one TurboModule. Specifically in this example we look for a TurboModule from samplelibrary
(the one we specified) and we fallback to the rncore
Module Provider (containing all the Core modules).
#include "MyApplicationModuleProvider.h"
#include <rncore.h>
#include <samplelibrary.h>
namespace facebook {
namespace react {
std::shared_ptr<TurboModule> MyApplicationModuleProvider(const std::string moduleName, const JavaTurboModule::InitParams ¶ms) {
auto module = samplelibrary_ModuleProvider(moduleName, params);
if (module != nullptr) {
return module;
}
return rncore_ModuleProvider(moduleName, params);
}
} // namespace react
} // namespace facebook
OnLoad.cpp
#include <fbjni/fbjni.h>
#include "MyApplicationTurboModuleManagerDelegate.h"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
return facebook::jni::initialize(vm, [] {
facebook::react::MyApplicationTurboModuleManagerDelegate::registerNatives();
});
}
6. Enable the useTurboModules
flag in your Application onCreate
Now you can finally enable the TurboModule
support in your Application. To do so, you need to turn on the useTurboModule
flag inside your Application onCreate
method.
- Java
- Kotlin
public class MyApplication extends Application implements ReactApplication {
@Override
public void onCreate() {
ReactFeatureFlags.useTurboModules = true;
//...
}
}
class MyApplication : Application(), ReactApplication {
override fun onCreate() {
ReactFeatureFlags.useTurboModules = true
// ...
}
}
It’s now time to run again your Android app to verify that everything works correctly:
yarn react-native run-android