开源,无需改代码、无需改 SDK,支持自托管,iOS 和 Android 都能用,并按 App Store 与 Google Play 的政策要求设计
一、为什么还要再造一个热更新?
做 Flutter 的同学大概都经历过:线上出了个小 Bug,改动只有一行,却得重新打包、提审、等审核、等用户升级。快的要一两天,慢的得拖上一周。
热更新本来是解决这类问题的标准手段,但在 Flutter 上一直卡在三个现实问题里:
1. 社区缺少成熟的开源方案 Flutter Release 包是 AOT 编译的机器码,没法像 JS 那样换个脚本文件就生效。社区过去的探索大致分两类:一类是自己写 Dart 解释器,或者把页面转成 DSL 下发,不仅写法受限,体验也很割裂;另一类是直接替换 .so 动态库,但这招只能用于 Android,iOS 无解,而且在 Google Play 上还属于违规操作。
2. Shorebird 落地门槛高 Shorebird 是目前最知名的商业方案,技术很强,但实际引入时经常遇到阻碍:它的核心服务绑定在官方云上,按补丁安装量计费,用户量上来后成本不低;不支持私有化部署,补丁和数据都得经过第三方服务器,很多公司过不了内部合规;此外,国内网络环境下的连接稳定性也是个现实问题。
3. 应用商店的审核红线 这一点最容易被忽略,也最致命:
-
Google Play 禁止从非官方渠道下载可执行代码(如
dex、jar、so等),但允许在虚拟机或解释器中运行、且不直接调用受限 Android API 的代码。 -
App Store 同样禁止下发原生可执行代码,但开发者协议允许加载由内置解释器执行的代码,前提是不改变 App 的主要用途、不用来绕过审核。
因此,一套真正能在生产环境落地的 Flutter 热更新,补丁必须以“解释执行”的方式运行,同时尽量不打扰开发者原有的编码习惯。
基于这个思路,我开发了 flutter_patcher。
二、flutter_patcher 是什么
flutter_patcher 是一套开源、可自托管、符合双端商店政策的 Flutter 热更新方案:
-
双端可用:Android 和 iOS 使用同一套构建与下发流程。
-
无需改代码、无需改 SDK:业务代码照常写,接入只需几行初始化代码。CLI 会根据项目的 Flutter 版本自动下载并校验配套引擎,不会污染本机的 Flutter SDK。
-
支持自托管:客户端只需要一个能下载静态文件的 HTTPS 地址,S3、R2、OSS、GCS 配合 CDN 都能直接用。存储和 CDN 本身不需要可信,所有下发内容都会经过签名校验。
-
面向商店政策设计:补丁产物是 Dart 字节码,由 App 内置的解释器执行,不下发任何原生可执行文件。
-
更新范围广:项目自身的 Dart 代码、新增或修改的纯 Dart 依赖、类结构调整,以及图片、字体、JSON 等静态资源,都能通过补丁更新。
-
安全兜底:内置补丁签名校验;若补丁引起启动崩溃,会自动回退到安装包版本并拉黑该补丁;支持发布端撤回,已撤回的补丁无法被重放。
三、它是怎么做到的?
下面重点聊聊大家最关心的三个问题:靠什么跑、怎么换掉旧代码、为什么商店允许。
1. 基础能力:Dart 官方的动态模块
Dart 和 Flutter 团队近期在做一项实验性能力,叫动态模块(Dynamic Modules) :在 AOT 编译的 App 里内置一个字节码解释器,让应用在运行时加载并执行 Dart 字节码。flutter_patcher 就建立在这项官方能力之上。
这项能力目前还没随官方 SDK 默认发布,需要自己从源码编译引擎。flutter_patcher 把这一步替你做好了:它直接提供预编译好的引擎,在官方源码基础上开启了动态模块,并补了少量底层改动(修复 VM 字节码读取异常、打通补丁内的资源读取、拦截引擎拉起的入口)。接入时你不需要自己编译任何东西。
2. 核心难题:官方限制“只能新增,不能替换”
官方的动态模块有一条硬性限制:加载进来的代码只能新增声明,不能覆盖 App 里已有的声明。 但热更新的目的恰恰是“换掉旧代码”,这该怎么绕过去?
flutter_patcher 的做法是重写包名并切换入口:
-
整包改名,重新编译:生成补丁时,工具会把应用自己的代码包完整复制一份,将包名改为
patch.<模块号>.<原包名>,连带里面的import路径也一并改写,最后编译成字节码模块。对 Dart VM 来说,加载进来的全是新声明,没有覆盖任何已有代码,完全符合官方限制。同时工具还会做静态检查:只要补丁里还引用了未改名的旧包路径,就会直接中断报错。 -
从补丁的 main 启动:执行
build打基线包时,框架会注入一个前置启动入口,在你的main之前运行。App 冷启动时,它会先验签并加载补丁,然后直接调用补丁里的main函数。宿主包里那份旧的业务代码,在这次运行中就不会再被调用到了。 -
拦截其他入口:像后台 Isolate、推送回调这类由引擎直接拉起的入口,预编译引擎在底层做了拦截,会自动把调用转到补丁里对应的新函数上。
-
底层共享:Flutter 框架层、SDK,以及带原生代码的插件、FFI 和依赖它们的包,都不会打进补丁,而是继续以宿主包里的机器码运行,补丁通过动态接口去调用它们。
在补丁体积控制上,框架分了两档:常规情况下只打包应用自己的业务代码(lean 模式);当检测到纯 Dart 依赖有变动时,会自动升级为把未锁定的纯 Dart 依赖也一起带上(whole 模式)。因为剥离了框架和原生部分,补丁体积非常小——在示例 App 里改一行文字,生成的补丁只有约 19 KB。
3. 为什么说它在政策范围内
两大应用商店的合规底线,都是不能下发原生可执行代码,但允许解释执行的代码。flutter_patcher 的字节码补丁由 App 内置解释器运行,既不会生成新的 .so 或 .dex 文件,也不需要向系统申请可执行内存,和很多 App 下发 JS、游戏下发 Lua 脚本属于同一类做法。
当然,技术上的合规不代表可以随意使用:热更新应该只用来修线上 Bug 和做小幅调整,不能用来大幅改变 App 的核心功能或绕过审核。 这个边界需要开发者自己把控。需要说明的是,审核结果最终由商店决定,没有任何热更新方案能做出 100% 的保证。我们能做的,是让技术实现落在政策明确允许的范围内。
4. 性能与边界
-
没打补丁时:和普通的 Release 包完全一样,行为与普通 Release 包一致。
-
打了补丁后:应用自己的业务代码在解释器里运行,执行速度比机器码慢;但因为 Flutter 框架、渲染引擎和插件依然是机器码,常规的业务逻辑和 UI 交互通常影响不大,只有计算密集型的代码会有明显变慢。
-
不支持热更的内容:原生代码(Kotlin/Swift/C++)、含原生代码的插件及依赖它们的包、构建脚本、删除已有资源、升级 Flutter 版本、补丁代码里的 FFI 调用、运行时动态构造的
IconData。
这些不支持的改动,在生成补丁时都会进行静态检查,一旦越界会直接报错,避免把坏补丁发到线上。
当前环境要求:支持 Flutter 3.47.0–3.47.9(生成补丁必须使用与基线包相同的 Flutter 版本);Android 需 arm64 架构、API 28 及以上,iOS 需 13.0 及以上;不满足条件的设备会直接按原包运行,不加载补丁。
四、上手只需三步
1. 添加依赖并初始化
Bash
flutter pub add flutter_patcher
Dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterPatcher.init();
runApp(const MyApp());
}
// 在合适的时机检查更新,更新地址配置在 flutter_patcher.yaml 的 url 字段中
final check = await FlutterPatcher.checkUpdate();
if (check.hasUpdate) {
await FlutterPatcher.applyPatch(check.patch!);
}
2. 生成密钥,构建基线安装包
Bash
dart run flutter_patcher keys init # 只需执行一次:生成密钥,请将生成的 flutter_patcher.yaml 提交到 Git
dart run flutter_patcher build android # 或 build ios(需在 Apple Silicon Mac 上执行)
直接用 build 产出的安装包去上架。该命令会在本地记录当前版本的基线(Baseline),后续补丁都基于这个基线对比生成。
3. 修复 Bug,生成并发布补丁
Bash
dart run flutter_patcher patch android # 与基线对比,生成差异补丁
dart run flutter_patcher publish # 签名并上传至存储服务
publish 命令可以直接把产物写入本地目录,或者自动上传到 S3 及兼容服务(如 Cloudflare R2);如果用其他云存储,把产物手动上传上去即可。
五、最后
flutter_patcher 1.0.0 只做一件事:App 上线后发现 Bug,不用重新发版就能修。
它用的是 Dart 官方的动态模块(dynamic modules)。定制引擎的编译、包名改写、入口切换、补丁签名、启动崩溃后自动回滚,这些都由工具链处理。日常修 Bug 只需要三条命令:build 打包上架,patch 生成补丁,publish 发布。
如果这个工具对你有帮助,欢迎去 GitHub 点个 ⭐️。用起来踩到坑或者有什么想法,欢迎提 Issue 或在评论区交流。
- GitHub:github.com/xuelinger23…
- pub.dev:pub.dev/packages/fl…