Android 原生模块
Native Module 和 Native Components 是 React Native 旧版架构中使用的稳定技术,但当新架构稳定后,它们将被弃用。新架构将使用 Turbo Native 模块和 Fabric 原生组件来实现类似功能。
欢迎使用 Android 原生模块。建议先阅读原生模块介绍以了解基本概念。
创建日历原生模块
在以下指南中,你将创建一个名为 CalendarModule 的原生模块,用于从 JavaScript 访问 Android 的日历 API。最终,你将能够从 JavaScript 调用 CalendarModule.createCalendarEvent('Dinner Party', 'My House'); 来触发 Java/Kotlin 方法创建日历事件。
设置
首先,在 Android Studio 中打开你的 React Native 应用的 Android 项目。建议使用 Android Studio 编写原生代码,因为它专为 Android 开发构建,能帮助快速解决语法错误等问题。同时,建议启用 Gradle Daemon 以加快构建速度。
创建自定义原生模块文件
第一步是在 android/app/src/main/java/com/your-app-name/ 文件夹中创建 Java 或 Kotlin 文件(例如 CalendarModule.java 或 CalendarModule.kt)。这个文件将包含你的原生模块类。
然后添加以下内容。对于 Java,示例代码如下:
package com.your-apps-package-name;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;
public class CalendarModule extends ReactContextBaseJavaModule {
CalendarModule(ReactApplicationContext context) {
super(context);
}
}
你的 CalendarModule 类扩展了 ReactContextBaseJavaModule。对于 Android,Java/Kotlin 原生模块通常写作扩展 ReactContextBaseJavaModule 的类,以实现 JavaScript 所需功能。虽然技术上只需扩展 BaseJavaModule 或实现 NativeModule 接口,但使用 ReactContextBaseJavaModule 更佳,因为它提供对 ReactApplicationContext 的访问,便于钩入活动生命周期方法,并有助于未来类型安全。
模块名称
所有 Android 原生模块都需要实现 getName() 方法,返回一个字符串表示模块名称。例如,添加以下代码到 CalendarModule.java:
@Override
public String getName() {
return "CalendarModule";
}
在 JavaScript 中,可以通过 ReactNative.NativeModules 访问该模块:
const {CalendarModule} = ReactNative.NativeModules;
将原生方法导出到 JavaScript
接下来,向原生模块添加一个方法,用于创建日历事件并从 JavaScript 调用。所有要从 JavaScript 调用的原生方法必须使用 @ReactMethod 注解。为 CalendarModule 设置 createCalendarEvent() 方法,接收名称和位置作为字符串参数:
@ReactMethod
public void createCalendarEvent(String name, String location) {
}
在方法中添加调试日志以确认调用。导入并使用 Android 的 Log 类:
import android.util.Log;
@ReactMethod
public void createCalendarEvent(String name, String location) {
Log.d("CalendarModule", "Create event called with name: " + name + " and location: " + location);
}
完成原生模块实现并连接到 JavaScript 后,可以查看应用日志验证。
同步方法
你可以通过传递 isBlockingSynchronousMethod = true 将原生方法标记为同步方法,但不建议这样做,因为同步调用可能导致性能损失和线程相关错误,且会禁用 Google Chrome 调试器。
注册模块(Android 特定)
编写原生模块后,需要向 React Native 注册。为此,将原生模块添加到 ReactPackage 并注册该包。首先,在 android/app/src/main/java/com/your-app-name/ 文件夹中创建实现 ReactPackage 的新类(例如 MyAppPackage.java 或 MyAppPackage.kt),内容如下:
package com.your-app-name;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MyAppPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new CalendarModule(reactContext));
return modules;
}
}
这个文件导入并实例化 CalendarModule,在 createNativeModules() 中返回它进行注册。注意,这种方式会在应用启动时预初始化所有原生模块,增加启动时间。可以使用 TurboReactPackage 作为替代,以实现按需初始化,但实现更复杂。
要注册 CalendarModule 包,将 MyAppPackage 添加到 ReactNativeHost 的 getPackages() 方法返回的包列表中。打开 MainApplication.java 或 MainApplication.kt 文件,在 ReactNativeHost 的 getPackages() 方法中添加你的包:
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new MyAppPackage());
return packages;
}
现在,你已成功注册 Android 原生模块。
测试你所构建的内容
至此,你已经为 Android 原生模块设置了基本脚手架。通过在 JavaScript 中访问原生模块并调用其导出方法来测试。例如,创建一个组件 NewModuleButton,在 onPress() 函数中调用原生模块:
import React from 'react';
import {NativeModules, Button} from 'react-native';
const NewModuleButton = () => {
const onPress = () => {
const {CalendarModule} = NativeModules;
CalendarModule.createCalendarEvent('testName', 'testLocation');
};
return (
<Button
title="Click to invoke your native module!"
color="#841584"
onPress={onPress}
/>
);
};
export default NewModuleButton;
最后,重建 React Native 应用以获取最新的原生代码。在命令行中运行相应的构建命令。迭代开发时,需要原生重建来访问更改,因为 Metro 打包器只监视 JavaScript 更改。
回顾
现在,你应该能够在应用中调用 createCalendarEvent() 方法。通过查看 ADB 日志确认,搜索你设置的 Log.d 消息。接下来,可以了解更多关于参数类型、回调和 Promise 的内容。
超越日历原生模块
更好的原生模块导出
通过从 NativeModules 拉取原生模块的方式有些笨拙。为了避免重复,可以创建一个 JavaScript 封装器。新建 CalendarModule.js 文件,内容如下:
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
export default CalendarModule;
如果需要类型安全,可以添加 TypeScript 接口:
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');
参数类型
当在 JavaScript 中调用原生模块方法时,React Native 会自动转换参数类型。支持的类型包括 boolean、double、String、Callback、Promise、ReadableMap、ReadableArray 等,映射到 JavaScript 的 boolean、number、string、function、Promise、object、array。对于其他类型,如 Date,需要手动处理转换。
导出常量
原生模块可以通过实现 getConstants() 方法导出常量,在 JavaScript 中可用。例如:
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("DEFAULT_EVENT_NAME", "New Event");
return constants;
}
在 JavaScript 中访问:
const {DEFAULT_EVENT_NAME} = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);
注意,常量仅在初始化时导出,未来 TurboModules 将使其成为常规方法。
回调
回调用于从 Java/Kotlin 异步传递数据到 JavaScript。在原生方法中,添加 Callback 类型的参数,并使用 @ReactMethod 注解。例如:
import com.facebook.react.bridge.Callback;
@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
Integer eventId = ...;
callBack.invoke(eventId);
}
在 JavaScript 中调用:
CalendarModule.createCalendarEvent('Party', 'My House', eventId => {
console.log(`Created a new event with id ${eventId}`);
});
错误处理可以通过回调的第一个参数传递错误,或使用单独的 onSuccess 和 onFailure 回调。注意,一次只能调用一个回调,且最多调用一次。
Promise
原生模块可以实现 Promise 以简化 JavaScript 代码,特别是使用 async/await 时。当方法的最后一个参数是 Promise 时,对应的 JS 方法返回 Promise 对象。重构回调示例:
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);
}
}
在 JavaScript 中使用:
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);
}
};
Promise 的 reject 方法接受多种参数组合,提供错误消息。
将事件发送到 JavaScript
原生模块可以向 JavaScript 发送事件而不直接调用。使用 RCTDeviceEventEmitter 从 ReactContext 获取并发射事件。例如:
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, WritableMap params) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
}
在 JavaScript 中,使用 NativeEventEmitter 注册监听器:
import {NativeEventEmitter, NativeModules} from 'react-native';
useEffect(() => {
const eventEmitter = new NativeEventEmitter(NativeModules.CalendarModule);
let eventListener = eventEmitter.addListener('EventReminder', event => {
console.log(event.eventProperty);
});
return () => {
eventListener.remove();
};
}, []);
从 startActivityForResult 获取活动结果
如果需要从 startActivityForResult 开始的活动获取结果,需要监听 onActivityResult。通过扩展 BaseActivityEventListener 或实现 ActivityEventListener,并在模块构造函数中注册监听器。例如,实现一个图片选择器:
public class ImagePickerModule extends ReactContextBaseJavaModule {
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);
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;
}
mPickerPromise = promise;
try {
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setType("image/*");
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;
}
}
}
监听生命周期事件
要监听活动生命周期事件(如 onResume、onPause),模块需实现 LifecycleEventListener,并在构造函数中注册监听器。然后实现相应方法处理事件。
线程处理
在 Android 上,所有原生模块异步方法默认在一个线程上执行。原生模块不应假设调用线程,因为未来可能变化。如果需要阻塞调用,应将繁重工作分派到内部管理的工作线程,并从那里分发回调。