Android 原生模块
原生模块和原生组件是我们通过旧版架构使用的稳定技术。当新版架构稳定后,它们将被弃用。新版架构使用 Turbo Native Module 和 Fabric Native Components 来实现类似的功能。
欢迎来到 Android 原生模块。请先阅读 原生模块简介,了解原生模块是什么。
创建日历原生模块
在接下来的指南中,您将创建一个名为 CalendarModule 的原生模块,它允许您从 JavaScript 访问 Android 的日历 API。最终,您将能够从 JavaScript 调用 CalendarModule.createCalendarEvent('Dinner Party', 'My House');,从而调用一个 Java/Kotlin 方法来创建日历事件。
设置
首先,在 Android Studio 中打开您的 React Native 应用程序内的 Android 项目。您可以在 React Native 应用内的以下位置找到您的 Android 项目:

我们建议使用 Android Studio 来编写原生代码。Android Studio 是一个专为 Android 开发而构建的 IDE,使用它可以帮助您快速解决代码语法错误等小问题。
我们还建议启用 Gradle Daemon,以加速在迭代 Java/Kotlin 代码时的构建速度。
创建自定义原生模块文件
第一步是在 android/app/src/main/java/com/your-app-name/ 文件夹(Kotlin 和 Java 的文件夹相同)内创建 CalendarModule.java 或 CalendarModule.kt Java/Kotlin 文件。这个 Java/Kotlin 文件将包含您的原生模块 Java/Kotlin 类。

然后添加以下内容:
- Java
- Kotlin
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);
}
}
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
class CalendarModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {...}
如您所见,您的 CalendarModule 类继承自 ReactContextBaseJavaModule 类。对于 Android,Java/Kotlin 原生模块被编写成继承 ReactContextBaseJavaModule 类并实现 JavaScript 所需功能的类。
值得注意的是,技术上 Java/Kotlin 类只需要继承 BaseJavaModule 类或实现 NativeModule 接口即可被 React Native 视为原生模块。
然而,我们建议您使用上面所示的 ReactContextBaseJavaModule。ReactContextBaseJavaModule 提供了对 ReactApplicationContext (RAC) 的访问,这对于需要挂钩到 Activity 生命周期方法的原生模块非常有用。使用 ReactContextBaseJavaModule 也能在未来更容易地使您的原生模块类型安全。对于即将发布的原生模块类型安全,React Native 会查看每个原生模块的 JavaScript spec,并生成一个继承自 ReactContextBaseJavaModule 的抽象基类。
模块名称
Android 中的所有 Java/Kotlin 原生模块都需要实现 getName() 方法。此方法返回一个字符串,代表原生模块的名称。然后就可以在 JavaScript 中使用其名称来访问原生模块。例如,在下面的代码片段中,getName() 返回 "CalendarModule"。
- Java
- Kotlin
// add to CalendarModule.java
@Override
public String getName() {
return "CalendarModule";
}
// add to CalendarModule.kt
override fun getName() = "CalendarModule"
然后就可以这样在 JS 中访问原生模块:
const {CalendarModule} = ReactNative.NativeModules;
将原生方法导出到 JavaScript
接下来,您需要在原生模块中添加一个方法,该方法将创建日历事件,并可以从 JavaScript 调用。所有旨在从 JavaScript 调用的原生模块方法都必须用 @ReactMethod 注释。
为 CalendarModule 设置一个 createCalendarEvent() 方法,该方法可以通过 CalendarModule.createCalendarEvent() 从 JS 调用。目前,该方法将接受字符串形式的名称和位置。参数类型选项稍后会介绍。
- Java
- Kotlin
@ReactMethod
public void createCalendarEvent(String name, String location) {
}
@ReactMethod fun createCalendarEvent(name: String, location: String) {}
在方法中添加一个调试日志,以确认当您从应用程序调用它时,它已被调用。下面是如何导入和使用 Android util 包中的 Log 类的示例:
- Java
- Kotlin
import android.util.Log;
@ReactMethod
public void createCalendarEvent(String name, String location) {
Log.d("CalendarModule", "Create event called with name: " + name
+ " and location: " + location);
}
import android.util.Log
@ReactMethod
fun createCalendarEvent(name: String, location: String) {
Log.d("CalendarModule", "Create event called with name: $name and location: $location")
}
完成原生模块的实现并将其集成到 JavaScript 后,您可以按照 这些步骤来查看应用程序的日志。
同步方法
您可以将 isBlockingSynchronousMethod = true 传递给原生方法,以将其标记为同步方法。
- Java
- Kotlin
@ReactMethod(isBlockingSynchronousMethod = true)
@ReactMethod(isBlockingSynchronousMethod = true)
目前,我们不建议这样做,因为同步调用方法可能会带来严重的性能损失,并会给您的原生模块引入与线程相关的错误。此外,请注意,如果您选择启用 isBlockingSynchronousMethod,您的应用程序将无法再使用 Google Chrome 调试器。这是因为同步方法需要 JS VM 与应用程序共享内存。对于 Google Chrome 调试器,React Native 运行在 Google Chrome 的 JS VM 中,并通过 WebSockets 与移动设备进行异步通信。
注册模块 (Android 特定)
编写完原生模块后,需要将其注册到 React Native。为此,您需要将您的原生模块添加到 ReactPackage 中,并将 ReactPackage 注册到 React Native。在初始化期间,React Native 将遍历所有包,然后为每个 ReactPackage 注册其中包含的每个原生模块。
React Native 调用 ReactPackage 上的 createNativeModules() 方法来获取要注册的原生模块列表。对于 Android,如果一个模块没有在 createNativeModules 中实例化并返回,那么它将无法从 JavaScript 中访问。
要将您的原生模块添加到 ReactPackage,首先在 android/app/src/main/java/com/your-app-name/ 文件夹内创建一个实现 ReactPackage 的新 Java/Kotlin 类(命名为 MyAppPackage.java 或 MyAppPackage.kt)。
然后添加以下内容:
- Java
- Kotlin
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;
}
}
package com.your-app-name // replace your-app-name with your app’s name
import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
class MyAppPackage : ReactPackage {
override fun createViewManagers(
reactContext: ReactApplicationContext
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
override fun createNativeModules(
reactContext: ReactApplicationContext
): MutableList<NativeModule> = listOf(CalendarModule(reactContext)).toMutableList()
}
此文件导入了您创建的原生模块 CalendarModule。然后,它在 createNativeModules() 函数中实例化 CalendarModule,并将其作为 NativeModules 列表返回以供注册。如果您以后添加更多原生模块,也可以在此处实例化它们并将其添加到返回的列表中。
值得注意的是,这种注册原生模块的方式会在应用程序启动时立即初始化所有原生模块,这会增加应用程序的启动时间。您可以选择使用 TurboReactPackage 作为替代方案。与返回已实例化原生模块对象的列表的 createNativeModules 不同,TurboReactPackage 实现了一个 getModule(String name, ReactApplicationContext rac) 方法,该方法在需要时创建原生模块对象。TurboReactPackage 目前实现起来有点复杂。除了实现 getModule() 方法外,您还需要实现一个 getReactModuleInfoProvider() 方法,该方法返回一个列表,其中包含包可以实例化的所有原生模块以及实例化它们的函数,示例如 此处。同样,使用 TurboReactPackage 可以加快应用程序的启动速度,但目前编写起来有点麻烦。因此,如果您选择使用 TurboReactPackages,请谨慎操作。
要注册 CalendarModule 包,您必须将 MyAppPackage 添加到 ReactNativeHost 的 getPackages() 方法返回的包列表中。打开您的 MainApplication.java 或 MainApplication.kt 文件,该文件位于以下路径:android/app/src/main/java/com/your-app-name/。
找到 ReactNativeHost 的 getPackages() 方法,并将您的包添加到 getPackages() 返回的包列表中。
- Java
- Kotlin
@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;
}
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
add(MyAppPackage())
}
您现在已成功注册了 Android 的原生模块!
测试您构建的内容
此时,您已经为 Android 上的原生模块搭建了基本框架。通过从 JavaScript 访问原生模块并调用其导出的方法来对其进行测试。
在应用程序中找到一个您想添加对原生模块 createCalendarEvent() 方法调用的地方。下面是一个名为 NewModuleButton 的组件示例,您可以将其添加到您的应用程序中。您可以在 NewModuleButton 的 onPress() 函数中调用原生模块。
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。
import {NativeModules} from 'react-native';
然后,您可以从 NativeModules 中访问 CalendarModule 原生模块。
const {CalendarModule} = NativeModules;
现在您已经可以访问 CalendarModule 原生模块了,您可以调用您的原生方法 createCalendarEvent()。下面将其添加到 NewModuleButton 的 onPress() 方法中:
const onPress = () => {
CalendarModule.createCalendarEvent('testName', 'testLocation');
};
最后一步是重新构建 React Native 应用程序,以便可以使用最新的原生代码(包含您的新原生模块!)。在命令行中,在 React Native 应用程序所在的位置,运行以下命令:
- npm
- Yarn
npm run android
yarn android
迭代构建
在阅读这些指南并迭代您的原生模块时,您需要对应用程序进行一次原生重建,才能从 JavaScript 访问您最新的更改。这是因为您编写的代码位于应用程序的原生部分。虽然 React Native 的 Metro 打包器可以监视 JavaScript 中的更改并即时重建,但它不会对原生代码执行此操作。因此,如果您想测试最新的原生更改,则需要使用上述命令进行重建。
回顾✨
您现在应该能够调用应用程序中原生模块的 createCalendarEvent() 方法。在我们的示例中,这发生在按下 NewModuleButton 时。您可以通过查看在 createCalendarEvent() 方法中设置的日志来确认这一点。您可以按照 这些步骤来查看应用程序的 ADB 日志。然后,您应该能够搜索您的 Log.d 消息(在我们的示例中是“Create event called with name: testName and location: testLocation”)并在每次调用原生模块方法时看到您的消息被记录下来。

此时,您已经创建了一个 Android 原生模块,并从 React Native 应用程序中的 JavaScript 调用了其原生方法。您可以继续阅读以了解更多关于原生模块方法可用的参数类型以及如何设置回调和 Promise 的信息。
超越日历原生模块
更好的原生模块导出
像上面那样通过从 NativeModules 中拉取来导入原生模块有点笨拙。
为了让您的原生模块的使用者不必每次都这样做,您可以创建一个 JavaScript 包装器。创建一个名为 CalendarModule.js 的新 JavaScript 文件,并包含以下内容:
/**
* 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 尚不支持从原生到 JS 的类型安全,但您的所有 JS 代码都将是类型安全的。这样做也使您以后更容易切换到类型安全的原生模块。下面是为 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 文件中,您可以像这样访问原生模块并调用其方法:
import CalendarModule from './CalendarModule';
CalendarModule.createCalendarEvent('foo', 'bar');
这假设您导入 CalendarModule 的位置与 CalendarModule.js 在同一层级。请根据需要更新相对导入。
参数类型
当原生模块方法在 JavaScript 中被调用时,React Native 会将参数从 JS 对象转换为其 Java/Kotlin 对象对应的类型。例如,如果您的 Java 原生模块方法接受一个 double,在 JS 中您需要使用一个数字来调用该方法。React Native 会为您处理转换。下面是原生模块方法支持的参数类型及其映射到的 JavaScript 等效类型列表:
| Java | Kotlin | JavaScript |
|---|---|---|
| Boolean | Boolean | ?boolean |
| 布尔值 | 布尔值 | |
| Double | Double | ?number |
| double | 数字 | |
| String | String | 字符串 |
| Callback | Callback | Function |
| Promise | Promise | Promise |
| ReadableMap | ReadableMap | Object |
| ReadableArray | ReadableArray | Array |
以下类型目前支持,但将来在 TurboModules 中将不再支持。请避免使用它们:
- Integer Java/Kotlin -> ?number
- Float Java/Kotlin -> ?number
- int Java -> number
- float Java -> number
对于上面未列出的参数类型,您需要自己处理转换。例如,在 Android 中,Date 转换不支持开箱即用。您可以像这样在原生方法中自己处理到 Date 类型的转换:
- Java
- Kotlin
String dateFormat = "yyyy-MM-dd";
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
Calendar eStartDate = Calendar.getInstance();
try {
eStartDate.setTime(sdf.parse(startDate));
}
val dateFormat = "yyyy-MM-dd"
val sdf = SimpleDateFormat(dateFormat, Locale.US)
val eStartDate = Calendar.getInstance()
try {
sdf.parse(startDate)?.let {
eStartDate.time = it
}
}
导出常量
原生模块可以通过实现可在 JS 中访问的原生方法 getConstants() 来导出常量。下面将实现 getConstants() 并返回一个包含 DEFAULT_EVENT_NAME 常量的 Map,该常量可以在 JavaScript 中访问:
- Java
- Kotlin
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("DEFAULT_EVENT_NAME", "New Event");
return constants;
}
override fun getConstants(): MutableMap<String, Any> =
hashMapOf("DEFAULT_EVENT_NAME" to "New Event")
然后可以通过在 JS 中调用原生模块的 getConstants 来访问该常量:
const {DEFAULT_EVENT_NAME} = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);
技术上,可以直接在原生模块对象上访问在 getConstants() 中导出的常量。这将不再支持 TurboModules,因此我们鼓励社区切换到上述方法,以避免将来不必要的迁移。
目前常量仅在初始化时导出,因此如果您在运行时更改 getConstants 值,它不会影响 JavaScript 环境。这将在 Turbomodules 中发生变化。使用 Turbomodules 时,getConstants() 将成为一个常规的原生模块方法,每次调用都会命中原生端。
回调
原生模块还支持一种特殊的参数:回调。回调用于将数据从 Java/Kotlin 传递到 JavaScript,以实现异步方法。它们还可以用于从原生端异步执行 JavaScript。
要创建带有回调的原生模块方法,首先导入 Callback 接口,然后向您的原生模块方法添加一个类型为 Callback 的新参数。回调参数有一些细微差别,这些细微差别将很快随着 TurboModules 的发布而得到解决。首先,您的函数参数中最多只能有两个回调:一个 successCallback 和一个 failureCallback。此外,如果一个函数是原生模块方法调用的最后一个参数,则将其视为 successCallback;如果一个函数是原生模块方法调用的倒数第二个参数,则将其视为 failure callback。
- Java
- Kotlin
import com.facebook.react.bridge.Callback;
@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
}
import com.facebook.react.bridge.Callback
@ReactMethod fun createCalendarEvent(name: String, location: String, callback: Callback) {}
您可以在 Java/Kotlin 方法中调用回调,并提供您想要传递给 JavaScript 的任何数据。请注意,您只能将可序列化的数据从原生代码传递到 JavaScript。如果您需要传递回原生对象,可以使用 WriteableMaps;如果您需要使用集合,请使用 WritableArrays。此外,重要的是要强调,回调不会在原生函数完成后立即调用。在早期调用中创建的事件的 ID 传递给了回调。
- Java
- Kotlin
@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
Integer eventId = ...
callBack.invoke(eventId);
}
@ReactMethod
fun createCalendarEvent(name: String, location: String, callback: Callback) {
val eventId = ...
callback.invoke(eventId)
}
然后可以使用以下方式在 JavaScript 中访问此方法:
const onPress = () => {
CalendarModule.createCalendarEvent(
'Party',
'My House',
eventId => {
console.log(`Created a new event with id ${eventId}`);
},
);
};
另一个需要注意的重要细节是,原生模块方法只能调用一次回调。这意味着您可以调用 success callback 或 failure callback,但不能同时调用两者,并且每个回调最多只能调用一次。但是,原生模块可以存储回调并在之后调用它。
有两种处理回调错误的方法。第一种是遵循 Node 的约定,将传递给回调的第一个参数视为错误对象。
- Java
- Kotlin
@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
Integer eventId = ...
callBack.invoke(null, eventId);
}
@ReactMethod
fun createCalendarEvent(name: String, location: String, callback: Callback) {
val eventId = ...
callback.invoke(null, eventId)
}
在 JavaScript 中,您可以检查第一个参数以查看是否传递了错误:
const onPress = () => {
CalendarModule.createCalendarEvent(
'testName',
'testLocation',
(error, eventId) => {
if (error) {
console.error(`Error found! ${error}`);
}
console.log(`event id ${eventId} returned`);
},
);
};
另一种选择是使用 onSuccess 和 onFailure 回调:
- Java
- Kotlin
@ReactMethod
public void createCalendarEvent(String name, String location, Callback myFailureCallback, Callback mySuccessCallback) {
}
@ReactMethod
fun createCalendarEvent(
name: String,
location: String,
myFailureCallback: Callback,
mySuccessCallback: Callback
) {}
然后在 JavaScript 中,您可以为错误和成功响应添加单独的回调:
const onPress = () => {
CalendarModule.createCalendarEvent(
'testName',
'testLocation',
error => {
console.error(`Error found! ${error}`);
},
eventId => {
console.log(`event id ${eventId} returned`);
},
);
};
Promise
原生模块还可以fulfill一个 Promise,这可以简化您的 JavaScript,尤其是在使用 ES2016 的 async/await 语法时。当原生模块 Java/Kotlin 方法的最后一个参数是 Promise 时,其对应的 JS 方法将返回一个 JS Promise 对象。
将上面的代码重构为使用 Promise 而不是回调如下所示:
- Java
- Kotlin
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);
}
}
import com.facebook.react.bridge.Promise
@ReactMethod
fun createCalendarEvent(name: String, location: String, promise: Promise) {
try {
val eventId = ...
promise.resolve(eventId)
} catch (e: Throwable) {
promise.reject("Create Event Error", e)
}
}
与回调类似,原生模块方法可以拒绝或解决(resolve)一个 Promise(但不能两者都做),并且最多只能执行一次。这意味着您可以调用 success callback 或 failure callback,但不能两者都调用,并且每个回调最多只能调用一次。但是,原生模块可以存储回调并在之后调用它。
此方法的 JavaScript 对等体返回一个 Promise。这意味着您可以在 async 函数中使用 await 关键字来调用它并等待其结果:
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);
}
};
reject 方法接受以下参数的各种组合:
- Java
- Kotlin
String code, String message, WritableMap userInfo, Throwable throwable
code: String, message: String, userInfo: WritableMap, throwable: Throwable
有关更多详细信息,您可以在此处找到 Promise.java 接口:here。如果未提供 userInfo,ReactNative 将将其设置为 null。对于其余参数,React Native 将使用默认值。message 参数提供了在错误调用堆栈顶部显示的错误 message。下面是 Java/Kotlin 中以下 reject 调用在 JavaScript 中显示的错误消息示例:
Java/Kotlin reject 调用
- Java
- Kotlin
promise.reject("Create Event error", "Error parsing date", e);
promise.reject("Create Event error", "Error parsing date", e)
Promise 被拒绝时 React Native 应用中的错误消息

发送事件到 JavaScript
原生模块可以在不直接被调用的情况下向 JavaScript 发送事件。例如,您可能希望向 JavaScript 发送一个提醒,即来自原生 Android 日历应用的日历事件即将发生。最简单的方法是使用 RCTDeviceEventEmitter,可以从 ReactContext 获取,如下面的代码片段所示。
- Java
- Kotlin
...
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);
...
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.Arguments
import com.facebook.react.modules.core.DeviceEventManagerModule
...
private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap?) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
private var listenerCount = 0
@ReactMethod
fun addListener(eventName: String) {
if (listenerCount == 0) {
// Set up any upstream listeners or background tasks as necessary
}
listenerCount += 1
}
@ReactMethod
fun removeListeners(count: Int) {
listenerCount -= count
if (listenerCount == 0) {
// Remove upstream listeners, stop unnecessary background tasks
}
}
...
val params = Arguments.createMap().apply {
putString("eventProperty", "someValue")
}
...
sendEvent(reactContext, "EventReminder", params)
JavaScript 模块然后可以通过在 NativeEventEmitter 类上调用 addListener 来注册接收事件。
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 获取 Activity 结果
如果您想从使用 startActivityForResult 启动的 Activity 获取结果,则需要监听 onActivityResult。为此,您必须扩展 BaseActivityEventListener 或实现 ActivityEventListener。前者更受青睐,因为它更能抵御 API 更改。然后,您需要在模块的构造函数中注册监听器,如下所示:
- Java
- Kotlin
reactContext.addActivityEventListener(mActivityResultListener);
reactContext.addActivityEventListener(mActivityResultListener);
现在,您可以通过实现以下方法来监听 onActivityResult:
- Java
- Kotlin
@Override
public void onActivityResult(
final Activity activity,
final int requestCode,
final int resultCode,
final Intent intent) {
// Your logic here
}
override fun onActivityResult(
activity: Activity?,
requestCode: Int,
resultCode: Int,
intent: Intent?
) {
// Your logic here
}
让我们通过实现一个基本的图片选择器来演示这一点。图片选择器将向 JavaScript 公开 pickImage 方法,该方法在调用时将返回图片的路径。
- Java
- Kotlin
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;
}
}
}
class ImagePickerModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
private var pickerPromise: Promise? = null
private val activityEventListener =
object : BaseActivityEventListener() {
override fun onActivityResult(
activity: Activity?,
requestCode: Int,
resultCode: Int,
intent: Intent?
) {
if (requestCode == IMAGE_PICKER_REQUEST) {
pickerPromise?.let { promise ->
when (resultCode) {
Activity.RESULT_CANCELED ->
promise.reject(E_PICKER_CANCELLED, "Image picker was cancelled")
Activity.RESULT_OK -> {
val uri = intent?.data
uri?.let { promise.resolve(uri.toString())}
?: promise.reject(E_NO_IMAGE_DATA_FOUND, "No image data found")
}
}
pickerPromise = null
}
}
}
}
init {
reactContext.addActivityEventListener(activityEventListener)
}
override fun getName() = "ImagePickerModule"
@ReactMethod
fun pickImage(promise: Promise) {
val activity = currentActivity
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist")
return
}
pickerPromise = promise
try {
val galleryIntent = Intent(Intent.ACTION_PICK).apply { type = "image\/*" }
val chooserIntent = Intent.createChooser(galleryIntent, "Pick an image")
activity.startActivityForResult(chooserIntent, IMAGE_PICKER_REQUEST)
} catch (t: Throwable) {
pickerPromise?.reject(E_FAILED_TO_SHOW_PICKER, t)
pickerPromise = null
}
}
companion object {
const val IMAGE_PICKER_REQUEST = 1
const val E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"
const val E_PICKER_CANCELLED = "E_PICKER_CANCELLED"
const val E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER"
const val E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND"
}
}
监听生命周期事件
监听 Activity 的生命周期事件,如 onResume、onPause 等,与 ActivityEventListener 的实现方式非常相似。模块必须实现 LifecycleEventListener。然后,您需要在模块的构造函数中注册一个监听器,如下所示:
- Java
- Kotlin
reactContext.addLifecycleEventListener(this);
reactContext.addLifecycleEventListener(this)
现在,您可以通过实现以下方法来监听 Activity 的生命周期事件:
- Java
- Kotlin
@Override
public void onHostResume() {
// Activity `onResume`
}
@Override
public void onHostPause() {
// Activity `onPause`
}
@Override
public void onHostDestroy() {
// Activity `onDestroy`
}
override fun onHostResume() {
// Activity `onResume`
}
override fun onHostPause() {
// Activity `onPause`
}
override fun onHostDestroy() {
// Activity `onDestroy`
}
线程
到目前为止,在 Android 上,所有原生模块的异步方法都在一个线程上执行。原生模块不应假定它们被调用的线程,因为当前的分配可能会在未来发生变化。如果需要阻塞调用,应将繁重的工作分派到内部管理的辅助线程,并从那里分发任何回调。