Skip to main content

Android 原生模块

信息

Native Module 和 Native Components 是我们旧版架构使用的稳定技术。当新架构稳定后,它们将被弃用。新架构使用 Turbo Native 模块Fabric 原生组件 来实现类似的结果。

¥Native Module and Native Components are our stable technologies used by the legacy architecture. They will be deprecated in the future when the New Architecture will be stable. The New Architecture uses Turbo Native Module and Fabric Native Components to achieve similar results.

欢迎使用 Android 原生模块。请首先阅读 原生模块介绍 以了解原生模块是什么。

¥Welcome to Native Modules for Android. Please start by reading the Native Modules Intro for an intro to what native modules are.

创建日历原生模块

¥Create a Calendar Native Module

在以下指南中,你将创建一个原生模块 CalendarModule,它允许你从 JavaScript 访问 Android 的日历 API。最后,你将能够从 JavaScript 调用 CalendarModule.createCalendarEvent('Dinner Party', 'My House');,调用创建日历事件的 Java/Kotlin 方法。

¥In the following guide you will create a native module, CalendarModule, that will allow you to access Android’s calendar APIs from JavaScript. By the end, you will be able to call CalendarModule.createCalendarEvent('Dinner Party', 'My House'); from JavaScript, invoking a Java/Kotlin method that creates a calendar event.

设置

¥Setup

首先,在 Android Studio 中的 React Native 应用中打开 Android 项目。你可以在 React Native 应用中找到你的 Android 项目:

¥To get started, open up the Android project within your React Native application in Android Studio. You can find your Android project here within a React Native app:

Image of opening up an Android project within a React Native app inside of Android Studio.
Image of where you can find your Android project

我们建议使用 Android Studio 编写原生代码。Android studio 是一款专为 Android 开发而构建的 IDE,使用它可以帮助你快速解决代码语法错误等小问题。

¥We recommend using Android Studio to write your native code. Android studio is an IDE built for Android development and using it will help you resolve minor issues like code syntax errors quickly.

我们还建议在你迭代 Java/Kotlin 代码时启用 Gradle 守护进程 以加快构建速度。

¥We also recommend enabling Gradle Daemon to speed up builds as you iterate on Java/Kotlin code.

创建自定义原生模块文件

¥Create A Custom Native Module File

第一步是在 android/app/src/main/java/com/your-app-name/ 文件夹中创建(CalendarModule.javaCalendarModule.kt)Java/Kotlin 文件(该文件夹对于 Kotlin 和 Java 来说是相同的)。此 Java/Kotlin 文件将包含你的原生模块 Java/Kotlin 类。

¥The first step is to create the (CalendarModule.java or CalendarModule.kt) Java/Kotlin file inside android/app/src/main/java/com/your-app-name/ folder (the folder is the same for both Kotlin and Java). This Java/Kotlin file will contain your native module Java/Kotlin class.

Image of adding a class called CalendarModule.java within the Android Studio.
Image of how to add the CalendarModuleClass

然后添加以下内容:

¥Then add the following content:

package com.your-apps-package-name; // replace your-apps-package-name with your app’s package name
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;

public class CalendarModule extends ReactContextBaseJavaModule {
CalendarModule(ReactApplicationContext context) {
super(context);
}
}

正如你所看到的,你的 CalendarModule 类扩展了 ReactContextBaseJavaModule 类。对于 Android,Java/Kotlin 原生模块被编写为扩展 ReactContextBaseJavaModule 并实现 JavaScript 所需功能的类。

¥As you can see, your CalendarModule class extends the ReactContextBaseJavaModule class. For Android, Java/Kotlin native modules are written as classes that extend ReactContextBaseJavaModule and implement the functionality required by JavaScript.

值得注意的是,从技术上讲,Java/Kotlin 类只需要扩展 BaseJavaModule 类或实现 NativeModule 接口即可被 React Native 视为 Native Module。

¥It is worth noting that technically Java/Kotlin classes only need to extend the BaseJavaModule class or implement the NativeModule interface to be considered a Native Module by React Native.

但是我们建议你使用 ReactContextBaseJavaModule,如上所示。ReactContextBaseJavaModule 提供对 ReactApplicationContext (RAC) 的访问,这对于需要钩子到活动生命周期方法的原生模块非常有用。使用 ReactContextBaseJavaModule 还可以让你的原生模块在未来变得类型安全。对于未来版本中即将推出的原生模块类型安全,React Native 会查看每个原生模块的 JavaScript 规范并生成一个扩展 ReactContextBaseJavaModule 的抽象基类。

¥However we recommend that you use ReactContextBaseJavaModule, as shown above. ReactContextBaseJavaModule gives access to the ReactApplicationContext (RAC), which is useful for Native Modules that need to hook into activity lifecycle methods. Using ReactContextBaseJavaModule will also make it easier to make your native module type-safe in the future. For native module type-safety, which is coming in future releases, React Native looks at each native module's JavaScript spec and generates an abstract base class that extends ReactContextBaseJavaModule.

模块名称

¥Module Name

Android 中所有 Java/Kotlin 原生模块都需要实现 getName() 方法。该方法返回一个字符串,它表示原生模块的名称。然后可以使用 JavaScript 的名称来访问原生模块。例如,在下面的代码片段中,getName() 返回 "CalendarModule"

¥All Java/Kotlin native modules in Android need to implement the getName() method. This method returns a string, which represents the name of the native module. The native module can then be accessed in JavaScript using its name. For example, in the below code snippet, getName() returns "CalendarModule".

// add to CalendarModule.java
@Override
public String getName() {
return "CalendarModule";
}

然后可以在 JS 中访问原生模块,如下所示:

¥The native module can then be accessed in JS like this:

const {CalendarModule} = ReactNative.NativeModules;

将原生方法导出到 JavaScript

¥Export a Native Method to JavaScript

接下来,你需要向原生模块添加一个方法,该方法将创建日历事件并可以在 JavaScript 中调用。所有要从 JavaScript 调用的原生模块方法都必须使用 @ReactMethod 进行注释。

¥Next you will need to add a method to your native module that will create calendar events and can be invoked in JavaScript. All native module methods meant to be invoked from JavaScript must be annotated with @ReactMethod.

CalendarModule 设置一个方法 createCalendarEvent(),可以通过 CalendarModule.createCalendarEvent() 在 JS 中调用。目前,该方法将以字符串形式接收名称和位置。参数类型选项将很快介绍。

¥Set up a method createCalendarEvent() for CalendarModule that can be invoked in JS through CalendarModule.createCalendarEvent(). For now, the method will take in a name and location as strings. Argument type options will be covered shortly.

@ReactMethod
public void createCalendarEvent(String name, String location) {
}

在方法中添加调试日志,以确认当你从应用调用该方法时已调用该方法。下面是如何从 Android util 包导入和使用 日志 类的示例:

¥Add a debug log in the method to confirm it has been invoked when you call it from your application. Below is an example of how you can import and use the Log class from the Android util package:

import android.util.Log;

@ReactMethod
public void createCalendarEvent(String name, String location) {
Log.d("CalendarModule", "Create event called with name: " + name
+ " and location: " + location);
}

一旦完成原生模块的实现并将其连接到 JavaScript 中,你就可以按照 这些步骤 查看应用的日志。

¥Once you finish implementing the native module and hook it up in JavaScript, you can follow these steps to view the logs from your app.

同步方法

¥Synchronous Methods

你可以将 isBlockingSynchronousMethod = true 传递给原生方法以将其标记为同步方法。

¥You can pass isBlockingSynchronousMethod = true to a native method to mark it as a synchronous method.

@ReactMethod(isBlockingSynchronousMethod = true)

目前,我们不建议这样做,因为同步调用方法可能会产生严重的性能损失,并向原生模块引入与线程相关的错误。此外,请注意,如果你选择启用 isBlockingSynchronousMethod,你的应用将无法再使用 Google Chrome 调试器。这是因为同步方法需要 JS VM 与应用共享内存。对于 Google Chrome 调试器,React Native 在 Google Chrome 中的 JS VM 内部运行,并通过 WebSocket 与移动设备进行异步通信。

¥At the moment, we do not recommend this, since calling methods synchronously can have strong performance penalties and introduce threading-related bugs to your native modules. Additionally, please note that if you choose to enable isBlockingSynchronousMethod, your app can no longer use the Google Chrome debugger. This is because synchronous methods require the JS VM to share memory with the app. For the Google Chrome debugger, React Native runs inside the JS VM in Google Chrome, and communicates asynchronously with the mobile devices via WebSockets.

注册模块(Android 特定)

¥Register the Module (Android Specific)

一旦编写了原生模块,就需要向 React Native 注册。为此,你需要将原生模块添加到 ReactPackage 并使用 React Native 注册 ReactPackage。在初始化期间,React Native 将循环遍历所有包,并为每个 ReactPackage 注册其中的每个原生模块。

¥Once a native module is written, it needs to be registered with React Native. In order to do so, you need to add your native module to a ReactPackage and register the ReactPackage with React Native. During initialization, React Native will loop over all packages, and for each ReactPackage, register each native module within.

React Native 在 ReactPackage 上调用方法 createNativeModules() 以获得要注册的原生模块列表。对于 Android,如果模块未在 createNativeModules 中实例化和返回,则 JavaScript 将无法使用该模块。

¥React Native invokes the method createNativeModules() on a ReactPackage in order to get the list of native modules to register. For Android, if a module is not instantiated and returned in createNativeModules it will not be available from JavaScript.

要将原生模块添加到 ReactPackage,首先创建一个名为(MyAppPackage.javaMyAppPackage.kt)的新 Java/Kotlin 类,该类在 android/app/src/main/java/com/your-app-name/ 文件夹中实现 ReactPackage

¥To add your Native Module to ReactPackage, first create a new Java/Kotlin Class named (MyAppPackage.java or MyAppPackage.kt) that implements ReactPackage inside the android/app/src/main/java/com/your-app-name/ folder:

然后添加以下内容:

¥Then add the following content:

package com.your-app-name; // replace your-app-name with your app’s name
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MyAppPackage implements ReactPackage {

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}

@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();

modules.add(new CalendarModule(reactContext));

return modules;
}

}

该文件导入你创建的原生模块 CalendarModule。然后,它在 createNativeModules() 函数中实例化 CalendarModule,并将其作为 NativeModules 的列表返回以进行注册。如果你添加更多原生模块,你还可以实例化它们并将它们添加到此处返回的列表中。

¥This file imports the native module you created, CalendarModule. It then instantiates CalendarModule within the createNativeModules() function and returns it as a list of NativeModules to register. If you add more native modules down the line, you can also instantiate them and add them to the list returned here.

值得注意的是,这种注册原生模块的方式会在应用启动时预初始化所有原生模块,这会增加应用的启动时间。你可以使用 TurboReact 包 作为替代方案。TurboReactPackage 实现了 getModule(String name, ReactApplicationContext rac) 方法,该方法可在需要时创建原生模块对象,而不是返回实例化原生模块对象列表的 createNativeModules。TurboReactPackage 目前实现起来有点复杂。除了实现 getModule() 方法之外,你还必须实现 getReactModuleInfoProvider() 方法,该方法返回包可以实例化的所有原生模块的列表以及实例化它们的函数,例如 此处。再次强调,使用 TurboReactPackage 将使你的应用拥有更快的启动时间,但目前编写起来有点麻烦。因此,如果你选择使用 TurboReactPackages,请务必小心。

¥It is worth noting that this way of registering native modules eagerly initializes all native modules when the application starts, which adds to the startup time of an application. You can use TurboReactPackage as an alternative. Instead of createNativeModules, which return a list of instantiated native module objects, TurboReactPackage implements a getModule(String name, ReactApplicationContext rac) method that creates the native module object, when required. TurboReactPackage is a bit more complicated to implement at the moment. In addition to implementing a getModule() method, you have to implement a getReactModuleInfoProvider() method, which returns a list of all the native modules the package can instantiate along with a function that instantiates them, example here. Again, using TurboReactPackage will allow your application to have a faster startup time, but it is currently a bit cumbersome to write. So proceed with caution if you choose to use TurboReactPackages.

要注册 CalendarModule 包,你必须将 MyAppPackage 添加到 ReactNativeHost 的 getPackages() 方法返回的包列表中。打开 MainApplication.javaMainApplication.kt 文件,可以在以下路径中找到该文件:android/app/src/main/java/com/your-app-name/

¥To register the CalendarModule package, you must add MyAppPackage to the list of packages returned in ReactNativeHost's getPackages() method. Open up your MainApplication.java or MainApplication.kt file, which can be found in the following path: android/app/src/main/java/com/your-app-name/.

找到 ReactNativeHost 的 getPackages() 方法并将你的包添加到 getPackages() 返回的包列表中:

¥Locate ReactNativeHost’s getPackages() method and add your package to the packages list getPackages() returns:

@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new MyAppPackage());
return packages;
}

你现在已经成功注册了 Android 的原生模块!

¥You have now successfully registered your native module for Android!

测试你所构建的内容

¥Test What You Have Built

至此,你已经为 Android 中的原生模块设置了基本的脚手架。通过访问原生模块并在 JavaScript 中调用其导出方法来测试这一点。

¥At this point, you have set up the basic scaffolding for your native module in Android. Test that out by accessing the native module and invoking its exported method in JavaScript.

在应用中找到一个要添加对原生模块的 createCalendarEvent() 方法的调用的位置。以下是你可以在应用中添加的组件 NewModuleButton 的示例。你可以在 NewModuleButtononPress() 函数中调用原生模块。

¥Find a place in your application where you would like to add a call to the native module’s createCalendarEvent() method. Below is an example of a component, NewModuleButton you can add in your app. You can invoke the native module inside NewModuleButton's onPress() function.

import React from 'react';
import {NativeModules, Button} from 'react-native';

const NewModuleButton = () => {
const onPress = () => {
console.log('We will invoke the native module here!');
};

return (
<Button
title="Click to invoke your native module!"
color="#841584"
onPress={onPress}
/>
);
};

export default NewModuleButton;

为了从 JavaScript 访问你的原生模块,你需要首先从 React Native 导入 NativeModules

¥In order to access your native module from JavaScript you need to first import NativeModules from React Native:

import {NativeModules} from 'react-native';

然后,你可以从 NativeModules 访问 CalendarModule 原生模块。

¥You can then access the CalendarModule native module off of NativeModules.

const {CalendarModule} = NativeModules;

现在你已经有了可用的 CalendarModule 原生模块,你可以调用原生方法 createCalendarEvent()。下面在 NewModuleButton 中添加到 onPress() 方法中:

¥Now that you have the CalendarModule native module available, you can invoke your native method createCalendarEvent(). Below it is added to the onPress() method in NewModuleButton:

const onPress = () => {
CalendarModule.createCalendarEvent('testName', 'testLocation');
};

最后一步是重建 React Native 应用,以便你可以获得最新的原生代码(以及新的原生模块!)。在 React Native 应用所在的命令行中,运行以下命令:

¥The final step is to rebuild the React Native app so that you can have the latest native code (with your new native module!) available. In your command line, where the react native application is located, run the following:

npm run android

边迭代边构建

¥Building as You Iterate

当你学习这些指南并迭代原生模块时,你将需要对应用进行原生重建,以访问 JavaScript 的最新更改。这是因为你正在编写的代码位于应用的原生部分中。虽然 React Native 的 Metro 打包器可以监视 JavaScript 中的更改并为你即时重建,但它不会对原生代码执行此操作。因此,如果你想测试最新的原生更改,你需要使用上述命令进行重建。

¥As you work through these guides and iterate on your native module, you will need to do a native rebuild of your application to access your most recent changes from JavaScript. This is because the code that you are writing sits within the native part of your application. While React Native’s metro bundler can watch for changes in JavaScript and rebuild on the fly for you, it will not do so for native code. So if you want to test your latest native changes you need to rebuild by using the above command.

回顾 ✨

¥Recap✨

你现在应该能够在应用的原生模块上调用 createCalendarEvent() 方法。在我们的示例中,这是通过按 NewModuleButton 来实现的。你可以通过查看在 createCalendarEvent() 方法中设置的日志来确认这一点。你可以按照 这些步骤 在你的应用中查看 ADB 日志。然后,你应该能够搜索 Log.d 消息(在我们的示例中“创建名为:testName 和位置:testLocation 的事件”),并在每次调用原生模块方法时查看记录的消息。

¥You should now be able to invoke your createCalendarEvent() method on your native module in the app. In our example this occurs by pressing the NewModuleButton. You can confirm this by viewing the log you set up in your createCalendarEvent() method. You can follow these steps to view ADB logs in your app. You should then be able to search for your Log.d message (in our example “Create event called with name: testName and location: testLocation”) and see your message logged each time you invoke your native module method.

Image of logs.
Image of ADB logs in Android Studio

此时,你已经创建了一个 Android 原生模块,并在 React Native 应用中从 JavaScript 调用了其原生方法。你可以继续阅读以了解有关原生模块方法可用的参数类型以及如何设置回调和 promise 等内容的更多信息。

¥At this point you have created an Android native module and invoked its native method from JavaScript in your React Native application. You can read on to learn more about things like argument types available to a native module method and how to setup callbacks and promises.

超越日历原生模块

¥Beyond a Calendar Native Module

更好的原生模块导出

¥Better Native Module Export

像上面那样通过从 NativeModules 中拉出原生模块来导入它有点笨拙。

¥Importing your native module by pulling it off of NativeModules like above is a bit clunky.

为了避免原生模块的使用者每次想要访问原生模块时都需要执行此操作,你可以为该模块创建一个 JavaScript 封装器。创建一个名为 CalendarModule.js 的新 JavaScript 文件,其中包含以下内容:

¥To save consumers of your native module from needing to do that each time they want to access your native module, you can create a JavaScript wrapper for the module. Create a new JavaScript file named CalendarModule.js with the following content:

/**

* This exposes the native CalendarModule module as a JS module. This has a

* function 'createCalendarEvent' which takes the following parameters:

* 1. String name: A string representing the name of the event

* 2. String location: A string representing the location of the event
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
export default CalendarModule;

此 JavaScript 文件也成为你添加任何 JavaScript 端功能的好位置。例如,如果你使用像 TypeScript 这样的类型系统,你可以在此处为原生模块添加类型注释。虽然 React Native 尚不支持 Native 到 JS 类型安全,但所有 JS 代码都将是类型安全的。这样做还可以让你更轻松地切换到类型安全的原生模块。下面是向 CalendarModule 添加类型安全的示例:

¥This JavaScript file also becomes a good location for you to add any JavaScript side functionality. For example, if you use a type system like TypeScript you can add type annotations for your native module here. While React Native does not yet support Native to JS type safety, all your JS code will be type safe. Doing so will also make it easier for you to switch to type-safe native modules down the line. Below is an example of adding type safety to the CalendarModule:

/**

* This exposes the native CalendarModule module as a JS module. This has a

* function 'createCalendarEvent' which takes the following parameters:

* * 1. String name: A string representing the name of the event

* 2. String location: A string representing the location of the event
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
interface CalendarInterface {
createCalendarEvent(name: string, location: string): void;
}
export default CalendarModule as CalendarInterface;

在其他 JavaScript 文件中,你可以访问原生模块并调用其方法,如下所示:

¥In your other JavaScript files you can access the native module and invoke its method like this:

import CalendarModule from './CalendarModule';
CalendarModule.createCalendarEvent('foo', 'bar');

这假设你导入 CalendarModule 的位置与 CalendarModule.js 位于同一层次结构中。请根据需要更新相对导入。

¥This assumes that the place you are importing CalendarModule is in the same hierarchy as CalendarModule.js. Please update the relative import as necessary.

参数类型

¥Argument Types

当在 JavaScript 中调用原生模块方法时,React Native 会将参数从 JS 对象转换为其 Java/Kotlin 对象类似物。例如,如果你的 Java Native Module 方法接受 double,则在 JS 中你需要使用数字来调用该方法。React Native 将为你处理转换。下面是原生模块方法支持的参数类型及其映射到的 JavaScript 等效项的列表。

¥When a native module method is invoked in JavaScript, React Native converts the arguments from JS objects to their Java/Kotlin object analogues. So for example, if your Java Native Module method accepts a double, in JS you need to call the method with a number. React Native will handle the conversion for you. Below is a list of the argument types supported for native module methods and the JavaScript equivalents they map to.

Java科特林JavaScript
布尔值布尔值?布尔值
booleanboolean
双倍的双倍的?数字
doublenumber
字符串字符串string
回调来回调来函数
PromisePromisePromise
ReadableMapReadableMap目的
ReadableArrayReadableArray数组

目前支持以下类型,但 TurboModule 中将不支持。请避免使用它们:

¥The following types are currently supported but will not be supported in TurboModules. Please avoid using them:

  • 整数 Java/Kotlin -> ?number

    ¥Integer Java/Kotlin -> ?number

  • 浮点 Java/Kotlin -> ?number

    ¥Float Java/Kotlin -> ?number

  • int Java -> 数字

    ¥int Java -> number

  • Java 浮点数 -> 数字

    ¥float Java -> number

对于上面未列出的参数类型,你需要自己处理转换。例如,在 Android 中,不支持开箱即用的 Date 转换。你可以自己在原生方法中处理到 Date 类型的转换,如下所示:

¥For argument types not listed above, you will need to handle the conversion yourself. For example, in Android, Date conversion is not supported out of the box. You can handle the conversion to the Date type within the native method yourself like so:

    String dateFormat = "yyyy-MM-dd";
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
Calendar eStartDate = Calendar.getInstance();
try {
eStartDate.setTime(sdf.parse(startDate));
}

导出常量

¥Exporting Constants

原生模块可以通过实现原生方法 getConstants() 来导出常量,该方法在 JS 中可用。下面你将实现 getConstants() 并返回一个包含可在 JavaScript 中访问的 DEFAULT_EVENT_NAME 常量的 Map:

¥A native module can export constants by implementing the native method getConstants(), which is available in JS. Below you will implement getConstants() and return a Map that contains a DEFAULT_EVENT_NAME constant you can access in JavaScript:

@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("DEFAULT_EVENT_NAME", "New Event");
return constants;
}

然后可以通过在 JS 中调用原生模块上的 getConstants 来访问该常量:

¥The constant can then be accessed by invoking getConstants on the native module in JS:

const {DEFAULT_EVENT_NAME} = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);

从技术上讲,可以直接从原生模块对象访问 getConstants() 中导出的常量。TurboModules 将不再支持此功能,因此我们鼓励社区改用上述方法,以避免必要的迁移。

¥Technically it is possible to access constants exported in getConstants() directly off the native module object. This will no longer be supported with TurboModules, so we encourage the community to switch to the above approach to avoid necessary migration down the line.

当前常量仅在初始化时导出,因此如果你在运行时更改 getConstants 值,则不会影响 JavaScript 环境。Turbomodules 将改变这种情况。使用 Turbomodules,getConstants() 将成为常规的原生模块方法,并且每次调用都会命中原生端。

¥That currently constants are exported only at initialization time, so if you change getConstants values at runtime it won't affect the JavaScript environment. This will change with Turbomodules. With Turbomodules, getConstants() will become a regular native module method, and each invocation will hit the native side.

回调

¥Callbacks

原生模块还支持一种独特的参数:回调。回调用于将数据从 Java/Kotlin 传递到 JavaScript 以实现异步方法。它们还可以用于从原生端异步执行 JavaScript。

¥Native modules also support a unique kind of argument: a callback. Callbacks are used to pass data from Java/Kotlin to JavaScript for asynchronous methods. They can also be used to asynchronously execute JavaScript from the native side.

为了创建带有回调的原生模块方法,首先导入 Callback 接口,然后向 Callback 类型的原生模块方法添加一个新参数。回调参数有一些细微差别,很快将通过 TurboModules 消除。首先,函数参数中只能有两个回调 - successCallback 和 failureCallback。此外,原生模块方法调用的最后一个参数(如果是函数)将被视为 successCallback,而原生模块方法调用的倒数第二个参数(如果是函数)将被视为失败回调。

¥In order to create a native module method with a callback, first import the Callback interface, and then add a new parameter to your native module method of type Callback. There are a couple of nuances with callback arguments that will soon be lifted with TurboModules. First off, you can only have two callbacks in your function arguments- a successCallback and a failureCallback. In addition, the last argument to a native module method call, if it's a function, is treated as the successCallback, and the second to last argument to a native module method call, if it's a function, is treated as the failure callback.

import com.facebook.react.bridge.Callback;

@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
}

你可以在 Java/Kotlin 方法中调用回调,提供你想要传递给 JavaScript 的任何数据。请注意,你只能将可序列化的数据从原生代码传递到 JavaScript。如果需要传回原生对象,可以使用 WriteableMaps,如果需要使用集合,则可以使用 WritableArrays。还需要强调的是,回调不会在原生函数完成后立即调用。下面将先前调用中创建的事件 ID 传递给回调。

¥You can invoke the callback in your Java/Kotlin method, providing whatever data you want to pass to JavaScript. Please note that you can only pass serializable data from native code to JavaScript. If you need to pass back a native object you can use WriteableMaps, if you need to use a collection use WritableArrays. It is also important to highlight that the callback is not invoked immediately after the native function completes. Below the ID of an event created in an earlier call is passed to the callback.

  @ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
Integer eventId = ...
callBack.invoke(eventId);
}

然后可以使用以下方法在 JavaScript 中访问该方法:

¥This method could then be accessed in JavaScript using:

const onPress = () => {
CalendarModule.createCalendarEvent(
'Party',
'My House',
eventId => {
console.log(`Created a new event with id ${eventId}`);
},
);
};

需要注意的另一个重要细节是原生模块方法一次只能调用一个回调。这意味着你可以调用成功回调或失败回调,但不能同时调用两者,并且每个回调最多只能调用一次。然而,原生模块可以存储回调并稍后调用它。

¥Another important detail to note is that a native module method can only invoke one callback, one time. This means that you can either call a success callback or a failure callback, but not both, and each callback can only be invoked at most one time. A native module can, however, store the callback and invoke it later.

有两种使用回调处理错误的方法。第一个是遵循 Node 的约定,并将传递给回调的第一个参数视为错误对象。

¥There are two approaches to error handling with callbacks. The first is to follow Node’s convention and treat the first argument passed to the callback as an error object.

  @ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
Integer eventId = ...
callBack.invoke(null, eventId);
}

在 JavaScript 中,你可以检查第一个参数以查看是否传递了错误:

¥In JavaScript, you can then check the first argument to see if an error was passed through:

const onPress = () => {
CalendarModule.createCalendarEvent(
'testName',
'testLocation',
(error, eventId) => {
if (error) {
console.error(`Error found! ${error}`);
}
console.log(`event id ${eventId} returned`);
},
);
};

另一种选择是使用 onSuccess 和 onFailure 回调:

¥Another option is to use an onSuccess and onFailure callback:

@ReactMethod
public void createCalendarEvent(String name, String location, Callback myFailureCallback, Callback mySuccessCallback) {
}

然后在 JavaScript 中,你可以为错误和成功响应添加单独的回调:

¥Then in JavaScript you can add a separate callback for error and success responses:

const onPress = () => {
CalendarModule.createCalendarEvent(
'testName',
'testLocation',
error => {
console.error(`Error found! ${error}`);
},
eventId => {
console.log(`event id ${eventId} returned`);
},
);
};

Promise

原生模块还可以实现 Promise,这可以简化你的 JavaScript,特别是在使用 ES2016 的 异步/等待 语法时。当原生模块 Java/Kotlin 方法的最后一个参数是 Promise 时,其对应的 JS 方法将返回 JS Promise 对象。

¥Native modules can also fulfill a Promise, which can simplify your JavaScript, especially when using ES2016's async/await syntax. When the last parameter of a native module Java/Kotlin method is a Promise, its corresponding JS method will return a JS Promise object.

重构上面的代码以使用 Promise 而不是回调,如下所示:

¥Refactoring the above code to use a promise instead of callbacks looks like this:

import com.facebook.react.bridge.Promise;

@ReactMethod
public void createCalendarEvent(String name, String location, Promise promise) {
try {
Integer eventId = ...
promise.resolve(eventId);
} catch(Exception e) {
promise.reject("Create Event Error", e);
}
}

与回调类似,原生模块方法可以拒绝或解析 promise(但不能两者兼而有之),并且最多只能执行一次。这意味着你可以调用成功回调或失败回调,但不能同时调用两者,并且每个回调最多只能调用一次。然而,原生模块可以存储回调并稍后调用它。

¥Similar to callbacks, a native module method can either reject or resolve a promise (but not both) and can do so at most once. This means that you can either call a success callback or a failure callback, but not both, and each callback can only be invoked at most one time. A native module can, however, store the callback and invoke it later.

此方法的 JavaScript 对应项返回一个 Promise。这意味着你可以在异步函数中使用 await 关键字来调用它并等待其结果:

¥The JavaScript counterpart of this method returns a Promise. This means you can use the await keyword within an async function to call it and wait for its result:

const onSubmit = async () => {
try {
const eventId = await CalendarModule.createCalendarEvent(
'Party',
'My House',
);
console.log(`Created a new event with id ${eventId}`);
} catch (e) {
console.error(e);
}
};

拒绝方法采用以下参数的不同组合:

¥The reject method takes different combinations of the following arguments:

String code, String message, WritableMap userInfo, Throwable throwable

有关更多详细信息,你可以找到 Promise.java 接口 此处。如果未提供 userInfo,ReactNative 会将其设置为 null。对于其余参数,React Native 将使用默认值。message 参数提供错误调用堆栈顶部显示的错误 message。下面是 Java/Kotlin 中拒绝调用的 JavaScript 中显示的错误消息示例。

¥For more detail, you can find the Promise.java interface here. If userInfo is not provided, ReactNative will set it to null. For the rest of the parameters React Native will use a default value. The message argument provides the error message shown at the top of an error call stack. Below is an example of the error message shown in JavaScript from the following reject call in Java/Kotlin.

Java/Kotlin 拒绝调用:

¥Java/Kotlin reject call:

promise.reject("Create Event error", "Error parsing date", e);

当 Promise 被拒绝时,React Native App 中出现错误消息:

¥Error message in React Native App when promise is rejected:

Image of error message in React Native app.
Image of error message

将事件发送到 JavaScript

¥Sending Events to JavaScript

原生模块可以向 JavaScript 触发事件信号,而无需直接调用。例如,你可能希望向 JavaScript 发出提醒信号,提醒你即将发生来自原生 Android 日历应用的日历事件。最简单的方法是使用 RCTDeviceEventEmitter,它可以从 ReactContext 获取,如下面的代码片段所示。

¥Native modules can signal events to JavaScript without being invoked directly. For example, you might want to signal to JavaScript a reminder that a calendar event from the native Android calendar app will occur soon. The easiest way to do this is to use the RCTDeviceEventEmitter which can be obtained from the ReactContext as in the code snippet below.

...
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
...
private void sendEvent(ReactContext reactContext,
String eventName,
@Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}

private int listenerCount = 0;

@ReactMethod
public void addListener(String eventName) {
if (listenerCount == 0) {
// Set up any upstream listeners or background tasks as necessary
}

listenerCount += 1;
}

@ReactMethod
public void removeListeners(Integer count) {
listenerCount -= count;
if (listenerCount == 0) {
// Remove upstream listeners, stop unnecessary background tasks
}
}
...
WritableMap params = Arguments.createMap();
params.putString("eventProperty", "someValue");
...
sendEvent(reactContext, "EventReminder", params);

然后,JavaScript 模块可以在 NativeEventEmitter 类上注册以接收 addListener 的事件。

¥JavaScript modules can then register to receive events by addListener on the NativeEventEmitter class.

import {NativeEventEmitter, NativeModules} from 'react-native';
...
useEffect(() => {
const eventEmitter = new NativeEventEmitter(NativeModules.ToastExample);
let eventListener = eventEmitter.addListener('EventReminder', event => {
console.log(event.eventProperty) // "someValue"
});

// Removes the listener once unmounted
return () => {
eventListener.remove();
};
}, []);

从 startActivityForResult 获取活动结果

¥Getting Activity Result from startActivityForResult

如果你想从使用 startActivityForResult 开始的活动中获得结果,则需要收听 onActivityResult。为此,你必须扩展 BaseActivityEventListener 或实现 ActivityEventListener。前者是首选,因为它对 API 更改更具弹性。然后,你需要在模块的构造函数中注册监听器,如下所示:

¥You'll need to listen to onActivityResult if you want to get results from an activity you started with startActivityForResult. To do this, you must extend BaseActivityEventListener or implement ActivityEventListener. The former is preferred as it is more resilient to API changes. Then, you need to register the listener in the module's constructor like so:

reactContext.addActivityEventListener(mActivityResultListener);

现在你可以通过执行以下方法来收听 onActivityResult

¥Now you can listen to onActivityResult by implementing the following method:

@Override
public void onActivityResult(
final Activity activity,
final int requestCode,
final int resultCode,
final Intent intent) {
// Your logic here
}

让我们实现一个基本的图片选择器来演示这一点。图片选择器将向 JavaScript 公开方法 pickImage,该方法将在调用时返回图片的路径。

¥Let's implement a basic image picker to demonstrate this. The image picker will expose the method pickImage to JavaScript, which will return the path of the image when called.

public class ImagePickerModule extends ReactContextBaseJavaModule {

private static final int IMAGE_PICKER_REQUEST = 1;
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
private static final String E_PICKER_CANCELLED = "E_PICKER_CANCELLED";
private static final String E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER";
private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND";

private Promise mPickerPromise;

private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {

@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
if (requestCode == IMAGE_PICKER_REQUEST) {
if (mPickerPromise != null) {
if (resultCode == Activity.RESULT_CANCELED) {
mPickerPromise.reject(E_PICKER_CANCELLED, "Image picker was cancelled");
} else if (resultCode == Activity.RESULT_OK) {
Uri uri = intent.getData();

if (uri == null) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, "No image data found");
} else {
mPickerPromise.resolve(uri.toString());
}
}

mPickerPromise = null;
}
}
}
};

ImagePickerModule(ReactApplicationContext reactContext) {
super(reactContext);

// Add the listener for `onActivityResult`
reactContext.addActivityEventListener(mActivityEventListener);
}

@Override
public String getName() {
return "ImagePickerModule";
}

@ReactMethod
public void pickImage(final Promise promise) {
Activity currentActivity = getCurrentActivity();

if (currentActivity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}

// Store the promise to resolve/reject when picker returns data
mPickerPromise = promise;

try {
final Intent galleryIntent = new Intent(Intent.ACTION_PICK);

galleryIntent.setType("image/*");

final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image");

currentActivity.startActivityForResult(chooserIntent, IMAGE_PICKER_REQUEST);
} catch (Exception e) {
mPickerPromise.reject(E_FAILED_TO_SHOW_PICKER, e);
mPickerPromise = null;
}
}
}

监听生命周期事件

¥Listening to Lifecycle Events

监听活动的 LifeCycle 事件(例如 onResumeonPause 等)与 ActivityEventListener 的实现方式非常相似。该模块必须实现 LifecycleEventListener。然后,你需要在模块的构造函数中注册一个监听器,如下所示:

¥Listening to the activity's LifeCycle events such as onResume, onPause etc. is very similar to how ActivityEventListener was implemented. The module must implement LifecycleEventListener. Then, you need to register a listener in the module's constructor like so:

reactContext.addLifecycleEventListener(this);

现在你可以通过实现以下方法来监听活动的 LifeCycle 事件:

¥Now you can listen to the activity's LifeCycle events by implementing the following methods:

@Override
public void onHostResume() {
// Activity `onResume`
}
@Override
public void onHostPause() {
// Activity `onPause`
}
@Override
public void onHostDestroy() {
// Activity `onDestroy`
}

螺纹加工

¥Threading

迄今为止,在 Android 上,所有原生模块异步方法都在一个线程上执行。原生模块不应该对它们被调用的线程有任何假设,因为当前的分配将来可能会发生变化。如果需要阻塞调用,则应将繁重的工作分派给内部管理的工作线程,并从那里分发任何回调。

¥To date, on Android, all native module async methods execute on one thread. Native modules should not have any assumptions about what thread they are being called on, as the current assignment is subject to change in the future. If a blocking call is required, the heavy work should be dispatched to an internally managed worker thread, and any callbacks distributed from there.