ReactNative篇 | 动态化容器
前言
前两章我们从底层架构的角度拆解了 RN 的老架构(Bridge)和新架构(Fabric + TurboModule)。但对于很多业务团队来说,选择 RN 而不是纯 Native 的最大理由,不是跨端,而是——动态化。
什么是动态化?简单说就是不发版就能更新 App 的能力。
想象一下:你刚上线的新功能有一个严重的 UI bug,如果是纯 Native 开发,你只能重新提交 App Store 审核,最快也要等 1-2 天。但如果你的 App 用了 RN 的动态化方案,你可以在几分钟内把修复后的 JSBundle 推送到用户手机上,无需重新发版。
这个能力对业务的价值是巨大的。但它也带来了一系列技术挑战:怎么拆包?怎么更新?怎么保证安全?怎么做增量?
面试中,“动态化”是 RN 高级岗位的高频考点。面试官想看的不仅是你会用 CodePush,更是你对整套动态化体系的理解深度。
本章,我们就从为什么需要动态化出发,完整拆解 JSBundle 拆包、热更新方案、增量更新、安全考量,最后和 Flutter 的动态化做个对比。
诊断自测
Q1:为什么 RN 可以做动态化,而 Native(Swift/Kotlin)不行?技术层面的根本原因是什么?
点击查看答案
根本原因在于 RN 的业务逻辑和 UI 描述是用 JavaScript 编写的,运行在 JS 引擎中。JS 代码以 JSBundle(本质是一个 .js 或 .bundle 文件)的形式存在,可以像加载任何文件一样动态下载和加载。而 Native 代码(Swift/Kotlin)是编译成机器码的,修改后必须重新编译打包并通过应用商店分发。
从 iOS 政策角度看,Apple 明确禁止动态下载和执行原生可执行代码(如动态链接库),但允许解释执行的脚本(如 JavaScript),这也是 RN 动态化的政策基础。
Q2:什么是”基础包 + 业务包”的拆包策略?为什么要拆?
点击查看答案
RN 默认会把所有 JS 代码打成一个 JSBundle。“基础包 + 业务包”是将 JSBundle 拆成两部分:
- 基础包(Common Bundle):包含 React、React Native 框架代码和公共组件,所有业务共用,变化频率低
- 业务包(Business Bundle):只包含某个业务模块的代码,变化频率高
为什么要拆?两个核心原因:
- 减少热更新下载量:大部分更新只涉及业务代码,只下发业务包即可,不需要重新下载包含框架代码的基础包
- 支持多业务模块独立更新:不同业务团队可以独立发布自己的业务包,互不影响
Q3:CodePush 是什么?它有什么局限性?
点击查看答案
CodePush 是微软提供的一个 RN/Cordova 热更新云服务。它允许开发者直接将 JSBundle 更新推送到用户设备,无需通过 App Store/Google Play。
局限性:
- 只能更新 JS 代码和静态资源,不能更新 Native 代码(新增 Native Module 必须发版)
- 服务在海外,国内访问速度和稳定性不理想
- 微软已于 2025 年 3 月 31 日正式关闭 App Center(包括 CodePush),意味着依赖 CodePush 的项目需要迁移到其他方案
- 不支持细粒度的灰度发布和回滚策略(相比自建平台)
一、为什么需要动态化?
1.1 业务驱动
动态化不是炫技,它解决的是真实的业务痛点:
痛点一:App Store 审核周期
iOS App Store 的审核通常需要 1-3 天,紧急情况下申请加急审核也要几小时到一天。如果线上有一个影响用户体验的 bug,等审核通过再更新,用户可能已经流失了。
痛点二:用户更新率
即使新版本已经上架,也不是所有用户都会立即更新。据统计,一个新版本要覆盖 90% 的用户,通常需要 1-2 周。动态化可以在用户无感知的情况下完成更新,覆盖率接近 100%。
痛点三:运营活动的灵活性
电商类 App 经常需要快速上线运营活动页面(双十一、618 等)。如果每次活动都要发版,节奏根本跟不上运营需求。动态化让前端团队可以像发布网页一样发布 App 内容。
痛点四:A/B 实验
动态化让 A/B 实验的成本大大降低。你可以在不发版的情况下,给不同用户推送不同版本的 JSBundle,快速验证功能方案。
1.2 技术可行性
RN 之所以能做动态化,核心在于它的运行时架构:
App 启动
→ 加载 JSBundle(一个 JS 文件)
→ JS 引擎(Hermes/JSC)解释执行
→ React 组件渲染为原生 UI
JSBundle 本质上就是一个文件。你可以把它打包在 App 内(离线包),也可以从服务器下载。只要替换了这个文件,App 重新加载后就能展现新的内容。
但有一个硬限制:动态化只能更新 JS 层的代码和静态资源(图片、字体等)。 如果你的更新涉及新增或修改 Native Module(原生代码),就必须走正常的发版流程。
二、JSBundle 拆包策略
默认情况下,RN 的 Metro bundler 会把所有 JS 代码打成一个 JSBundle。对于小型项目这没问题,但对于中大型项目,这个单一的 Bundle 可能会有几 MB 甚至十几 MB,每次热更新都全量下载显然不合理。
2.1 基础包 + 业务包
最常见的拆包策略是**“一个基础包 + 多个业务包”**:
┌─────────────────────┐
│ 基础包 (Common) │ React, RN, 公共组件, 公共工具
│ ~2MB, 很少变化 │
└─────────────────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 业务包 A │ │ 业务包 B │ │ 业务包 C │
│ 首页模块 │ │ 订单模块 │ │ 我的模块 │
│ ~200KB │ │ ~300KB │ │ ~150KB │
└──────────┘ └──────────┘ └──────────┘
基础包 包含:
react、react-native框架代码- 公共组件库(按钮、弹窗、表单等)
- 公共工具函数(网络请求、存储、日志等)
- 全局状态管理(Redux/MobX 等)
业务包 包含:
- 某个业务模块的页面和逻辑
- 该模块特有的组件和资源
2.2 拆包的技术实现
RN 官方的 Metro bundler 从 0.60+ 开始支持配置多入口和自定义序列化器,可以实现基础拆包。但大多数生产项目会使用更成熟的方案:
方案一:基于 Metro 自定义配置
通过修改 metro.config.js,配置 createModuleIdFactory 和 processModuleFilter,控制模块的分配:
// metro.config.js(简化示意)
module.exports = {
serializer: {
createModuleIdFactory() {
// 自定义模块 ID 生成规则,确保基础包和业务包的模块 ID 不冲突
return (path) => {
// 基于文件路径生成稳定的模块 ID
return hashModulePath(path);
};
},
processModuleFilter(module) {
// 打业务包时,过滤掉已经在基础包中的模块
if (isBuildingBusinessBundle) {
return !isInCommonBundle(module.path);
}
return true;
}
}
};
方案二:使用 Re.Pack(前身 Haul)
Re.Pack 是一个用 Webpack 替代 Metro 的方案,天然支持 Code Splitting 和 Module Federation。它允许你用 Webpack 生态的 SplitChunksPlugin 来做更灵活的拆包。
方案三:自建拆包工具
一些大厂(如携程、美团)会自建拆包工具链,根据业务模块的依赖关系自动分析和拆分。
2.3 运行时加载
拆包之后,运行时需要先加载基础包,再按需加载业务包:
App 启动
→ 加载基础包(内置在 App 中或从缓存读取)
→ JS 引擎初始化,React/RN 框架就绪
→ 用户进入某个业务模块
→ 动态加载对应的业务包
→ 业务模块渲染
动态加载业务包的方式通常是通过一个 Native Module 来实现:
// 简化示意
async function loadBusinessBundle(bundleName) {
const bundlePath = await BundleManager.download(bundleName);
await BundleManager.loadBundle(bundlePath);
// 业务包中的组件现在可以使用了
}
三、热更新方案
热更新是动态化的核心能力。下面介绍几种主流方案。
3.1 CodePush(已停服)
CodePush 曾经是 RN 热更新的事实标准,由微软提供。
工作流程:
开发者发布更新
→ 上传新的 JSBundle 到 CodePush 服务器
→ App 启动时检查更新(或后台定时检查)
→ 发现新版本 → 下载 JSBundle
→ 下次启动时加载新的 JSBundle(或立即重启加载)
核心 API:
import CodePush from 'react-native-code-push';
// 方式一:装饰器方式,自动检查更新
const App = CodePush({
checkFrequency: CodePush.CheckFrequency.ON_APP_START,
installMode: CodePush.InstallMode.ON_NEXT_RESTART,
})(MyApp);
// 方式二:手动控制
async function checkUpdate() {
const update = await CodePush.checkForUpdate();
if (update) {
await update.download();
await CodePush.restartApp();
}
}
重要提醒: 微软的 App Center(包括 CodePush 服务)已于 2025 年 3 月 31 日正式关闭。目前社区有以下替代方案:
- EAS Update(Expo):如果你使用 Expo,这是官方推荐的替代方案
- react-native-ota-hot-update:社区开源的自托管方案
- 自建热更新平台:大中型团队的首选
3.2 自建热更新平台
对于中大型团队,自建热更新平台是更常见的选择。它提供了更高的灵活性和控制力。
一个典型的自建平台包含以下组件:
┌─────────────────────────────────────┐
│ 管理后台 (Web) │
│ - Bundle 版本管理 │
│ - 灰度发布配置 │
│ - 回滚操作 │
│ - 发布审批流程 │
│ - 数据统计(更新率、成功率、崩溃率) │
└────────────────┬────────────────────┘
│
┌────────────────▼────────────────────┐
│ Bundle 服务器 │
│ - Bundle 存储(OSS/CDN) │
│ - 版本管理 API │
│ - 差分计算服务 │
│ - 签名验证 │
└────────────────┬────────────────────┘
│
┌────────────────▼────────────────────┐
│ App 端 SDK │
│ - 启动时/后台检查更新 │
│ - 下载 + 验签 + 解压 │
│ - 加载新 Bundle │
│ - 异常回滚(加载失败自动回退旧版本) │
└─────────────────────────────────────┘
核心设计要点:
- 版本管理:每个 Bundle 有唯一版本号,和 App 的 Native 版本绑定(确保 JS 版本和 Native 版本兼容)
- 灰度发布:先推送给 1% 的用户,观察崩溃率和异常数据,没问题再逐步扩大
- 回滚机制:发现问题时,可以在后台一键回滚到上一个稳定版本
- 强制更新/静默更新:根据更新内容的重要性选择策略
3.3 更新时机策略
热更新的时机选择也很重要,常见策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 启动时静默更新 | App 启动时后台下载,下次启动生效 | 非紧急更新 |
| 启动时强制更新 | App 启动时下载并立即重启 | 严重 bug 修复 |
| 后台定时检查 | 定时轮询服务器,发现新版本就下载 | 一般场景 |
| 进入模块前检查 | 用户进入某个模块时,检查该模块的业务包是否有更新 | 拆包场景 |
| 手动触发 | 在设置页提供”检查更新”按钮 | 用户主动行为 |
四、增量更新与差分下载
全量下载 JSBundle 在包体较大时会消耗较多流量,特别是对于”只改了一行代码”的小修复。增量更新就是为了解决这个问题。
4.1 差分算法
增量更新的核心是差分算法:计算新旧 JSBundle 之间的差异,只下发差异部分(patch)。
常用的差分算法:
- bsdiff:二进制差分算法,压缩率高,适合任意二进制文件
- google-diff-match-patch:文本差分算法,更适合 JS 代码这种文本文件
流程:
服务端:
旧 Bundle (v1.0) + 新 Bundle (v1.1) → 差分算法 → Patch 文件 (~20KB)
客户端:
本地 Bundle (v1.0) + Patch 文件 → 合并算法 → 新 Bundle (v1.1)
假设完整的业务包是 300KB,而某次更新只修改了几个组件,差分后的 Patch 可能只有 10-30KB。下载量减少了 90% 以上。
4.2 增量更新的注意事项
版本链管理:
差分是基于”某个特定旧版本”计算的。如果用户本地的版本是 v1.0,服务端只有 v1.0 → v1.2 的 patch,但用户错过了 v1.1,直接下发 v1.0 → v1.2 的 patch 就行了。但如果服务端只有 v1.1 → v1.2 的 patch,用户就没法增量更新了。
常见做法是:服务端为最近 N 个版本都生成差分包,覆盖大部分用户。如果用户版本太旧,回退到全量下载。
服务端存储:
v1.0 → v1.3 patch
v1.1 → v1.3 patch
v1.2 → v1.3 patch
v1.3 全量包(兜底)
完整性校验:
客户端合并 Patch 后,必须对新 Bundle 做完整性校验(通常是比对 hash),确保合并结果正确。如果校验失败,回退到全量下载。
五、动态化的安全考量
动态化意味着你在用户的手机上执行从网络下载的代码——这对安全的要求非常高。
5.1 传输安全
必须使用 HTTPS。所有 Bundle 的下载必须通过 HTTPS,防止中间人篡改。这是最基本的要求。
证书固定(Certificate Pinning)。对于安全性要求更高的 App,建议在客户端固定服务器证书,防止中间人使用伪造证书进行攻击。
5.2 代码签名
这是动态化安全的核心。即使传输过程是安全的,你还需要确保下载的 Bundle 确实是你的服务器发出的,没有被篡改。
签名流程:
发布端:
JSBundle → 计算 Hash → 用私钥签名 → 发布 Bundle + 签名
客户端:
下载 Bundle + 签名 → 用公钥验签 → 签名合法 → 加载 Bundle
→ 签名非法 → 拒绝加载,报警
通常使用 RSA 或 ECDSA 非对称加密。私钥只在发布服务器上保存,公钥内置在 App 中。
5.3 代码混淆与加密
JSBundle 是 JavaScript 代码,下载到用户设备后理论上是可以被反编译阅读的。为了保护商业逻辑:
- 代码混淆:使用 Metro 的 minify 或第三方混淆工具,让代码难以阅读
- Hermes 字节码:使用 Hermes 引擎的 AOT 编译,将 JS 编译成 Hermes 字节码(
.hbc文件),不仅执行更快,也增加了逆向难度 - 整体加密:有些方案会对 Bundle 整体加密,加载时在内存中解密
5.4 灰度发布与回滚
从安全和稳定性角度看,灰度发布和快速回滚是必不可少的:
- 灰度发布:新版本先推给小比例用户(1%→5%→20%→100%),监控崩溃率和异常数据
- 自动回滚:如果新 Bundle 加载失败(白屏、崩溃),客户端 SDK 应自动回退到上一个稳定版本
- 服务端回滚:运营人员可以在后台一键将所有用户回退到指定版本
5.5 Apple 政策风险
Apple 的 App Store 审核指南第 3.3.2 条允许下载和执行 JavaScript 代码,但有一些限制:
- 下载的代码不能改变 App 的主要功能和用途
- 不能绕过审核引入被禁止的功能
- 不能下载原生可执行代码(动态链接库等)
在实践中,大多数正常的热更新(bug 修复、UI 调整、功能微调)是被允许的。但如果你用热更新做了很激进的事情(比如上线了一个审核时完全不存在的功能),有被下架的风险。
六、与 Flutter 动态化的对比
面试中经常会被问到 RN 和 Flutter 在动态化方面的对比。这是一个能体现你技术视野的问题。
6.1 Flutter 的动态化困境
Flutter 使用 Dart 语言,在 Release 模式下会被 AOT 编译成原生机器码。这意味着:
- Dart 代码不能像 JS 那样动态下载和解释执行
- Apple 禁止动态下载原生可执行代码,所以在 iOS 上,Flutter 的 Dart 代码不能做热更新
这是 Flutter 相比 RN 最大的劣势之一。
6.2 Flutter 社区的尝试
虽然 Flutter 官方不支持动态化,但社区做了不少尝试:
| 方案 | 原理 | 局限性 |
|---|---|---|
| Shorebird(官方相关) | Dart 代码差分更新 + 运行时修补 | 2024 年后逐渐成熟,但 iOS 端仍有政策风险 |
| Fair(58同城) | 将 DSL 编译为 JSON,运行时解析渲染 | 功能受限,不能覆盖所有 Widget |
| MXFlutter(腾讯) | 用 JS 编写 Flutter UI,JS 引擎解析 | 已停止维护 |
| 动态 Widget | 服务端下发 Widget 描述 JSON | 只能做简单 UI,不能下发逻辑 |
6.3 对比总结
| 维度 | React Native | Flutter |
|---|---|---|
| 动态化可行性 | 天然支持(JS 解释执行) | 非常困难(Dart AOT 编译) |
| iOS 政策风险 | 低(Apple 允许 JS 动态下载) | 高(Apple 禁止动态下载原生代码) |
| 热更新生态 | 成熟(CodePush 替代方案、自建平台) | 不成熟(社区方案各有局限) |
| 更新粒度 | 可以更新完整的业务逻辑 + UI | 大多只能更新 UI 描述 |
| 更新包大小 | JSBundle 通常较大,但支持增量 | 原生代码差分更新效率高 |
面试中的回答思路: 如果被问到”RN 和 Flutter 怎么选”,动态化是 RN 的核心优势之一。对于需要频繁更新、运营驱动的业务(如电商、社交),RN 的动态化能力是 Flutter 很难替代的。
常见误区
误区一:“热更新可以更新所有东西”
热更新只能更新 JS 代码和静态资源(图片、字体、JSON 等)。如果你的更新涉及新增 Native Module(比如接入一个新的相机 SDK)、修改原生配置(如 Info.plist 或 AndroidManifest.xml)、升级 RN 版本、修改原生代码,都必须走正常的发版流程。一个常见的坑是:开发者在热更新包中引用了一个只在新版 Native 代码中才有的 Native Module,导致老版本 App 加载热更新后直接崩溃。
误区二:“CodePush 是唯一的热更新方案”
CodePush 曾经是最流行的方案,但它只是众多方案中的一个,而且已经停服。对于国内的生产环境,自建热更新平台才是主流选择。自建平台可以根据业务需求定制灰度策略、审批流程、数据统计等功能,灵活性远超第三方服务。不要把”热更新”和”CodePush”画等号。
误区三:“拆包只是为了减少下载量”
减少下载量是拆包的好处之一,但不是唯一的原因。拆包还解决了以下问题:
- 多团队协作:不同业务团队可以独立开发、独立发布自己的业务包
- 按需加载:用户只加载当前需要的业务模块,减少内存占用和启动时间
- 故障隔离:某个业务包出问题,只影响该模块,不会拖垮整个 App
误区四:“Flutter 完全不能做动态化”
Flutter 的动态化确实比 RN 困难得多,但并非完全不可能。Shorebird 等方案正在逐步成熟,Android 端的动态化限制也比 iOS 松得多。更准确的说法是:Flutter 在 iOS 上做代码级别的动态化有严格的政策限制,而 UI 级别的动态化(服务端下发 Widget 描述)是可行但功能受限的。
小结
本章从动态化的业务价值出发,系统梳理了 RN 动态化容器的完整技术体系。
核心要点
- 动态化的本质:利用 JS 解释执行的特性,通过替换 JSBundle 文件实现不发版更新
- 拆包策略:基础包(框架 + 公共代码)+ 业务包(模块代码),减少更新量、支持独立发布
- 热更新方案:CodePush 已停服,自建平台是主流,需要关注灰度、回滚、监控
- 增量更新:差分算法生成 Patch,客户端合并 + 校验,减少 90%+ 下载量
- 安全体系:HTTPS + 代码签名 + 混淆/加密 + 灰度/回滚
- vs Flutter:RN 天然支持动态化是其核心优势,Flutter 受限于 Dart AOT 编译和 iOS 政策
记忆口诀
动态化四部曲:拆(拆包)→ 传(安全传输)→ 更(增量更新)→ 灰(灰度发布)。
本章思维导图
- 为什么需要动态化
- 绕过 App Store 审核周期
- 提高用户覆盖率(静默更新)
- 运营活动灵活上线
- A/B 实验低成本实施
- 技术基础:JS 解释执行
- JSBundle 拆包
- 基础包 + 业务包
- 拆包实现:Metro 自定义 / Re.Pack / 自建工具
- 运行时按需加载
- 热更新方案
- CodePush(已停服 2025.03)
- 自建平台(管理后台 + Bundle 服务器 + SDK)
- 更新时机策略(静默/强制/定时/按需/手动)
- 增量更新
- 差分算法(bsdiff / google-diff-match-patch)
- 版本链管理
- 完整性校验
- 安全考量
- HTTPS + 证书固定
- 代码签名(RSA/ECDSA)
- 代码混淆 + Hermes 字节码
- 灰度发布 + 自动回滚
- Apple 政策合规
- vs Flutter 动态化
- RN:天然支持(JS 解释执行)
- Flutter:困难(Dart AOT + iOS 政策限制)
- Flutter 社区方案(Shorebird / Fair / 动态 Widget)
练习挑战
第一题 ⭐(基础):概念辨析
请解释”热更新”和”热重载(Hot Reload)“的区别。它们在技术实现和使用场景上有什么不同?
点击查看答案
热更新(Hot Update / OTA Update):
- 指在用户设备上,不通过应用商店,动态下载并替换 JSBundle 的过程
- 发生在生产环境,面向终端用户
- 更新后通常需要重新加载 App(或重新加载 JS 引擎)
- 目的是修复线上 bug、发布新功能
热重载(Hot Reload / Fast Refresh):
- 指在开发过程中,修改代码后不需要重新编译整个 App,只替换修改的模块
- 发生在开发环境,面向开发者
- 保留组件的当前状态,只更新变化的部分
- 目的是提高开发效率
两者名字很像但完全是两回事:热更新是生产环境的部署机制,热重载是开发环境的调试工具。
第二题 ⭐⭐(进阶):方案设计
你负责一个 RN 电商 App,有首页、商品详情、购物车、我的四个主要模块。请设计一个拆包和热更新策略,考虑以下因素:
- 首页变化最频繁(运营活动)
- 购物车模块对稳定性要求最高
- 团队有 4 个小组,各负责一个模块
点击查看答案
拆包策略:
基础包:React + RN + 公共组件 + 网络库 + 状态管理 + 路由
↓
业务包 × 4:
- home.bundle(首页模块)
- product.bundle(商品详情模块)
- cart.bundle(购物车模块)
- profile.bundle(我的模块)
每个业务包独立打包、独立发布、独立版本管理。4 个团队可以各自按自己的节奏发布。
热更新策略:
| 模块 | 更新策略 | 灰度策略 | 回滚 |
|---|---|---|---|
| 首页 | 高频更新,启动时静默下载 | 1%→10%→50%→100%,每步观察 1 小时 | 自动回滚(崩溃率 > 0.5%) |
| 商品详情 | 中频更新,后台定时检查 | 1%→20%→100%,每步观察 2 小时 | 自动回滚(崩溃率 > 0.3%) |
| 购物车 | 低频更新,稳定性优先 | 1%→5%→20%→50%→100%,每步观察 4 小时 | 自动回滚(崩溃率 > 0.1%),且需人工审批才能全量 |
| 我的 | 中频更新,后台定时检查 | 1%→20%→100% | 自动回滚 |
其他设计:
- 基础包内置在 App 中,只在大版本更新时变化(跟随发版)
- 所有业务包支持增量更新(bsdiff),减少用户流量消耗
- 购物车模块因为涉及支付,安全等级最高,加签名验证 + 代码混淆
- 首页模块支持”预加载”,在 WiFi 环境下提前下载下一个版本
第三题 ⭐⭐⭐(综合):故障排查
线上用户反馈:热更新后 App 启动白屏。你作为技术负责人,请列出可能的原因(至少 5 个),并为每个原因提供排查思路和预防措施。
点击查看答案
原因一:JSBundle 下载不完整
- 排查:检查下载日志,对比本地文件 hash 和服务端 hash
- 预防:下载完成后做 hash 校验,校验失败则重新下载或回退
原因二:增量合并失败
- 排查:检查 Patch 合并的日志,对比合并后 Bundle 的 hash
- 预防:合并后做完整性校验,校验失败回退到全量下载
原因三:JS 和 Native 版本不兼容
- 排查:检查热更新包是否引用了当前 App Native 版本不存在的 Native Module
- 预防:热更新包绑定 Native 版本号,服务端下发时校验兼容性
原因四:Bundle 中存在 JS 运行时错误
- 排查:查看 JS 错误日志(ErrorBoundary 捕获的错误、全局错误处理器的日志)
- 预防:发布前自动化测试,灰度阶段监控 JS 错误率
原因五:Hermes 字节码版本不匹配
- 排查:如果热更新包是 Hermes 字节码(.hbc),检查编译时使用的 Hermes 版本和 App 内置的 Hermes 引擎版本是否一致
- 预防:CI/CD 流程中确保使用与 App 相同版本的 Hermes 编译器
原因六:存储空间不足
- 排查:检查设备可用存储空间,检查 Bundle 写入是否成功
- 预防:下载前检查可用空间,空间不足时跳过更新
原因七:自动回滚机制未生效
- 排查:检查回滚逻辑的日志,确认回滚触发条件和执行结果
- 预防:实现”加载计数器”机制——新 Bundle 加载失败超过 N 次,自动回退到旧版本
自我检测
读完本章后,对照下面的清单检验一下自己的掌握程度:
- 能说清楚为什么 RN 可以做动态化而 Native App 不行——从技术和政策两个角度
- 能解释”基础包 + 业务包”的拆包策略:各包含什么、为什么要拆、怎么实现
- 能说出至少两种热更新方案(CodePush、自建平台),并说明各自的优缺点
- 能解释增量更新的原理:差分算法、版本链管理、完整性校验
- 能列出动态化的安全考量清单:HTTPS、代码签名、混淆/加密、灰度/回滚
- 能从动态化角度对比 RN 和 Flutter,并说出 Flutter 动态化困难的根本原因
- 能设计一个适合自己项目的拆包和热更新策略
- 了解 Apple App Store 对动态化的政策限制,知道什么能做、什么不能做
购买课程解锁全部内容
大厂前端面试通关:71 篇构建完整知识体系
¥89.90