引言
Flutter 作为 Google 开源的 UI 工具包,以其高效的跨平台开发能力受到广泛关注。然而,与原生应用相比,Flutter 在热修复方面缺乏官方支持,这给线上 Bug 修复带来了挑战。热修复(Hotfix)技术允许在不发布新版本应用的情况下,动态修复线上 Bug,对于提升用户体验和降低运营成本至关重要。
本报告旨在深入研究当前 Flutter 热修复领域的主流方案,分析其技术原理、优缺点以及适用场景,为开发者选择合适的 Flutter 热修复方案提供参考。报告将涵盖以下几个主要方案:
- Shorebird: 一种接近官方的 Flutter 热更新方案。
- 基于 Flutter Web 的页面降级方案: 以众安银行为例的实践。
- 基于 Tinker 的 Android 端
libapp.so
替换方案: 以腾讯云的实践为例。 - Dart 虚拟机定制方案: 以京东云的探索为例。
通过对这些方案的详细分析和对比,希望能为 Flutter 应用的热修复实践提供有价值的洞察。
1. Shorebird 方案
Shorebird 是一个接近官方的 Flutter 热更新方案,它通过“魔改” Flutter Engine 和 Dart VM 实现热更新。其主要特点如下:
实现原理:
- 在构建时,使用
shorebird build
替代flutter build
,引入分叉版本的 Flutter Engine 和 Dart VM。 - 在不存在 patch 更新的情况下,性能与官方 Flutter 一致。
- Android 端: 通过二进制替换(
dlc.vmcode
文件)在 Dart VM 层实现动态替换,性能无影响,始终保持 AOT 运行模式。 - iOS 端: 由于 App Store 政策限制,不允许动态下载可执行代码。Shorebird 在 iOS 上采用解释器运行 patch 代码,未更改部分仍采用 AOT 运行。性能会有所变化,但尽可能接近原有性能(官方宣称可达 90% 以上)。
- Dart VM 增加了解释器和 Linker,解释器用于解析代码使其可以在 JIT 模式下运行到 Dart VM 上,Linker 用于分析和裁剪出最小差异的代码。
核心特点:
- 合规性: 以“二进制 patch 文件”进行热更新,避免直接修改 Native 代码,符合平台政策。
- 性能: Android 端性能无损;iOS 端在有 patch 时,性能略有损耗,但整体影响不大。
- 支持范围: 仅支持 Dart 代码更新,不支持 Native 代码更新。
- 版本要求: 对 Flutter 版本有要求(如 Android 3.10+,iOS 3.24+),与 Flutter 版本同步。
- 接入成本: 接入简单,日常开发流程不变,只需在构建和发布时使用
shorebird build
。 - 退出机制: 简单,删除即可恢复到官方 Flutter。
总结: Shorebird 通过对 Flutter Engine 和 Dart VM 的深度定制,利用二进制差异补丁和解释器机制,在保证合规性的前提下,为 Flutter 提供了高效的热更新能力。
2. 基于 Flutter Web 的页面降级方案
众安银行的 Flutter 热修复实践提供了一种基于 Flutter Web 页面降级 的方案,以解决金融类 APP 对热修复的强需求。其核心思路和特点如下:
方案选型考量:
- 目标: 确保热修复不影响上层业务,接入成本极低,支持业务动态扩展和 Flutter 版本迭代,方便业务升级。
- 现状分析:
- 业务改造 JS: 成本高,开发工具不兼容。
- AOT 搭载 JIT: 包体积增大(20MB+),iOS 不支持 JIT Release,不符合要求。
- 静态 DSL: 实现成本巨大,后期 Flutter 版本升级可能受影响。
- Flutter Web: 成本较低,可保持 Flutter 开发体验,对 Flutter 升级影响小。
- Shorebird: 国内用户下载补丁可能受阻,且实现细节复杂。
- 最终选择: 综合考虑人力、技术和时间成本,选择 Flutter Web 方案。
整体方案:基于页面降级的热修复
- 原理: 当遇到 Bug 时,在有问题页面进行修复,通过 Dart2JS 将项目转换为 Flutter Web,然后在路由中将原本跳转的有问题 Flutter PageB 切换至 Flutter Web PageB,从而达到修复 Bug 的目的。
- 页面降级原理: 路由基于协议模式跳转,通过设定不同路由表的页面等级来控制是否跳转到降级页面(H5、原生、RN 或 Flutter Web)。
- 部分原生逻辑降级: 通过统一的注册中心和逻辑中心,动态根据注册表的业务逻辑进行动态逻辑。对于支持 action 的业务逻辑,也可以支持原生进行降级,即不需要将页面进行页面降级,通过将方法中的数据作为数据流管道,在热修复时将管道嫁接到 Flutter Web 的管道,实现动态修改业务逻辑。
总结: 众安银行的实践提供了一种基于 Flutter Web 的热修复思路,通过页面降级和动态路由控制,实现了在不修改原生代码的前提下,对 Flutter 应用进行热修复。这种方案的优势在于成本较低、开发体验良好,且对 Flutter 版本升级影响较小,但其主要针对页面级别的 Bug 修复,对于更深层次的逻辑修复可能需要更复杂的机制。
3. 基于 Tinker 的 Android 端 libapp.so
替换方案
腾讯云文章中介绍的 Flutter 热更新方案主要基于 Tinker,针对 Android 平台,通过替换 libapp.so
文件实现热更新。
核心思想:
- Flutter 在 Android 的 Release 编译产物是
libapp.so
文件。热修复的核心就是将其替换为修复后的libapp.so
文件。 - 利用 Tinker 的热修复能力,将修复后的
libapp.so
文件下发到客户端,并通过 HookFlutterLoader
来加载新的libapp.so
。
实现细节:
- 接入 Tinker: 在原生 Android 工程中接入 Tinker。
- 获取补丁
libapp.so
: Tinker 下发补丁成功后,会在应用的数据目录生成修复后的libapp.so
补丁文件。 - Hook
FlutterLoader
: 通过反射修改FlutterLoader
中的sAotSharedLibraryName
字段(在较新版本 Flutter 中为aotSharedLibraryName
),使其指向 Tinker 下发的补丁libapp.so
文件。
方案对比(与自行开发替换 libapp.so
方案对比):
特性 | 自己开发方案 | 基于 Tinker 方案 |
---|---|---|
补丁下载逻辑 | 需要自己写 | 否 |
补丁大小 | 比较大 | 小 |
管理端开发 | 需要 | 不需要 |
补丁合成逻辑 | 需要 | 不需要 |
稳定性 | 需要验证 | 很稳定 |
成本 | 很高 | 低 |
总结: 该方案利用了 Tinker 在 Android 热修复领域的成熟能力,通过巧妙地 Hook Flutter 的加载机制,实现了 Flutter 应用在 Android 端的 libapp.so
热更新。其优势在于补丁小、稳定性高、开发成本低,但主要适用于 Android 平台,且需要对 Flutter 和原生 Android 开发都有一定了解。
4. Dart 虚拟机定制方案
京东云文章深入探讨了 Flutter 热更新技术,主要分析了三种可能的方案,并重点讲解了 Dart 虚拟机定制方案。
三种方案分析:
- 类似 RN 的方案:
- 原理: 用 JS 以 Flutter 语法写 Dart,然后将 XML DSL 转换为 Flutter 原子 Widget 组件,再由 Flutter 渲染。
- 优点: iOS 系统内置支持 JS,可实现更新。
- 缺点: 跨语言执行影响性能,学习成本高,Android 端需额外引入 JS 库。
- 开源方案: 手Q 的 MXFlutter,58 同城的 Fair。
- 页面动态组件方案:
- 原理: 编译期插桩/预埋 DynamicWidget 到代码中,动态下发 Json 数据,通过约定语义匹配 JSON 内数据,动态替换 Widget 内容。
- 优点: 支持 Android/iOS 两端更新。
- 缺点: UI 更新相对容易,业务逻辑动态化较麻烦;语义解析器开发成本大,不易维护;需要一整套前后端服务和工具。
- 开源方案: 天猫的 Tangram,淘宝的 DinamicX 等。
- Dart 虚拟机定制方案(重点):
- 原理: 分析 Dart 虚拟机原理,修改 Flutter Engine 层 Java/C++ 代码实现热更新。
- 优点: 性能影响小,动态性很高,可替换所有 Flutter 页面(包括 UI、逻辑、资源文件)。
- 缺点: 使用定制引擎,需维护不同版本的 Flutter 引擎代码。
- 开源方案: 未开源。
Dart 虚拟机定制方案的实现探索(以 Android 端为例):
- 核心思想: Flutter 业务代码由四个段组成(kDartVmSnapshotData, kDartVmSnapshotInstructions, kDartIsolateSnapshotData, kDartIsolateSnapshotInstructions),理论上只要能动态替换加载的代码段和数据段即可实现热更新。
- Android 端热修复核心步骤:
- 修改 Flutter Engine 代码,加载指定路径的
libapp.so
和flutter_assets
(如私有目录data/data/files
)。 - 编译 APK 时,利用 Gradle Transform 插件,根据 Flutter SDK 的 engine version 动态替换官方的 Flutter engine,最终写入修改后的 engine 到 APK。
- 生成补丁包:利用 BSDiff 算法比较新旧 APK 文件,生成 patch 补丁包。
- APP 启动时访问后端接口,根据参数拉取补丁包。
- 合成补丁包:校验 md5、app 版本号、补丁版本号、安装时间。
- 自定义 Flutter Engine 加载指定路径的
libapp.so
和flutter_assets
资源文件。
- 修改 Flutter Engine 代码,加载指定路径的
总结: 京东云的分析强调了 Dart 虚拟机定制方案在性能和动态性上的优势,并详细阐述了其在 Android 端的实现思路,即通过修改 Flutter Engine 来加载自定义路径的 libapp.so
和 flutter_assets
。该方案需要深入 Flutter 引擎底层,实现成本和维护成本较高,但能实现更彻底的热更新。iOS 端由于系统限制,动态加载可读写代码段数据到内存中仍是技术难点。
5. 方案对比与总结
下表对上述 Flutter 热修复方案进行了对比:
方案名称 | 原理 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
Shorebird | 修改 Flutter Engine 和 Dart VM,二进制 Patch | 接近官方,Android 性能无损,iOS 性能接近原生,合规性好,接入成本低。 | 仅支持 Dart 代码更新,不支持 Native 代码,对 Flutter 版本有要求,iOS 端性能略有损耗。 | 对性能和合规性要求高,主要修复 Dart 代码逻辑 Bug。 |
Flutter Web 页面降级 | 将问题页面转换为 Flutter Web 页面加载 | 成本低,可保持 Flutter 开发体验,对 Flutter 升级影响小,可实现页面级别 Bug 修复。 | 主要针对页面级别 Bug 修复,对深层次逻辑修复可能复杂,需要前端团队支持。 | 页面级别 Bug 修复,对热修复深度要求不高,有前端开发能力。 |
基于 Tinker 的 libapp.so 替换 | 利用 Tinker 替换 libapp.so 文件,Hook Flutter 加载机制 | 补丁小,稳定性高,开发成本低(基于 Tinker 成熟方案)。 | 主要适用于 Android 平台,需要对 Flutter 和原生 Android 开发有一定了解,iOS 端不适用。 | Android 平台 Bug 修复,对补丁大小和稳定性要求高。 |
Dart 虚拟机定制 | 修改 Flutter Engine 层代码,动态替换代码段和数据段 | 性能影响小,动态性高,可替换所有 Flutter 页面(UI、逻辑、资源文件)。 | 实现成本和维护成本高,需要深入 Flutter 引擎底层,iOS 端动态加载仍是技术难点。 | 对热修复深度和性能要求极高,有强大技术团队支持。 |
总结:
Flutter 热修复目前没有官方统一的解决方案,但社区和企业已经探索出多种可行的方案,各有优缺点和适用场景。选择哪种方案取决于项目的具体需求、团队的技术栈以及对性能、合规性和开发成本的权衡。
- Shorebird 是目前最接近官方且相对成熟的方案,尤其适合对合规性和性能有较高要求的项目,但需要关注其对 iOS 端性能的潜在影响以及对 Flutter 版本的兼容性。
- Flutter Web 页面降级 方案适用于对热修复深度要求不高,主要集中在页面展示和交互逻辑修复的场景,且团队具备前端开发能力。
- 基于 Tinker 的
libapp.so
替换 方案是 Android 平台上一种成熟且高效的热修复方式,适合对 Android 端 Bug 修复有强烈需求的项目。 - Dart 虚拟机定制 方案虽然能实现最彻底的热修复,但其高昂的开发和维护成本使其更适合拥有强大技术实力和定制化需求的企业。
在实际应用中,可以根据项目的具体情况,甚至结合多种方案的优势,构建适合自身业务的 Flutter 热修复体系。例如,可以考虑将 Shorebird 作为主要的热修复方案,同时辅以 Flutter Web 页面降级作为兜底方案,以应对不同类型的 Bug 修复需求。