React Native 热更新
架构设计 全剖析
从 JSBundle 构建流水线到灰度发布策略,再到线上秒级热修复机制—— 一次完整的技术解构,带你掌握 RN 热更的每一个核心环节。
热更新三阶段生命周期
JSBundle 构建
将 JS 源码编译、分包、压缩为可分发的 Bundle 包,是热更新的基石。
灰度发布策略
分批次、可控地将新 Bundle 推送给用户,降低全量发布的风险。
秒级热修复
在不发版的前提下,将修复后的 Bundle 极速下发到用户设备。
热更新完整链路
热更新为什么能生效?
React Native 运行时架构 —— 一切的前提
理解热更新的第一步,是理解 RN 将代码分成了两个世界。 正是这种分离,赋予了 JS 层「可独立替换」的能力。
App 启动时 Bundle 加载的完整生命周期
每次 App 冷启动,Native 端的加载器都会执行一套固定流程来决定加载哪个 Bundle。 热更新的「替换」就发生在启动前的准备阶段。
iOS: AppDelegate → didFinishLaunchingWithOptions Android: Application.onCreate()
从 AsyncStorage / NSUserDefaults / 文件系统中读取热更记录,与 App 内置 Bundle 版本做比较。
若存在已下载且校验通过的热更 Bundle → 加载热更版本;否则 → 降级到 App 内置 Bundle。
// 伪代码:Bundle 路径决策
const hotBundle = await getPendingBundle();
if (hotBundle && await verifyIntegrity(hotBundle)) {
bundlePath = hotBundle.path; // → 加载热更版本
} else {
bundlePath = BUILTIN_BUNDLE_PATH; // → 降级到内置版本
}引擎从文件系统读取 Bundle → 解析 → 编译(Hermes 为 AOT 字节码)→ 执行入口函数 index.js
// Hermes AOT: 已预编译为 .hbc 字节码
// 启动时直接加载字节码,省去 parse + compile 阶段
HermesRuntime::loadBytecode(bundlePath);
// JS 侧入口开始执行
AppRegistry.registerComponent('App', () => App);
AppRegistry.runApplication('App', { rootTag });React 组件 render → Shadow Tree 计算布局 → Native 端创建真实 View → 屏幕显示。
比较本地版本号与服务端版本号,若有新版本则后台下载 Patch,下次冷启动时自动替换。
热替换的「魔法」原理
可以热更的部分
无法热更的部分 (需要发版)
旧架构 (Bridge) vs 新架构 (JSI/Fabric) 对热更的影响
RN 新架构用 JSI (JavaScript Interface) 替代了异步 JSON Bridge, 这对热更新机制产生了深远影响。
TurboModuleRegistry.get() 返回 null 的兼容性问题。建议在热更 Manifest 中声明 TurboModule 兼容版本号。趁引擎还没读到新文件之前,把旧文件换掉 → 引擎读到新逻辑 → 新 UI 呈现
JSBundle 构建深度拆解
Metro Bundler 构建流程
Bundle 产物结构
const { getDefaultConfig } = require('metro-config');
module.exports = (async () => {
const config = await getDefaultConfig();
return {
...config,
transformer: {
...config.transformer,
// 启用 Hermes 字节码输出
hermesCommand: './node_modules/.bin/hermesc',
enableHermes: true,
},
serializer: {
...config.serializer,
// 自定义分包逻辑
customSerializer: require('./scripts/bundleSplit'),
},
};
})();全量更新 vs 增量 Diff 更新
灰度发布策略设计
白名单发布
指定内部用户或设备 ID 先行更新,用于功能验证和内部测试。
比例放量
按百分比逐步放量(1% → 5% → 20% → 50% → 100%),结合监控指标决策。
多维度定向
按 App 版本、系统版本、地域、设备型号等维度精准定向发布。
灰度发布时间线 (典型流程)
白名单用户 (≤ 100 人),核心功能冒烟测试
监控崩溃率 (Crash Rate < 0.1%)、接口异常率
逐步扩量,关注用户反馈、性能指标 (FPS/TTI)
全量发布,持续监控 48 小时
任一指标超阈值 → 自动/手动回滚至上一稳定版本
class HotUpdateGuard {
constructor(options) {
this.maxCrashCount = options.maxCrashCount ?? 3;
this.rollbackVersion = options.rollbackVersion;
this.monitoringWindow = options.windowMs ?? 60_000; // 1 min
}
async onBundleLoaded(bundleVersion) {
// 启动崩溃监控窗口
const crashCount = await this.startMonitoring(this.monitoringWindow);
if (crashCount >= this.maxCrashCount) {
console.warn(`[HotUpdate] Crash threshold exceeded (${crashCount}), rolling back...`);
await this.rollback(this.rollbackVersion);
// 强制重新加载上一稳定版本
NativeModules.DevSettings.reload();
}
}
async rollback(targetVersion) {
// 1. 标记当前版本为 broken
await this.markBundleBroken(currentVersion);
// 2. 激活上一版本 Bundle
await this.activateBundle(targetVersion);
// 3. 上报回滚事件
this.reportRollback(currentVersion, targetVersion);
}
}线上秒级热修复机制
双 Bundle 预加载策略
async function loadBundle(version: string) {
const bundlePath = `${RNFS.DocumentDirectoryPath}/bundles/${version}`;
// 1. 检查本地是否已有该版本
const exists = await RNFS.exists(bundlePath);
if (!exists) {
// 从 CDN 下载 Diff Patch
const patch = await downloadPatch(currentVersion, version);
// 合并生成新 Bundle
await bspatch(currentBundlePath, bundlePath, patch);
}
// 2. 校验 Bundle 完整性
const hash = await computeSHA256(bundlePath);
if (hash !== expectedHash) {
throw new Error('Bundle integrity check failed');
}
// 3. 标记为 Standby,等待下次启动切换
await AsyncStorage.setItem(
'pending_bundle',
JSON.stringify({ version, path: bundlePath })
);
}热更新关键监控指标
主流 RN 热更新方案对比
| 方案 | Diff 更新 | 增量大小 | 回滚机制 | Hermes 支持 | 开源 |
|---|---|---|---|---|---|
| CodePush (MS) | ✓ | ~20KB | 自动 | ✓ | ✓ |
| AppCenter | ✓ | ~30KB | 自动 | ✓ | ✗ |
| Pushy (国内) | ✓ | ~15KB | 自动 | ✓ | ✓ |
| 自研方案 | 可选 | 可控 | 自定义 | ✓ | ✗ |
最佳实践 & 避坑指南
推荐实践
常见陷阱
热更新的核心是 安全 × 速度 × 可观测
一套成熟的热更新方案,不仅需要高效的构建和分发能力, 更需要完善的灰度策略和自动回滚机制来保障线上稳定性。 把每一次热更都当作一次「可控的发版」来对待。