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 守护进程,以加快您在 Java/Kotlin 代码上迭代时的构建速度。
创建自定义原生模块文件
第一步是创建 (CalendarModule.java
或 CalendarModule.kt
) Java/Kotlin 文件,放在 android/app/src/main/java/com/your-app-name/
文件夹中(对于 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)
目前,我们不建议这样做,因为同步调用方法可能会产生严重的性能损失,并向原生模块引入与线程相关的错误。此外,请注意,如果您选择启用 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
,首先创建一个名为 (MyAppPackage.java
或 MyAppPackage.kt
) 的新 Java/Kotlin 类,该类实现 ReactPackage
,并将其放在 android/app/src/main/java/com/your-app-name/
文件夹中
然后添加以下内容
- 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 作为替代方案。TurboReactPackage 实现了一个
getModule(String name, ReactApplicationContext rac)
方法,而不是返回实例化原生模块对象的createNativeModules
列表,该方法在需要时创建原生模块对象。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 Bundler 可以监视 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 |
---|---|---|
布尔值 | 布尔值 | ?布尔值 |
布尔值 | 布尔值 | |
双精度浮点数 | 双精度浮点数 | ?数字 |
双精度浮点数 | 数字 | |
字符串 | 字符串 | 字符串 |
回调 | 回调 | 函数 |
Promise | Promise | Promise |
ReadableMap | ReadableMap | 对象 |
ReadableArray | ReadableArray | 数组 |
以下类型当前受支持,但在 TurboModules 中将不受支持。请避免使用它们
- Integer Java/Kotlin -> ?数字
- Float Java/Kotlin -> ?数字
- int Java -> 数字
- float Java -> 数字
对于上面未列出的参数类型,您需要自己处理转换。例如,在 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,而原生模块方法调用的倒数第二个参数(如果它是函数)将被视为 failureCallback。
- 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}`);
},
);
};
另一个需要注意的重要细节是,原生模块方法只能调用一个回调一次。这意味着您可以调用 successCallback 或 failureCallback,但不能同时调用两者,并且每个回调最多只能调用一次。但是,原生模块可以存储回调并在以后调用它。
有两种方法可以处理回调错误。第一种是遵循 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(但不能同时进行),并且最多只能这样做一次。这意味着您可以调用 successCallback 或 failureCallback,但不能同时调用两者,并且每个回调最多只能调用一次。但是,原生模块可以存储回调并在以后调用它。
此方法的 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
。以下是 JavaScript 中显示的错误消息的示例,该消息来自 Java/Kotlin 中的以下 reject 调用。
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 上,所有原生模块的异步方法都在一个线程上执行。原生模块不应该对它们被调用的线程有任何假设,因为当前的分配将来可能会发生变化。如果需要阻塞调用,则应将繁重的任务分派到内部管理的工作线程,并从那里分发任何回调。