100、React Native与Android混合开发:RN集成、Native Module、Bridge通信
说实话,React Native 和 Android 的混合开发,是很多团队绕不开的坎。
我记得刚带项目那会儿,产品经理一拍脑袋说“我们要在现有 App 里嵌入 RN 页面”。我当时心里咯噔一下——不是技术难,而是坑多。但做下来之后发现,只要把集成流程、Native Module 和 Bridge 通信这三块理清楚,其实挺顺的。
今天我就把这套东西掰开揉碎讲给你听。
一、为什么要在 Android 里集成 React Native?
说白了,就是“既要又要”。
- 原生性能:核心页面、复杂动画、硬件交互,还得靠 Android 原生。
- 动态更新:业务页面频繁改版,用 RN 可以热更新,不用等发版。
- 跨平台复用:一套 RN 代码,iOS 和 Android 都能跑,节省人力。
但代价也很明显——你得维护两套通信机制,还要处理生命周期冲突。嗯,这里要注意,不是所有页面都适合 RN。
二、RN 集成到 Android 项目的完整流程
我习惯把集成分成三步:环境准备、依赖注入、页面加载。
2.1 环境准备
首先,你的 Android 项目得支持 React Native。核心依赖就一个:
// app/build.gradle
dependencies {
implementation "com.facebook.react:react-native:0.73.4"
// 如果用了 hermes 引擎
implementation "com.facebook.react:hermes-engine:0.73.4"
}
然后在 settings.gradle 里加上 Maven 仓库:
maven {
url "$rootDir/../node_modules/react-native/android"
}
别忘了,你的项目根目录下要有 package.json 和 node_modules。我一般用 npx react-native init 初始化一个 RN 项目,然后把 Android 部分拷过来。
2.2 创建 React Native 页面
在 Android 里,RN 页面本质上是一个 ReactRootView。你可以在 Activity 或 Fragment 里加载它:
// MyReactActivity.java
public class MyReactActivity extends AppCompatActivity {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
mReactRootView = new ReactRootView(this);
mReactRootView.startReactApplication(
mReactInstanceManager,
"MyReactNativeApp", // 对应 JS 端的 AppRegistry.registerComponent
null
);
setContentView(mReactRootView);
}
}
JS 端只需要注册一个组件:
// index.js
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('MyReactNativeApp', () => App);
ReactInstanceManager 是重量级对象,建议做成单例,不要每个 Activity 都 new 一个。我曾经在项目里踩过这个坑,内存直接飙到 200MB+。
2.3 加载离线 Bundle
生产环境不能走 Metro 开发服务器。你需要把 JS Bundle 打包成 index.android.bundle,放到 assets 目录下:
react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/
然后在 ReactInstanceManager 里指定 setBundleAssetName("index.android.bundle")。
三、Native Module:让 JS 调用原生能力
RN 能调用的原生 API 有限。如果你想用蓝牙、指纹、或者自定义硬件,就得自己写 Native Module。
我举个例子:获取设备唯一标识(不是简单的 IMEI,Android 10 以后拿不到)。
3.1 创建 Native Module 类
// DeviceInfoModule.java
public class DeviceInfoModule extends ReactContextBaseJavaModule {
public DeviceInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "DeviceInfoModule"; // JS 端通过这个名称调用
}
@ReactMethod
public void getDeviceId(Promise promise) {
try {
String deviceId = Settings.Secure.getString(
getReactApplicationContext().getContentResolver(),
Settings.Secure.ANDROID_ID
);
promise.resolve(deviceId);
} catch (Exception e) {
promise.reject("DEVICE_ID_ERROR", e.getMessage());
}
}
}
3.2 注册 Package
// DeviceInfoPackage.java
public class DeviceInfoPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new DeviceInfoModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
然后在 ReactInstanceManager 的 addPackage 里加上:
.addPackage(new DeviceInfoPackage())
3.3 JS 端调用
import { NativeModules } from 'react-native';
const { DeviceInfoModule } = NativeModules;
DeviceInfoModule.getDeviceId()
.then(id => console.log('Device ID:', id))
.catch(err => console.error(err));
@ReactMethod 注解的方法默认跑在 JS 线程。如果你要做耗时操作(比如文件读写),记得用 AsyncTask 或 Handler 切到子线程,否则会卡 UI。
四、Bridge 通信:双向数据流
Bridge 是 RN 和原生之间的“翻译官”。它负责把 JS 的调用转成原生方法,再把结果传回去。
我画了一张图,帮你理解整个通信链路:
4.1 通信原理
Bridge 的核心是 消息队列。JS 端调用原生方法时,不会直接执行,而是把调用信息(模块名、方法名、参数)序列化成 JSON,塞进队列。原生端从队列里取出来执行,再把结果塞回队列,JS 端轮询拿到结果。
所以,Bridge 通信是异步的。你没法在 JS 里同步等待原生返回数据。
4.2 原生向 JS 发送事件
有时候原生需要主动通知 JS,比如蓝牙连接成功、传感器数据变化。这时候可以用 DeviceEventEmitter:
// 原生端
private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
// 调用
WritableMap map = Arguments.createMap();
map.putString("status", "connected");
sendEvent(getReactApplicationContext(), "BluetoothStatus", map);
JS 端监听:
import { DeviceEventEmitter } from 'react-native';
useEffect(() => {
const subscription = DeviceEventEmitter.addListener('BluetoothStatus', (event) => {
console.log('蓝牙状态:', event.status);
});
return () => subscription.remove();
}, []);
4.3 线程模型
这里有个容易踩的坑:Native Module 的方法默认跑在 JS 线程。如果你在方法里直接操作 UI,会崩溃。
我建议这样处理:
- 轻量操作(读 SharedPreferences、取设备信息):直接跑在 JS 线程,没问题。
- 耗时操作(网络请求、文件读写):用
AsyncTask或Handler切到子线程,然后通过Promise回调。 - UI 操作:用
UiThreadUtil.runOnUiThread()切到主线程。
• JS 调用原生:通过 Bridge 异步调用,返回 Promise 或回调。
• 原生调用 JS:通过 DeviceEventEmitter 发送事件。
• 所有通信都是异步的,不要假设执行顺序。
五、避坑指南
做混合开发,坑是真的多。我挑几个最常见的说说。
5.1 生命周期管理
RN 页面在 Android 里是 ReactRootView,它有自己的生命周期。如果你在 Activity 的 onPause 里没有通知 RN,可能会导致内存泄漏。
正确的做法:
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this);
}
}
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, null);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this);
}
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
}
}
5.2 Bundle 加载失败
我曾经遇到过一个问题:打 Release 包后,RN 页面白屏。查了半天发现是 Bundle 文件没打进 APK。
解决方案:在 build.gradle 里确保 assets 目录被包含:
android {
sourceSets {
main {
assets.srcDirs = ['src/main/assets', 'src/main/assets/index.android.bundle']
}
}
}
5.3 多实例冲突
如果你的 App 里有多个 RN 页面,不要每个页面都创建 ReactInstanceManager。做成单例,所有页面共享同一个实例。否则内存会爆炸。
六、总结
React Native 与 Android 混合开发,说白了就是三件事:
- 集成:把 RN 运行时塞进 Android 项目,加载 Bundle 页面。
- Native Module:让 JS 能调用原生能力,比如硬件、系统 API。
- Bridge 通信:JS 和原生之间异步传数据,双向流通。
这套架构一旦搭好,后续的业务迭代会非常快。我现在的团队,新功能从开发到上线,平均只要 3 天——因为核心逻辑在原生,UI 和交互在 RN,各司其职。
嗯,如果你正在做混合开发,希望这篇能帮你少踩几个坑。
公众号:蓝海资料掘金营,微信deep3321