Android 原生模块
原生模块和原生组件是我们传统架构中使用的稳定技术。 当新架构稳定后,它们将被弃用。新架构使用TurboModule和Fabric 组件来实现类似的功能。
欢迎来到 Android 的原生模块。请先阅读 原生模块简介 以了解原生模块的基本概念。
创建一个 Calendar 原生模块
在以下指南中,你将创建一个名为 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
)。该 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
类。对于安卓系统,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 工具包中导入并使用[Log](https:// developer.android.com/reference/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 将使你的应用拥有更快的启动时间,但目前编写起来有些麻烦。所以如果你选择使用 TurboReactPackage,请小心谨慎。
要注册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() {
@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 调用了它的原生方法。您可以继续学习诸如原生模块方法可用的参数类型以及如何设置回调和承诺之类的内容。
超越日历原生模块
更好的原生模块导出
从上面的 NativeModules
导入您的原生模块是有点麻烦的。
为了让您原生模块的消费者无需每次都这样访问您的原生模块,您可以为该模块创建一个 JavaScript 包装器。创建一个名为 CalendarModule.js
的新 JavaScript 文件,其中包含以下内容:
/**
* 这将原生 CalendarModule 模块作为 JS 模块暴露。它有一个名为 'createCalendarEvent' 的函数,该函数接受以下参数:
* 1. String name: 表示事件名称的字符串
* 2. String location: 表示事件位置的字符串
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
export default CalendarModule;
此 JavaScript 文件也将成为您添加任何 JavaScript 端功能的绝佳位置。例如,如果您使用 TypeScript 等类型系统,可以在此处为您的原生模块添加类型注释。虽然 React Native 目前还不支持从原生到 JS 的类型安全性,但您所有的 JS 代码都将是类型安全的。这样做也将使您将来更容易切换到类型安全的原生模块。下面是为 CalendarModule 添加类型安全性的示例:
/**
* 这个模块以 JS 模块的方式暴露了原生 CalendarModule。它有一个名为 "createCalendarEvent" 的函数,接受以下两个参数:
*
* 1. String name: 代表事件名称的字符串
* 2. String location: 代表事件地点的字符串
*/
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 |
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
}
}