在React Native开发中,与原生平台的通信能力就像一座桥梁,连接着JavaScript世界和原生操作系统。这座桥梁从最初的简单木板(Callback)逐渐升级为钢结构(Promise),最终演变为双向八车道的高速公路(EventEmitter)。我经历过一个电商项目,当时需要在RN页面实时接收原生推送的订单状态变更,最初用Callback实现时各种回调地狱,后来重构为EventEmitter方案后代码简洁度提升了70%。
跨平台通信本质上要解决三个核心问题:调用方向(RN调原生还是原生调RN)、数据格式(如何序列化复杂对象)、线程模型(UI线程与JS线程的交互)。Android和iOS在实现细节上各有特点:
ReactContextBaseJavaModule暴露方法RCTBridgeModule协议Callback就像打电话留言,RN把要说的话和回拨号码(回调函数)一起传给原生端。下面是一个获取设备信息的典型实现:
java复制// Android原生代码
@ReactMethod
public void getDeviceInfo(Callback errorCallback, Callback successCallback) {
try {
WritableMap info = Arguments.createMap();
info.putString("model", Build.MODEL);
info.putInt("apiLevel", Build.VERSION.SDK_INT);
successCallback.invoke(info);
} catch (Exception e) {
errorCallback.invoke(e.getMessage());
}
}
对应的RN调用代码需要注意this绑定问题:
javascript复制import { NativeModules } from 'react-native';
const handleGetInfo = () => {
NativeModules.DeviceModule.getDeviceInfo(
error => console.error(error),
info => setDeviceInfo(info)
);
};
我在金融项目中遇到过典型问题:回调丢失。当RN组件卸载后回调函数仍然被原生模块持有,导致内存泄漏。解决方案是在componentWillUnmount中取消回调:
javascript复制componentWillUnmount() {
if(this.callbackId) {
NativeModules.DeviceModule.cancelCallback(this.callbackId);
}
}
另一个常见问题是线程跳跃。Android原生模块默认不在UI线程执行,如果要做UI操作需要这样处理:
java复制@ReactMethod
public void showToast(final String message) {
getCurrentActivity().runOnUiThread(() ->
Toast.makeText(getReactApplicationContext(), message, Toast.LENGTH_SHORT).show()
);
}
Promise把回调的嵌套结构拉平,就像把杂乱的电线整理成整齐的线缆。改造前面的设备信息获取示例:
java复制@ReactMethod
public void getDeviceInfoPromise(Promise promise) {
try {
WritableMap info = Arguments.createMap();
info.putString("os", "Android");
promise.resolve(info);
} catch (Exception e) {
promise.reject("GET_DEVICE_FAILED", e);
}
}
RN侧使用async/await语法更加清晰:
javascript复制const fetchDeviceInfo = async () => {
try {
const info = await NativeModules.DeviceModule.getDeviceInfoPromise();
setDeviceInfo(info);
} catch (e) {
Alert.alert('Error', e.message);
}
};
对于需要连续调用多个原生方法的场景,Promise链展现出巨大优势。比如先获取位置再查询天气:
javascript复制const fetchWeather = async () => {
const location = await NativeModules.GeoModule.getCurrentLocation();
return await NativeModules.WeatherModule.query(location);
};
在iOS端需要注意线程安全,Promise回调可能在任意线程触发:
objectivec复制RCT_EXPORT_METHOD(fetchData:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程安全操作
NSData *result = [self loadDataSafely];
resolve(result);
});
}
EventEmitter就像安装了对讲机,双方可以随时主动通信。Android端需要继承ReactContextBaseJavaModule并实现事件发送:
java复制public class EventEmitterModule extends ReactContextBaseJavaModule {
public void sendEvent(String eventName, WritableMap params) {
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
iOS端则需要继承RCTEventEmitter:
objectivec复制@implementation EventEmitter
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents {
return @[@"ScanResult"];
}
- (void)sendScanResult:(NSString *)result {
[self sendEventWithName:@"ScanResult" body:@{@"result": result}];
}
@end
在蓝牙开发中,我们需要同时处理设备扫描和连接状态:
javascript复制const eventEmitter = new NativeEventEmitter(NativeModules.BluetoothManager);
useEffect(() => {
const scanSubscription = eventEmitter.addListener(
'ScanResult',
device => updateDeviceList(device)
);
const connectSubscription = eventEmitter.addListener(
'ConnectionState',
state => updateConnection(state)
);
return () => {
scanSubscription.remove();
connectSubscription.remove();
};
}, []);
高频事件(如传感器数据)需要特殊处理:
java复制private long lastEmitTime;
public void onSensorChanged(SensorEvent event) {
long now = System.currentTimeMillis();
if(now - lastEmitTime > 100) { // 每秒最多10次
sendEvent("SensorData", convertToMap(event));
lastEmitTime = now;
}
}
NativeArray| 特性 | Callback | Promise | EventEmitter |
|---|---|---|---|
| 调用方向 | RN→原生 | RN→原生 | 双向 |
| 多次调用支持 | 有限 | 是 | 是 |
| 内存泄漏风险 | 高 | 中 | 低 |
| 代码可读性 | 差 | 好 | 优秀 |
| 适合场景 | 简单操作 | 异步任务 | 实时事件 |
在直播应用中,我们这样设计通信架构:
javascript复制// 直播房间状态管理
class RoomManager {
constructor() {
this.emitter = new NativeEventEmitter(NativeModules.RoomBridge);
}
async enterRoom(roomId) {
const initState = await NativeModules.RoomBridge.enter(roomId);
this.subscription = this.emitter.addListener(
'RoomStateChange',
this.handleStateChange
);
return initState;
}
leaveRoom() {
this.subscription?.remove();
NativeModules.RoomBridge.leave();
}
}
推荐使用自定义Bridge代理来监控通信:
java复制public class DebugBridgeDelegate implements ReactBridgeDelegate {
@Override
public void callJSFunction(
String moduleName,
String functionName,
NativeArray arguments
) {
Log.d("Bridge", "Calling JS: "+moduleName+"."+functionName);
// 原始调用逻辑
}
}
在RN端可以包装NativeModules:
javascript复制const originalModule = NativeModules.RealModule;
const debugModule = new Proxy(originalModule, {
get(target, prop) {
console.log(`Calling native method: ${prop}`);
return target[prop];
}
});