文章

RN与原生通信机制深度解析

从旧架构Bridge到新架构JSI,全面解析React Native与原生层通信的四种核心方式及其适用场景。

RN与原生通信机制深度解析

一句话概括

React Native与原生层的通信经历了从Bridge异步队列到JSI同步绑定的演进,理解Native Modules注册与调用、事件传递、回调与Promise四种通信模式,是掌握RN跨端能力的核心。

背景与意义

React Native的”跨端”能力本质上是”JS代码驱动原生UI”的能力。RN应用运行在JS引擎中,但屏幕上渲染的是原生组件——中间的桥梁就是JS与原生层的通信机制。通信的效率直接决定了应用的响应速度,通信的灵活性决定了JS能调用多少系统级能力。

从最早期的Bridge异步消息队列,到新架构中JSI的同步宿主对象绑定,React Native的通信机制走过了一条从”封装原生能力供JS调用”到”JS与原生无感融合”的演进之路。对于RN开发者来说,掌握这几种通信方式,以及它们在业务中的最佳实践,是继基础组件使用之后的第二个能力台阶。

概念与定义

Bridge(桥接层): 旧架构中的核心通信枢纽。Bridge在JS侧和原生侧各维护一个消息队列,两侧通过”序列化→队列→反序列化”的方式进行异步通信。桥梁两侧互不知晓对方的执行上下文。

JSI(JavaScript Interface): 新架构中的核心通信层。JSI向JS引擎暴露C++宿主对象的接口,让JS代码能直接操作C++内存中的对象,实现同步调用。

Native Module(原生模块): 封装原生能力供JS调用的模块。在Android上用Java/Kotlin实现,在iOS上用Objective-C/Swift实现。

Callbacks & Promises: 原生模块向JS侧返回结果的两种方式——Callback是基于函数的回调,Promise是经过封装后支持async/await的调用方式。

RCTDeviceEventEmitter: iOS/Android侧向JS侧发送事件的通道,适用于通知型场景(蓝牙连接状态变化、位置更新等)。

最小示例

一个最简单的原生模块,展示四种通信方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// Android Native Module - CalculatorModule.java
package com.myapp.modules;

import android.util.Log;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.*;

public class CalculatorModule extends ReactContextBaseJavaModule {

    CalculatorModule(ReactApplicationContext context) {
        super(context);
    }

    @Override
    @NonNull
    public String getName() {
        return "Calculator";
    }

    // 方式1:同步返回(仅在旧架构Bridge中走@ReactMethod + isBlockingSynchronousMethod)
    @ReactMethod(isBlockingSynchronousMethod = true)
    public int addSync(int a, int b) {
        return a + b;
    }

    // 方式2:异步回调
    @ReactMethod
    public void addWithCallback(int a, int b, Callback successCallback) {
        int result = a + b;
        successCallback.invoke(result);
    }

    // 方式3:Promise
    @ReactMethod
    public void addWithPromise(int a, int b, Promise promise) {
        try {
            int result = a + b;
            promise.resolve(result);
        } catch (Exception e) {
            promise.reject("CALC_ERROR", e.getMessage());
        }
    }

    // 方式4:事件发送
    @ReactMethod
    public void startAutoIncrement(int intervalMs) {
        new Thread(() -> {
            int count = 0;
            while (count < 10 && !Thread.interrupted()) {
                try {
                    Thread.sleep(intervalMs);
                } catch (InterruptedException e) {
                    break;
                }
                count++;
                WritableMap params = Arguments.createMap();
                params.putInt("count", count);
                getReactApplicationContext()
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit("onCountUpdate", params);
            }
        }).start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// JS侧的调用方式
import { NativeModules, DeviceEventEmitter } from 'react-native';

const { Calculator } = NativeModules;

// 同步调用(仅限isBlockingSynchronousMethod)
const syncResult = Calculator.addSync(3, 5);
console.log(syncResult); // 8

// 异步回调
Calculator.addWithCallback(3, 5, (result) => {
  console.log(result); // 8
});

// Promise
const promiseResult = await Calculator.addWithPromise(3, 5);
console.log(promiseResult); // 8

// 事件监听
useEffect(() => {
  const subscription = DeviceEventEmitter.addListener('onCountUpdate', (event) => {
    console.log('当前计数:', event.count);
  });
  Calculator.startAutoIncrement(1000);
  return () => subscription.remove();
}, []);

核心知识点拆解

1. Native Module的注册与生命周期

Native Module在应用启动时完成注册。在旧架构中,通过Package自动注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Android - Packages注册
public class MyAppPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Arrays.<NativeModule>asList(
            new CalculatorModule(reactContext),
            new ImageCacheModule(reactContext)
        );
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

然后将MyAppPackage添加到MainApplication.javagetPackages列表中:

1
2
3
4
5
6
7
@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
        new MainReactPackage(),
        new MyAppPackage() // 自定义模块
    );
}

Native Module的生命周期与ReactApplicationContext关联。当应用处于前台时,模块存活;应用进入后台,模块仍然存活,但发送的事件可能不会被JS侧处理。

在新架构中,Native Module升级为Turbo Module,通过Codegen自动生成桩代码,并在初始化时通过JSI绑定到JS运行时,实现了懒加载和同步调用。

2. Bridge通信的完整链路

旧架构中,一次JS调用原生方法的完整链路(以调用addWithCallback为例):

1
2
3
4
5
6
7
8
9
10
11
12
JS线程:Calculator.addWithCallback(3, 5, callback)
  → 将调用信息序列化为JSON:{type: "methodCall", module: "Calculator", method: "addWithCallback", args: [3, 5, callbackId]}
  → 推入Bridge的消息发送队列

(Bridge自行调度,等待原生侧拉取)

原生侧(可以是任意线程):
  → 从消息队列中批量拉取消息
  → 反序列化JSON
  → 根据moduleName/methodName找到对应的NativeModule和对应方法
  → 通过反射调用方法
  → 方法执行完毕后,将callback的结果序列化回JS侧

这个过程存在几个性能瓶颈:序列化/反序列化开销、消息队列的批处理延迟、反射调用的性能损失。

3. JSI通信的完整链路(新架构)

新架构中,同样调用原生方法addWithPromise(3, 5)

1
2
3
4
5
JS线程:Calculator.addWithPromise(3, 5)
  → JSI查找Calculator对象(已在初始化时绑定为C++ HostObject)
  → 直接调用HostObject.get("addWithPromise")返回的Function
  → Function的C++实现获取参数,调用原生方法
  → 原生方法返回Promise结果,通过JSI直接写回JS引擎内存

这里的关键改进:无序列化、无队列、无反射。JS引擎直接通过JSI接口操作C++侧的内存。

4. 事件传递的两种模式

从原生到JS(主动通知): 原生侧将事件通过DeviceEventEmitter发送到JS侧。典型场景包括——蓝牙设备连接状态变化、定位更新、后台任务进度通知。

从JS到原生(请求-响应): 通过Method调用实现,JS侧发起请求,原生侧处理后返回结果。Promise方式和Callback方式的本质区别在于:Promise返回单个结果,Callback可以多次回调。

实战案例:实现一个蓝牙设备扫描模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// BluetoothScannerModule.java
public class BluetoothScannerModule extends ReactContextBaseJavaModule {
    private static final String MODULE_NAME = "BluetoothScanner";
    private boolean isScanning = false;

    BluetoothScannerModule(ReactApplicationContext context) {
        super(context);
    }

    @Override
    public String getName() {
        return MODULE_NAME;
    }

    // 启动扫描,持续通过事件返回发现的设备
    @ReactMethod
    public void startScan(ReadableMap filters, Promise promise) {
        if (isScanning) {
            promise.reject("SCANNING", "Already scanning");
            return;
        }
        isScanning = true;
        
        // 使用BluetoothLeScanner进行扫描
        BluetoothLeScanner scanner = getBluetoothAdapter().getBluetoothLeScanner();
        ScanCallback callback = new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                WritableMap device = Arguments.createMap();
                device.putString("name", result.getDevice().getName());
                device.putString("address", result.getDevice().getAddress());
                device.putInt("rssi", result.getRssi());
                
                // 通过事件将设备信息推送到JS侧
                getReactApplicationContext()
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit("onDeviceDiscovered", device);
            }

            @Override
            public void onScanFailed(int errorCode) {
                WritableMap error = Arguments.createMap();
                error.putInt("code", errorCode);
                getReactApplicationContext()
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit("onScanError", error);
            }
        };
        
        scanner.startScan(callback);
        promise.resolve(true);
    }

    @ReactMethod
    public void stopScan() {
        isScanning = false;
        // 停止扫描逻辑...
    }

    private BluetoothAdapter getBluetoothAdapter() {
        BluetoothManager manager = (BluetoothManager) 
            getReactApplicationContext().getSystemService(Context.BLUETOOTH_SERVICE);
        return manager.getAdapter();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// JS侧:使用蓝牙扫描模块
import { NativeModules, DeviceEventEmitter } from 'react-native';

const { BluetoothScanner } = NativeModules;

export function useBluetoothScanner() {
  const [devices, setDevices] = useState([]);
  const [scanning, setScanning] = useState(false);

  useEffect(() => {
    const discoveredSub = DeviceEventEmitter.addListener(
      'onDeviceDiscovered',
      (device) => {
        setDevices(prev => {
          // 去重:如果已存在相同地址的设备则更新,否则新增
          const exists = prev.find(d => d.address === device.address);
          if (exists) {
            return prev.map(d => d.address === device.address ? device : d);
          }
          return [...prev, device];
        });
      }
    );

    const errorSub = DeviceEventEmitter.addListener(
      'onScanError',
      (error) => {
        console.error('扫描错误:', error.code);
        setScanning(false);
      }
    );

    return () => {
      discoveredSub.remove();
      errorSub.remove();
      BluetoothScanner.stopScan();
    };
  }, []);

  const startScan = async (filters = {}) => {
    try {
      setScanning(true);
      await BluetoothScanner.startScan(filters);
    } catch (e) {
      setScanning(false);
      console.error('启动扫描失败:', e);
    }
  };

  return { devices, scanning, startScan, stopScan };
}

底层原理:Bridge消息队列的批处理机制

旧架构Bridge的消息队列是”双端队列”——JS侧和原生侧各有一个消息队列。以Android为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ReactBridge.java(旧架构核心,简化版)
public class ReactBridge {
    private final MessageQueueThread mNativeModulesQueueThread;
    private final Queue<Message> mMessageQueue = new ConcurrentLinkedQueue<>();

    // JS->Native的消息入队
    public void enqueueMessage(Message message) {
        mMessageQueue.add(message);
    }

    // 原生侧批量处理消息
    public void processMessages() {
        // 拉取当前批次的所有消息
        List<Message> batch = new ArrayList<>();
        while (!mMessageQueue.isEmpty()) {
            batch.add(mMessageQueue.poll());
        }
        // 批量处理以避免频繁调用JNI
        for (Message message : batch) {
            processSingleMessage(message);
        }
    }
}

Bridge会按批次处理消息,默认的批处理间隔在16ms左右(与帧率同步)。这意味着在极端情况下,一次JS→原生调用可能延迟一个渲染帧后才能被处理。

新架构的JSI消除了这个消息队列,变为”调用即执行”。但有趣的是,在iOS的新架构过渡方案中(Fabric尚未完全替换所有场景时),仍然存在部分”Fallback”到Bridge的消息。

高频面试题解析

面试题1:@ReactMethod(isBlockingSynchronousMethod = true)@ReactMethod的区别是什么?什么场景下应该使用同步方法?

解析: isBlockingSynchronousMethod = true表示该方法在JS侧是同步的——JS线程会等待原生方法执行完毕才继续后面的代码。这在旧架构中是通过Rn的Message Queue Special机制实现的。

同步方法适合简单快速的计算类操作,比如获取设备信息(设备名称、版本号等)。不适合耗时操作,比如文件读写、网络请求——因为这些操作会直接阻塞JS线程,导致整个应用无响应。

苹果App Store审核指南明确禁止在iOS上使用同步原生调用阻塞JS线程,所以在iOS上使用isBlockingSynchronousMethod需要在主线程之外执行耗时操作。

面试题2:Callback和Promise各有什么优劣,底层实现上有什么区别?

解析: Callback更灵活,支持多次回调(比如进度回调可以连续调用),在旧架构时代是实现流式数据的标准方式。但Callback容易导致回调地狱,而且JS侧的Callback引用如果不释放会导致内存泄漏。

Promise更现代,支持await语法,错误处理更清晰(.catchtry-catch)。底层实现上,RN将Promise封装为com.facebook.react.bridge.Promise接口,原生侧调用resolve/reject方法时,RN框架会自动处理JS侧Promise的状态切换。

建议:新代码优先用Promise,只有在需要多值回调(如进度更新、流式处理)时才使用Callback。

面试题3:如何处理原生侧发送事件时JS侧尚未初始化的问题?

解析: 这是一个常见的时序问题。如果原生模块在onHostResume后立即发送事件,但JS侧的监听器还没有注册好,事件就会丢失。解决方案有三个:

  1. 延迟发送: 给JS侧留出初始化时间(通过Handler.postDelayed
  2. 事件队列缓存: 在原生侧维护一个事件缓存队列,JS侧通过获取缓存来”补捞”丢失的事件
  3. 状态查询模式: 不在原生侧主动推事件,而是由JS侧在合适的时机调用getLatestState来查询原生侧的最新状态。这种”拉”模式比”推”模式更可控

其中方案3在Fabric新架构中被广泛推荐,因为它符合”JS主动查询”的设计理念,避免了事件丢失和消息风暴的问题。

总结与扩展

React Native与原生层的通信机制是它的”生命线”——无论是旧架构的Bridge还是新架构的JSI,其本质都是在”让JS能够调用原生能力”和”让原生能够通知JS侧”。从异步到同步,从序列化到宿主对象绑定,这条演进路线体现了跨端框架对性能极致的追求。

除了这四种核心通信方式,未来值得关注的方向包括:

  • JSI的深度整合: 更多原生SDK(如AR/VR、传感器驱动)将直接通过JSI绑定,无需RN官方维护适配
  • WebAssembly通信: WASM模块通过JSI与原生层互调的实验性方案
  • 原生驱动的状态管理: 状态变更直接在原生侧计算差异,仅将结果同步给JS,减少跨线程通信
本文由作者按照 CC BY 4.0 进行授权