Android 原生模块
原生模块和原生组件是我们稳定技术,由旧架构使用。当新架构稳定后,它们将被弃用。新架构使用 Turbo 原生模块 和 Fabric 原生组件 来实现类似的结果。
欢迎来到 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) 的访问,这对于需要挂钩到活动生命周期方法的原生模块很有用。使用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
,首先在 android/app/src/main/java/com/your-app-name/
文件夹中创建一个名为 (MyAppPackage.java
或 MyAppPackage.kt
) 的新 Java/Kotlin 类,该类实现 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 作为替代方案。TurboReactPackage 实现了一个
getModule(String name, ReactApplicationContext rac)
方法,该方法在需要时创建原生模块对象,而不是返回实例化原生模块对象列表的createNativeModules
。TurboReactPackage 的实现目前稍微复杂一些。除了实现getModule()
方法外,您还需要实现一个getReactModuleInfoProvider()
方法,该方法返回包可以实例化的所有原生模块的列表以及实例化它们的函数,例如 这里。同样,使用 TurboReactPackage 将使您的应用程序具有更快的启动时间,但目前编写起来有点麻烦。因此,如果您选择使用 TurboReactPackages,请谨慎操作。
为了注册 `CalendarModule` 包,你需要在 `ReactNativeHost` 的 `getPackages()` 方法中返回的包列表中添加 `MyAppPackage`。打开你的 `MainApplication.java` 或 `MainApplication.kt` 文件,它位于以下路径:`android/app/src/main/java/com/your-app-name/`。
找到 `ReactNativeHost` 的 `getPackages()` 方法,并将你的包添加到 `getPackages()` 返回的包列表中。
- Java
- Kotlin
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// below MyAppPackage is added to the list of packages returned
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:
// packages.add(new 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 尚未支持 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 |
可读映射 | 可读映射 | 对象 |
可读数组 | 可读数组 | 数组 |
以下类型目前支持,但在 TurboModules 中将不再支持。请避免使用它们
- 整数 Java/Kotlin -> ?数字
- 浮点数 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 中很快被消除。首先,您只能在函数参数中使用两个回调函数 - 一个成功回调函数和一个失败回调函数。此外,如果原生模块方法调用的最后一个参数是函数,则它将被视为成功回调函数,如果倒数第二个参数是函数,则它将被视为失败回调函数。
- 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(但不能同时进行),并且最多只能进行一次。这意味着您可以调用成功回调函数或失败回调函数,但不能同时调用两者,并且每个回调函数最多只能调用一次。但是,原生模块可以存储回调函数并在稍后调用它。
此方法的 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 中的以下拒绝调用在 JavaScript 中显示的错误消息示例。
Java/Kotlin 拒绝调用
- 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 信号事件。例如,您可能希望向 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 模块可以通过 addListener
在 NativeEventEmitter 类上注册以接收事件。
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
启动的活动中获取结果,则需要监听 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"
}
}
监听生命周期事件
监听活动的生命周期事件,例如 onResume
、onPause
等,与 ActivityEventListener
的实现方式非常相似。该模块必须实现 LifecycleEventListener
。然后,您需要在模块的构造函数中注册监听器,如下所示
- Java
- Kotlin
reactContext.addLifecycleEventListener(this);
reactContext.addLifecycleEventListener(this)
现在,您可以通过实现以下方法来监听活动的生命周期事件
- 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 上,所有本机模块异步方法都在一个线程上执行。本机模块不应对它们被调用的线程有任何假设,因为当前分配可能会在将来发生变化。如果需要阻塞调用,则应将繁重的工作分派到内部管理的工作线程,并从那里分发任何回调。