React Native Android 原生模块开发指南

Viewed 0

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.javaCalendarModule.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.javaMyAppPackage.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.javaMainApplication.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 发送事件而不直接调用。使用 RCTDeviceEventEmitterReactContext 获取并发射事件。例如:

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 上,所有原生模块异步方法默认在一个线程上执行。原生模块不应假设调用线程,因为未来可能变化。如果需要阻塞调用,应将繁重工作分派到内部管理的工作线程,并从那里分发回调。

0 Answers