Skip to main content

安卓 & iOS 原生模块(Calendar 原生模块)

Calendar 原生模块的使用和深入研究

您想在 React Native 应用程序内的 JavaScript 中访问 iOS/Android 原生日历 API,以创建日历事件。React Native 没有公开与原生日历库通信的 JavaScript API。然而,通过原生模块,您可以编写与原生日历 API 通信的原生代码。然后您可以在 React Native 应用程序中的 JavaScript 里调用该原生代码。

有多种方法可以为您的 React Native 应用程序编写原生模块:

  • 创建一个可在您的 React Native 应用程序中导入的原生库。阅读[创建原生库](local-library-setup)指南以了解更多信息。
  • 直接在您的 React Native 应用程序的 iOS / Android 项目中
  • 作为一个 NPM 包,可以被您/其他 React Native 应用程序作为依赖项安装。

本指南将首先介绍如何直接在 React Native 应用程序内实现原生模块。

安卓原生模块

创建一个名为 CalendarModule 的原生模块,它允许你从 JavaScript 访问 Android 的日历 API。

创建自定义原生模块文件

创建工程npx react-native@latest init AwesomeProject, 在 Android Studio 中打开 React Native 应用程序中的 Android 项目。 第一步是在 android/app/src/main/java/com/your-app-name/ 文件夹中创建 Java/Kotlin 文件(CalendarModule.java 或 CalendarModule.kt)。该 Java/Kotlin 文件将包含您的原生模块 Java/Kotlin 类。

然后添加如下代码:

CalendarModule.java
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);
}
}

从技术上讲,Java/Kotlin 类只需要扩展BaseJavaModule类或实现NativeModule接口,才能被 React Native 视为原生模块。

所以示例中CalendarModule类继承自ReactContextBaseJavaModule类。 ReactContextBaseJavaModule提供了ReactApplicationContext(RAC)的访问权限,这对于需要挂钩到活动生命周期方法的原生模块非常有用。

模块名称

所有 Android 平台上的 Java/Kotlin 原生模块都需要实现 getName() 方法。该方法返回一个字符串,代表了原生模块的名称。这样,原生模块就可以通过其名称在 JavaScript 中被访问。例如,在下面的代码片段中,getName() 返回 "CalendarModule"。

CalendarModule.java
// add to CalendarModule.java
@Override
public String getName() {
return "CalendarModule";
}

可以通过以下方式在 JS 中访问原生模块:

const {CalendarModule} = ReactNative.NativeModules;

向 JavaScript 导出原生方法

在原生模块中添加一个可以创建日历事件的方法。所有打算从 JavaScript 调用的原生模块方法都必须使用@ReactMethod进行注解。

CalendarModule设置一个方法createCalendarEvent(),可以通过CalendarModule.createCalendarEvent()在 JS 中调用。

CalendarModule.java
import android.util.Log;
@ReactMethod
public void createCalendarEvent(String name, String location) {
// 添加一条调试日志以确认它已被调用。
Log.d("CalendarModule", "Create event called with name: " + name
+ " and location: " + location);
}

同步方法

您可以将 isBlockingSynchronousMethod = true 传递给原生方法,将其标记为同步方法。

@ReactMethod(isBlockingSynchronousMethod = true)

我们并不建议这么做,因为同步的方式调用方法可能会带来严重的性能损失,还可能会给你的原生模块引入与线程相关的 bug,并且你的应用程序将无法再使用 Google Chrome 调试器。

在 Android 上注册模块(将原生模块添加到 ReactPackage 中)

编写完原生模块需要将其注册到 React Native 中。在初始化过程中,React Native 会遍历所有包,并对于每个 ReactPackage,注册其中的原生模块。

React Native 会调用 ReactPackagecreateNativeModules() 方法,以获取要注册的原生模块列表。只有注册后的模块才能在js中调用。

要将你的原生模块添加到 ReactPackage 中, 在 android/app/src/main/java/com/your-app-name/ 文件夹中创建一个新的 Java/Kotlin 类(MyAppPackage.java 或 MyAppPackage.kt),并实现 ReactPackage 接口:

MyAppPackage.java
package com.awesomeproject;  // 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;
}
}

这个文件导入了原生模块CalendarModule,然后在createNativeModules()函数中实例化了CalendarModule并将其作为NativeModules列表返回以便注册。如果将来你添加更多原生模块,也在这里实例化它们并添加到返回的列表中。

info

这种注册原生模块的方式会在应用启动时初始化所有原生模块,增加应用的启动时间。你可以使用TurboReactPackage作为替代方案。TurboReactPackage 实现了一个getModule(String name, ReactApplicationContext rac)方法,在需要时创建原生模块对象。目前实现 TurboReactPackage 有点复杂。除了实现getModule()方法外,你还必须实现一个getReactModuleInfoProvider()方法. 此方法不在这里演示,有示例 参考

打开MainApplication.javaMainApplication.kt文件,路径:android/app/src/main/java/com/your-app-name/MainApplication。 将MyAppPackage添加到 ReactNativeHostgetPackages()方法返回的包列表中注册CalendarModule包。

  @Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new MyAppPackage());
return packages;
}

您已为 Android 原生模块搭建了基本的脚手架。通过在 JavaScript 中访问原生模块并调用它导出的方法来测试一下。

测试已构建的内容

找一个调用原生模块 createCalendarEvent()的位置。下面是一个示例组件 NewModuleButton, 您可以在 NewModuleButtononPress()函数中调用原生模块。

NewModuleButton.tsx
import React from 'react';
// 为了从 JavaScript 访问您的原生模块,您需要先从 React Native 导入NativeModules
import { NativeModules, Button } from 'react-native';

const NewModuleButton = () => {
// 从NativeModules访问CalendarModule原生模块
const { CalendarModule } = NativeModules;

const onPress = () => {
// 调用您的原生方法createCalendarEvent()
CalendarModule.createCalendarEvent('testName', 'testLocation');
};

return (
<Button
title="Click to invoke your native module!"
color="#841584"
onPress={onPress}
/>
);
};

export default NewModuleButton;

虽然 React Native 的 metro bundler 可以监视 JavaScript 中的更改并为您实时重建,但它不会对原生代码进行操作。因此,如果您想测试最新的原生更改,需要使用如下命令进行重建。

最后执行
npm run android
warning

安卓原生日志我们看不到,解决方式是先命令行执行下面指令,打开memo npx react-native start

android studio执行debug,从里面的Logcat查看原生Log日志。

安卓原生模块高级用法

优化导入方式

上面的 NativeModules 导入您的原生模块是有点麻烦的。更好的实现方式是为该模块创建一个 JavaScript 包装器。

可以使用js文件CalendarModule.js

CalendarModule.js
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
export default CalendarModule;

但是js非类型安全,所以建议使用ts

CalendarModule.ts
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
interface CalendarInterface {
createCalendarEvent(name: string, location: string): void;
}
export default CalendarModule as CalendarInterface;

然后在使用的地方就可以导入使用了

import CalendarModule from './CalendarModule';
CalendarModule.createCalendarEvent('foo''bar');

更深入的探索原生交互,请参考官网安卓超越日历原生模块

iOS 原生模块

我们的目标就是在 Javascript 中可以访问到 iOS 的日历功能。

一个“原生模块”就是一个实现了“RCTBridgeModule”协议的 Objective-C 类,其中 RCT 是 ReaCT 的缩写。

CalendarManager.h
#import <React/RCTBridgeModule.h>
@interface CalendarManager : NSObject <RCTBridgeModule>
@end

为了实现RCTBridgeModule协议,你的类需要包含RCT_EXPORT_MODULE()宏。这个宏也可以添加一个参数用来指定在 JavaScript 中访问这个模块的名字。如果你不指定,默认就会使用这个 Objective-C 类的名字。如果类名以 RCT 开头,则 JavaScript 端引入的模块名会自动移除这个前缀。

CalendarManager.m
#import "CalendarManager.h"

@implementation CalendarManager

// To export a module named CalendarManager
RCT_EXPORT_MODULE();

// This would name the module AwesomeCalendarManager instead
// RCT_EXPORT_MODULE(AwesomeCalendarManager);

@end

由于iOS模块没有特殊的地方,因此本教程不再写iOS相关教程。 参考官网iOS日历原生模块