关键词:混合工程 / 启动优化 / JS Split Bundle / 模块化交付 / 工程化

这篇文章并不是一篇“如何集成 React Native”的入门教程,而是一次站在 Android 主工程视角,对大型混合工程如何长期演进的系统性总结。

如果你遇到过下面这些问题,那么这套方案大概率适合你:

  • RN 页面一多,启动越来越慢

  • RN 与 Native 页面栈割裂,维护成本高

  • bundle 越来越大,不敢轻易改启动逻辑

  • CodePush 越用越心虚,线上事故频发

本文将完整拆解一套已在真实项目中验证的工程方案,核心目标只有一个:

让 React Native 在 Android 工程中,具备“模块化交付能力”。
一、问题背景:为什么混合工程一定会“越做越慢”?

在很多团队里,混合工程的演进路径通常是这样的:

最初:Native 首屏 + 少量 RN 页面
中期:RN 页面增加,bundle 变大
后期:启动变慢、页面加载不稳定、不敢动架构

根因并不复杂:

  • 两套 Runtime 叠加(Android + JS)

  • RN 初始化被错误地放进启动关键路径

  • bundle 作为“整体”交付,而不是“模块”交付

如果不在架构层面解决,这些问题只会随业务增长被不断放大。

二、核心设计原则(先给结论)

在深入细节之前,先明确 4 个工程级原则:

  1. 原生首屏永远优先,RN 不得阻塞启动

  2. ReactContext 进程级唯一,只初始化一次

  3. JS 不再是一个 bundle,而是多个业务模块

  4. Native 对 RN 有完整的版本、加载与回退控制权

后面的所有设计,都是围绕这 4 点展开。

三、第一阶段:启动优化(让 RN 不再“拖慢一切”) 3.1 错误示例(很多工程仍在使用)

Application.onCreate() {
initSDKs();
initReactNative(); // ❌ 同步初始化
}

结果只有一个:冷启动被 RN 直接拉垮

3.2 正确姿势:原生首屏 + RN 后台预加载

推荐启动模型:

冷启动
├─ 原生 Activity 首帧渲染
├─ 用户可交互
└─ IdleHandler 中 preload RN

RN 初始化的关键词只有三个:延迟、异步、不可感知

四、第二阶段:JS Split Bundle(从“整体交付”到“模块交付”)

这是整个架构的分水岭

4.1 为什么一定要拆 bundle?

  • 冷启动只需要RN Runtime + 基础能力

  • 业务页面天然是按需使用

  • 一个 5MB 的 bundle,本质上是多个页面的“错误聚合”

4.2 推荐拆包结构

js/
├── base/ // RN runtime / bridge / 全局依赖
├── common/ // 业务无关公共能力
└── pages/
├── home/
└── profile/

base.bundle 只做一件事:让 RN 跑起来。

4.3 依赖规则(必须写进规范)

模块

允许依赖

base

common

pages/*

common

pages/*

❌ pages/*

这一步的本质是:防止 Metro 帮你“偷偷合包”。

五、第三阶段:Native × JS 的强一致性校验

拆包之后,最危险的事情只有一件:

Native 与 JS 版本不一致,却仍然尝试加载。
5.1 bundle manifest(强烈建议)

{
"versionCode": 88020,
"bundles": {
"base": "base.bundle",
"home": "home.bundle"
}
}
5.2 Native 启动时必须校验
  • versionCode 不一致 → 不加载 RN

  • bundle 缺失 → 回退 Native

宁可失败,也不要黑屏。

六、第四阶段:页面级 preload(体验质变点)

有了拆包之后,RN 页面加载就不再是“不可控的大操作”。

6.1 推荐 preload 策略

进入原生首页
├─ preload base.bundle
├─ preload 高频页面(1~2 个)
└─ 其余页面按需加载

最终效果只有一句话:

用户点击 RN 页面时,ReactContext 和 bundle 都已经准备好。
七、进阶一:Split Bundle × CodePush(安全边界重建)

结论先行

❌ 不要热更新 base.bundle ✅ 只热更新 page.bundle

原因很简单:

  • base.bundle 强依赖 Native

  • page.bundle 是纯业务 JS

热更新的前提不是“能不能”,而是“是否可控”

八、进阶二:RN Fragment 化(页面栈统一)

当 RN 页面变多后,Activity 模式一定会遇到瓶颈:

  • 页面栈割裂

  • 动画不一致

  • 返回逻辑复杂

Fragment 化之后:

Activity(容器)
└── RNFragment
└── ReactRootView

RN 页面才真正成为Android 页面体系的一等公民

九、进阶三:性能埋点(用数据而不是感觉)

推荐至少埋 4 个点:

指标

含义

Context Ready

RN 初始化成本

Bundle Load

拆包是否生效

TTI

用户真实感知

Warm Start

预加载命中率

没有数据的优化,本质都是“心理安慰”。

十、进阶四:面向 RN 新架构的“可演进设计”

在 RN 0.70 这个时间点:

  • 不建议激进全量上 Fabric

  • 必须保证设计不被新架构淘汰

核心原则只有一句:

Runtime 在 base,业务在 page。

这会让你未来的升级成本无限接近于“可控”。

十一、总结:这不是 RN 集成,而是模块化交付

如果用一句话总结这套方案:

你不是在 Android 工程里“嵌入 RN”,而是在 Android 容器中运行 RN 业务模块。

这意味着:

  • RN 可控、可回滚、可替换

  • Native 依然是架构主导者

  • RN 发挥的是“效率优势”,而不是“架构侵入性”

如果你所在的团队:

  • RN 页面已超过 5 个

  • 或正在规划长期混合架构

  • 或对启动速度、稳定性有明确 KPI

那么这套方案,值得你认真评估一次