一、为什么要用原生模块?

想象一下,你在用React Native开发一个APP,突然需要调用手机的指纹识别功能。这时候你会发现,React Native官方并没有提供这个API。怎么办呢?这就是原生模块大显身手的时候了。

原生模块就像是React Native和手机原生功能之间的翻译官。它能让JavaScript代码调用Java(Android)或Objective-C/Swift(iOS)写的原生代码。比如:

  • 访问设备硬件(摄像头、传感器等)
  • 使用平台特有的API
  • 复用现有的原生代码库
  • 提升性能关键部分的执行效率

二、搭建开发环境

在开始写代码前,我们需要准备好工具。这里以Android平台为例:

  1. 确保安装了Android Studio
  2. 配置好Java开发环境
  3. React Native项目初始化完成

在项目的android目录下,用Android Studio打开原生模块工程。我们将在android/app/src/main/java/com/yourproject/下创建原生模块。

三、创建Android原生模块

让我们创建一个简单的Toast模块,可以在JavaScript中调用显示原生Toast提示。

// 技术栈:React Native + Java

package com.yourproject;

import android.widget.Toast;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class ToastModule extends ReactContextBaseJavaModule {
    // 构造函数
    public ToastModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    // 必须重写getName方法,返回模块名称
    @Override
    public String getName() {
        return "ToastExample";
    }

    // 暴露给JavaScript的方法
    @ReactMethod
    public void show(String message, int duration) {
        Toast.makeText(getReactApplicationContext(), message, duration).show();
    }
}

这个模块做了三件事:

  1. 继承ReactContextBaseJavaModule
  2. 通过getName()定义模块名称
  3. 用@ReactMethod注解暴露show方法给JS

四、注册原生模块

创建完模块后,还需要注册它才能使用。创建一个Package类:

// 技术栈:React Native + Java

package com.yourproject;

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 CustomToastPackage implements ReactPackage {
    // 注册原生模块
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new ToastModule(reactContext));
        return modules;
    }

    // 注册原生视图(本例不需要)
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

最后,在MainApplication.java中添加这个Package:

// 技术栈:React Native + Java

@Override
protected List<ReactPackage> getPackages() {
    List<ReactPackage> packages = new PackageList(this).getPackages();
    packages.add(new CustomToastPackage()); // 添加我们的Package
    return packages;
}

五、在JavaScript中调用

现在,我们可以在JS代码中使用这个模块了:

// 技术栈:React Native + JavaScript

import { NativeModules } from 'react-native';

// 获取原生模块
const { ToastExample } = NativeModules;

// 调用原生方法
ToastExample.show('Hello from Native!', ToastExample.LONG);

为了让调用更方便,我们可以封装一下:

// 技术栈:React Native + JavaScript

import { NativeModules, Platform } from 'react-native';

const Toast = {
    show: (message) => {
        NativeModules.ToastExample.show(
            message,
            Platform.OS === 'android' ? 1 : 0 // 0:SHORT, 1:LONG
        );
    }
};

// 使用
Toast.show('This is a toast message!');

六、处理回调与Promise

原生模块不仅能接收参数,还能返回结果。我们来看两种方式:

  1. 使用回调:
// 技术栈:React Native + Java

@ReactMethod
public void getDeviceInfo(Callback successCallback) {
    String model = Build.MODEL;
    String brand = Build.BRAND;
    successCallback.invoke(model, brand);
}

JS端调用:

ToastExample.getDeviceInfo((model, brand) => {
    console.log(`Model: ${model}, Brand: ${brand}`);
});
  1. 使用Promise:
// 技术栈:React Native + Java

@ReactMethod
public void checkPermission(Promise promise) {
    try {
        boolean granted = checkPermissionInNative();
        promise.resolve(granted);
    } catch (Exception e) {
        promise.reject("PERMISSION_ERROR", e.getMessage());
    }
}

JS端调用:

ToastExample.checkPermission()
    .then(granted => console.log(granted))
    .catch(error => console.error(error));

七、处理线程问题

React Native的JS代码运行在JS线程,而原生模块默认运行在UI线程。对于耗时操作,我们需要特别注意:

// 技术栈:React Native + Java

@ReactMethod
public void doHeavyWork(final Promise promise) {
    // 创建新线程执行耗时任务
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // 模拟耗时操作
                Thread.sleep(3000);
                promise.resolve("Work done!");
            } catch (Exception e) {
                promise.reject("WORK_ERROR", e.getMessage());
            }
        }
    }).start();
}

八、跨平台兼容处理

虽然我们以Android为例,但实际开发中通常需要同时支持iOS。建议:

  1. 保持两端API一致
  2. 在JS层处理平台差异
  3. 为每个平台创建对应的原生模块

iOS端的Objective-C实现示例:

// 技术栈:React Native + Objective-C

#import "ToastModule.h"
#import <React/RCTLog.h>

@implementation ToastModule

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(show:(NSString *)message duration:(NSInteger)duration) {
    dispatch_async(dispatch_get_main_queue(), ^{
        UIAlertController *alert = [UIAlertController 
            alertControllerWithTitle:nil
            message:message
            preferredStyle:UIAlertControllerStyleAlert];
        
        [self presentViewController:alert animated:YES completion:nil];
        
        dispatch_after(duration, dispatch_get_main_queue(), ^{
            [alert dismissViewControllerAnimated:YES completion:nil];
        });
    });
}

@end

九、调试与排错技巧

开发原生模块时,调试可能会有点挑战:

  1. 使用Android Studio的Logcat查看日志
  2. 在Java代码中添加日志:
Log.d("ToastModule", "Showing toast: " + message);
  1. 在JS端捕获错误:
try {
    await NativeModules.ToastExample.doSomething();
} catch (e) {
    console.error('Native module error:', e);
}
  1. 使用adb logcat过滤日志:
adb logcat ReactNative:V *:S

十、性能优化建议

  1. 避免频繁的跨语言调用
  2. 批量处理数据而不是多次调用
  3. 对于大量数据传输,考虑使用直接内存访问
  4. 将计算密集型任务放在原生端

例如,处理图片时:

// 技术栈:React Native + Java

@ReactMethod
public void processImage(String uri, Promise promise) {
    // 在原生端完成所有图像处理
    Bitmap bitmap = BitmapFactory.decodeFile(uri);
    Bitmap processed = applyFilters(bitmap);
    String resultUri = saveToFile(processed);
    promise.resolve(resultUri);
}

十一、实际应用场景

原生模块在以下场景特别有用:

  1. 支付SDK集成(微信、支付宝等)
  2. 生物识别认证(指纹、面容)
  3. 硬件功能调用(蓝牙、NFC)
  4. 高性能计算(图像处理、视频编码)
  5. 使用第三方原生库

十二、技术优缺点分析

优点:

  • 突破React Native的功能限制
  • 复用现有原生代码
  • 提升关键路径性能
  • 访问平台特有功能

缺点:

  • 增加维护成本(需要维护多平台代码)
  • 跨语言调用有性能开销
  • 调试复杂度增加
  • 需要原生开发知识

十三、注意事项

  1. 数据类型转换要小心:

    • JS的number对应Java的double
    • JS的Array对应Java的ReadableArray
    • JS的Object对应Java的ReadableMap
  2. 线程安全:

    • UI操作必须在主线程
    • 耗时操作应该在工作线程
  3. 内存管理:

    • 注意原生对象的生命周期
    • 避免内存泄漏
  4. 版本兼容:

    • 考虑不同React Native版本的API变化
    • 测试不同Android/iOS版本的兼容性

十四、总结

原生模块是React Native强大扩展能力的核心。通过桥接原生代码,我们可以突破框架限制,访问平台特有功能。虽然开发过程比纯JS复杂一些,但对于需要深度集成原生功能的场景,这是必不可少的技能。

记住几个关键点:

  1. 定义清晰的API接口
  2. 处理好线程问题
  3. 注意数据类型转换
  4. 做好错误处理
  5. 保持跨平台一致性

掌握了原生模块开发,你的React Native应用将获得无限可能!