一、为什么需要桥接原生模块
在跨平台开发中,我们经常会遇到一些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();
}, []);
五、技术细节与最佳实践
线程处理:
- iOS的RCT_EXPORT_METHOD默认在主线程执行
- Android的@ReactMethod默认不在UI线程执行
- 对于耗时操作,应该自行管理线程
类型转换:
- 基本类型会自动转换
- 复杂对象需要手动转换
- iOS可以使用RCTConvert类
调试技巧:
- 使用console.log在Native端打印日志
- 在Xcode/Android Studio中设置断点
- 使用React Native的Chrome调试工具
性能优化:
- 减少跨桥通信次数
- 批量处理数据
- 使用原生UI组件替代JS组件
六、应用场景与选择建议
典型应用场景:
- 访问平台特有API(如iOS的ARKit、Android的NFC)
- 复用现有原生代码库
- 性能敏感的操作(如图像处理)
- 后台任务执行
技术优缺点: 优点:
- 扩展React Native功能
- 复用现有代码
- 性能优化
缺点:
- 增加维护成本
- 需要掌握原生开发
- 可能引入平台差异
注意事项:
- 确保原生代码质量,避免内存泄漏
- 处理好线程安全问题
- 考虑向后兼容性
- 充分测试各平台表现
七、总结与展望
桥接原生模块是React Native强大扩展能力的体现。通过合理使用这一特性,我们可以在保持跨平台优势的同时,突破框架限制,实现各种复杂功能。随着React Native生态的成熟,越来越多的常用功能已经可以通过现成的第三方库实现。但在某些特殊场景下,自定义原生模块仍然是不可或缺的解决方案。
未来,随着新架构(如Turbo Modules和Fabric)的推出,原生模块的性能和开发体验还将进一步提升。但核心思想不会改变:在正确的地方做正确的事,让JavaScript和原生代码各司其职,共同构建出色的应用体验。
评论