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/
文件夹内创建 Java/Kotlin 文件(CalendarModule.java
或 CalendarModule.kt
)(Kotlin 和 Java 的文件夹路径相同)。这个 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 规范,并生成一个继承自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)
目前,我们不建议这样做,因为同步调用方法可能会导致严重的性能损失,并给您的原生模块引入与线程相关的 bug。此外,请注意,如果您选择启用 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/
文件夹内创建一个新的 Java/Kotlin 类文件(MyAppPackage.java
或 MyAppPackage.kt
),该类实现 ReactPackage
接口。
然后添加以下内容
- 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”),并在每次调用原生模块方法时看到您的消息被记录。

至此,您已经在您的 React Native 应用程序中创建了一个 Android 原生模块,并从 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 代码都将是类型安全的。这样做也会让您以后更容易切换到类型安全的原生模块。
/**
* 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 中使用 number 类型调用该方法。React Native 会为您处理转换。下面列出了原生模块方法支持的参数类型及其对应的 JavaScript 类型。
Java | Kotlin | JavaScript |
---|---|---|
Boolean | Boolean | ?boolean |
boolean | boolean | |
Double | Double | ?number |
double | number | |
String | 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
}
}
导出常量
原生模块可以通过实现原生方法 getConstants()
来导出常量,该方法可在 JS 中访问。您将在下面实现 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}`);
},
);
};
另一个需要注意的重要细节是,原生模块方法只能调用一个回调,并且只能调用一次。这意味着您只能调用成功回调或失败回调中的一个,不能两者都调用,并且每个回调最多只能调用一次。但是,原生模块可以存储回调并在以后调用它。
使用回调处理错误有两种方法。第一种是遵循 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
原生模块也可以履行 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)
}
}
与回调类似,原生模块方法可以拒绝或解决一个 Promise(但不能同时进行),并且最多只能进行一次。这意味着您只能调用成功回调或失败回调中的一个,不能两者都调用,并且每个回调最多只能调用一次。但是,原生模块可以存储 Promise 并在以后调用它。
这个方法在 JavaScript 中的对应方法返回一个 Promise。这意味着您可以在异步函数中使用 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
接口。如果未提供 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 上,所有原生模块的异步方法都在一个线程上执行。原生模块不应该对它们在哪个线程上被调用有任何假设,因为当前的分配将来可能会发生变化。如果需要进行阻塞调用,繁重的工作应该分派到一个内部管理的 worker 线程,并从那里分发任何回调。