一、为什么需要桥接原生模块

在跨平台开发中,我们经常会遇到一些React Native本身无法实现的功能。比如调用设备的硬件特性(蓝牙、NFC)、使用平台特有的API(iOS的3D Touch、Android的快捷方式),或者复用现有的原生代码库。这时候就需要通过桥接的方式让JavaScript和原生代码"对话"。

想象一下,你正在开发一个健身App,需要实时获取用户的心率数据。React Native本身没有直接访问心率传感器的API,这就需要我们通过桥接原生模块来实现。

二、iOS原生模块桥接实战

让我们从iOS端开始,假设我们要实现一个获取设备电池电量的功能。以下是完整的Objective-C实现示例:

// BatteryModule.h
#import <React/RCTBridgeModule.h>

@interface BatteryModule : NSObject <RCTBridgeModule>
@end

// BatteryModule.m
#import "BatteryModule.h"
#import <UIKit/UIKit.h>

@implementation BatteryModule

// 必须的宏定义,暴露模块给React Native
RCT_EXPORT_MODULE(BatteryModule);

// 暴露给JS的方法
RCT_EXPORT_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
  // 获取设备电池状态
  UIDevice.currentDevice.batteryMonitoringEnabled = YES;
  float batteryLevel = UIDevice.currentDevice.batteryLevel;
  
  if (batteryLevel < 0) {
    // 获取失败时返回错误
    NSError *error = [NSError errorWithDomain:@"BatteryError" 
                                     code:-1
                                 userInfo:nil];
    reject(@"battery_error", @"Could not get battery level", error);
  } else {
    // 成功时返回电量百分比
    resolve(@(batteryLevel * 100));
  }
}

// 可选:暴露常量
- (NSDictionary *)constantsToExport {
  return @{ @"BATTERY_MODULE_NAME": @"BatteryModule" };
}

@end

对应的JavaScript调用代码:

import { NativeModules } from 'react-native';

const { BatteryModule } = NativeModules;

async function checkBattery() {
  try {
    const level = await BatteryModule.getBatteryLevel();
    console.log(`当前电量: ${level}%`);
  } catch (error) {
    console.error('获取电量失败:', error);
  }
}

三、Android原生模块桥接实战

现在让我们看看Android端的实现。同样以获取电池电量为例,以下是Kotlin实现:

// BatteryModule.kt
package com.yourAppPackage

import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise

class BatteryModule(reactContext: ReactApplicationContext) : 
    ReactContextBaseJavaModule(reactContext) {

    // 模块名称
    override fun getName() = "BatteryModule"

    // 获取电量方法
    @ReactMethod
    fun getBatteryLevel(promise: Promise) {
        val context = reactApplicationContext
        val batteryStatus: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { ifilter ->
            context.registerReceiver(null, ifilter)
        }
        
        batteryStatus?.let {
            val level = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
            val scale = it.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
            val batteryPct = level * 100 / scale.toFloat()
            
            if (level >= 0 && scale > 0) {
                promise.resolve(batteryPct)
            } else {
                promise.reject("BATTERY_ERROR", "无法获取电量信息")
            }
        } ?: run {
            promise.reject("BATTERY_ERROR", "无法获取电池状态")
        }
    }

    // 暴露常量
    override fun getConstants(): Map<String, Any> {
        return hashMapOf("BATTERY_MODULE_NAME" to "BatteryModule")
    }
}

还需要创建一个Package类来注册模块:

// BatteryPackage.kt
package com.yourAppPackage

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class BatteryPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(BatteryModule(reactContext))
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }
}

最后在MainApplication.java中注册:

@Override
protected List<ReactPackage> getPackages() {
  return Arrays.asList(
      new MainReactPackage(),
      new BatteryPackage()  // 添加我们的电池模块
  );
}

四、双向通信与事件监听

有时候我们不仅需要从JS调用原生代码,还需要原生代码主动向JS发送事件。比如监听电池电量变化:

iOS端实现:

// 在BatteryModule.m中添加
RCT_EXPORT_METHOD(startBatteryMonitoring) {
  [UIDevice currentDevice].batteryMonitoringEnabled = YES;
  
  [[NSNotificationCenter defaultCenter] addObserver:self
                                       selector:@selector(batteryLevelDidChange:)
                                           name:UIDeviceBatteryLevelDidChangeNotification
                                         object:nil];
}

- (void)batteryLevelDidChange:(NSNotification *)notification {
  float batteryLevel = [UIDevice currentDevice].batteryLevel;
  [self sendEventWithName:@"batteryLevelChanged" body:@(batteryLevel * 100)];
}

// 必须实现这个方法声明支持的事件
- (NSArray<NSString *> *)supportedEvents {
  return @[@"batteryLevelChanged"];
}

Android端实现:

// 在BatteryModule.kt中添加
private var receiver: BroadcastReceiver? = null

@ReactMethod
fun startBatteryMonitoring() {
    val context = reactApplicationContext
    receiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            intent?.let {
                val level = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
                val scale = it.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
                if (level >= 0 && scale > 0) {
                    val batteryPct = level * 100 / scale.toFloat()
                    sendEvent("batteryLevelChanged", batteryPct)
                }
            }
        }
    }
    
    context.registerReceiver(
        receiver,
        IntentFilter(Intent.ACTION_BATTERY_CHANGED)
    )
}

private fun sendEvent(eventName: String, params: Any) {
    reactApplicationContext
        .getJSModule(RCTDeviceEventEmitter::class.java)
        .emit(eventName, params)
}

@ReactMethod
fun stopBatteryMonitoring() {
    receiver?.let {
        reactApplicationContext.unregisterReceiver(it)
        receiver = null
    }
}

JS端监听代码:

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

const batteryEmitter = new NativeEventEmitter(NativeModules.BatteryModule);

useEffect(() => {
  const subscription = batteryEmitter.addListener(
    'batteryLevelChanged',
    (level) => {
      console.log('电量变化:', level);
    }
  );
  
  return () => subscription.remove();
}, []);

五、技术细节与最佳实践

  1. 线程处理

    • iOS的RCT_EXPORT_METHOD默认在主线程执行
    • Android的@ReactMethod默认不在UI线程执行
    • 对于耗时操作,应该自行管理线程
  2. 类型转换

    • 基本类型会自动转换
    • 复杂对象需要手动转换
    • iOS可以使用RCTConvert类
  3. 调试技巧

    • 使用console.log在Native端打印日志
    • 在Xcode/Android Studio中设置断点
    • 使用React Native的Chrome调试工具
  4. 性能优化

    • 减少跨桥通信次数
    • 批量处理数据
    • 使用原生UI组件替代JS组件

六、应用场景与选择建议

典型应用场景

  1. 访问平台特有API(如iOS的ARKit、Android的NFC)
  2. 复用现有原生代码库
  3. 性能敏感的操作(如图像处理)
  4. 后台任务执行

技术优缺点: 优点:

  • 扩展React Native功能
  • 复用现有代码
  • 性能优化

缺点:

  • 增加维护成本
  • 需要掌握原生开发
  • 可能引入平台差异

注意事项

  1. 确保原生代码质量,避免内存泄漏
  2. 处理好线程安全问题
  3. 考虑向后兼容性
  4. 充分测试各平台表现

七、总结与展望

桥接原生模块是React Native强大扩展能力的体现。通过合理使用这一特性,我们可以在保持跨平台优势的同时,突破框架限制,实现各种复杂功能。随着React Native生态的成熟,越来越多的常用功能已经可以通过现成的第三方库实现。但在某些特殊场景下,自定义原生模块仍然是不可或缺的解决方案。

未来,随着新架构(如Turbo Modules和Fabric)的推出,原生模块的性能和开发体验还将进一步提升。但核心思想不会改变:在正确的地方做正确的事,让JavaScript和原生代码各司其职,共同构建出色的应用体验。