Shorebird 之外,Flutter Android 热更新还有什么选择

一个常见场景

发版当晚,运营在群里发了截图:列表页空指针,30% 用户打不开。

如果是 React Native,下发一个 bundle 五分钟修完。Flutter 不行------应用商店再审一次要 1-3 天,国内市场宽松点也要几小时,这期间用户继续崩。

这是几乎每个 Flutter Android 团队都会遇到的痛点。Flutter 官方明确不支持热更新,所有方案都是社区在做。调研过的人大概率第一个看到的是 Shorebird。下面是几个 Shorebird 之外的考量。

Shorebird 没说出口的几个前提

Shorebird 现在是 Flutter 热更新里最成熟的方案,技术和文档都没什么好挑的。但它有几个前提,不见得适合所有团队。

后端不开源。 补丁要走 Shorebird 的服务器,用户日志、版本数据、灰度策略全经过海外服务。合规能不能过,看你的业务线。

收费。 免费版限制严,团队/企业版按 seat 收费,对小团队和独立开发者是真实成本。

国内访问。 CDN 主节点在海外,国内部分网络环境下补丁拉取速度不稳定。不是绝对劝退,但需要实测。

长期可用性。 万一某天 Shorebird 商业化策略变了或服务下线,你所有线上 App 都得跟着升级。金融、政企这种长生命周期产品,最好提前把这事想清楚。

如果以上四点都不是问题,Shorebird 很好用,这篇文章可以关了。如果其中一点你在意,往下看。

抛开 Shorebird,自己做要面对什么

Flutter Android 的热更新本质上只有一条路:替换 libapp.so

原因要从 Flutter 的编译模型说起。Release 模式下 Dart 代码 AOT 编译成机器码,打包进 libapp.so,里面包含 VM snapshot 和 isolate snapshot。运行时 Flutter Engine 加载这个 so 启动 Dart VM。

这意味着:

  • 不能像 RN 那样下发 JS。AOT 已经是机器码,不存在解释器层。
  • 只能整体替换 libapp.so。改一行 Dart 代码也要重新编译整个产物。
  • 替换时机必须在 Engine 加载前。Flutter Engine 一旦初始化,so 已经 mmap 进内存。
  • 版本必须严格对齐。补丁的 libapp.so 必须用和线上 App 完全相同的 Flutter SDK 版本编译,snapshot 格式不一致直接 crash。

理解这几点之后,自研要解决的就这几件事:

  • 替换流程:在 Application onCreate 之前介入 native lib 加载路径
  • 崩溃保护:替换失败要能回滚到原始 so,不能因为热更新本身搞崩 App 启动
  • 签名校验:防止补丁被中间人换成恶意代码
  • 接口约定:客户端怎么拉、服务端怎么下发、灰度怎么做
  • CDN:补丁包十几 MB,自己存还是上 OSS

每一件都不算复杂,加在一起就是个完整的工程。

几条路径的工作量

按从重到轻排:

最重的是 hook Flutter Engine。改 Engine 源码或者用 PLT/GOT hook,能做到很细的更新粒度。代价是复杂、跟版本强绑定、Flutter 升级时容易炸。团队里没 Android native 老兵就别碰。

中间这条是整体替换 libapp.so,在 System.loadLibrary 之前把磁盘上的 so 换掉。逻辑简单、版本依赖清晰、粒度粗(整个 Dart 层)。大多数实践方案走的就是这条,Shorebird 本质上也是。

最轻的是 AB 包热切换,直接下发完整 APK 让用户重装。严格说不算热更新。

第二条路径上,开源能直接用的方案不多。Tinker、Sophix 都是面向原生 Android 的,对 Flutter 项目不适用。所以一直有团队在自己造轮子。

一个开源实现:flutter_patcher

我前段时间把自己实现的方案开源了,叫 flutter_patcher。走的就是上面"替换 libapp.so"这条路径,范围非常窄:

  • 只做 Android。iOS 那边 App Store 禁止动态下发可执行代码,这条路根本走不通,没必要硬做跨端。
  • 后端自托管。补丁接口和 CDN 都在你手里,不依赖任何外部服务。
  • 只动 Dart 层。native code、assets、Manifest、Engine 变更都不在范围内。

快速上手:

yaml 复制代码
dependencies:
  flutter_patcher: ^0.1.2
rust 复制代码
await FlutterPatcher.checkUpdate(serverUrl: 'https://your-api/check');
await FlutterPatcher.applyPatch();

0.1.2 加了一个本地 mock server,可以不搭后端先跑通:

arduino 复制代码
dart run flutter_patcher:mock_server

5 分钟可以体验完整的 checkUpdate -> applyPatch 流程,决定值不值得往下搞。

崩溃保护是绕不开的一关。flutter_patcher 默认 fail-fast:补丁加载后只要确认失败一次(启动前 crash、native crash、ANR、首屏 5 秒内的 Dart 严重异常),下次冷启动就回到 APK 内置版本,并把这份补丁按 (version, md5) 写入本地黑名单,避免同一份问题包被反复下载。Android 11+ 还会用 ApplicationExitInfo 区分真崩溃和用户主动划掉,少误判。这部分边界 case 不踩一遍想不全,也是和"自己手撸一个简版"差距最大的地方。

它不是万能的

说几个不适合的场景:

  • iOS 项目: 商店政策禁止,做不了
  • 要改 native / assets / Manifest: 超出 Dart 层范围,必须发版
  • 不能自己维护后端的小团队: Shorebird 反而更省事
  • 强合规渠道: 部分应用市场(金融类、政企类)明确禁止动态下发可执行代码,先看渠道协议

但如果你的场景是"国内 Android-only Flutter 项目、团队有后端能力、想要数据在自己手里、成本可控",flutter_patcher 值得花 30 分钟试一下。

GitHub: github.com/xuelinger23... pub.dev: pub.dev/packages/fl...

如果方向对你有用,欢迎 star、提 issue、聊聊你的场景,特别是踩到边界 case 时。

相关推荐
李剑一1 小时前
前端必看 | Vue 刷新页面,生命周期钩子直接 "罢工",原来问题在这?90% 开发者都栽过!
前端·vue.js
閞杺哋笨小孩1 小时前
域名驱动多租户入驻:后台配置 + 前端解析
前端·vue.js
TeamDev1 小时前
在 Excel 加载项中嵌入 Web 视图
前端·后端·.net
悠哉摸鱼大王1 小时前
cesium学习(一)-基本概念
前端·cesium
LinDaiDai_霖呆呆1 小时前
大白话介绍大模型的一些底层原理,看完终于能跟人聊两句了
前端·人工智能·面试
悠哉摸鱼大王1 小时前
cesium学习(二)-地图地形
前端·cesium
青山师2 小时前
【AI热点资讯】5月10日AI热点:Cloudflare裁员1100人、Musk庭审第二周回顾、OpenAI发布Codex Chrome插件
前端·人工智能·chrome·ai·ai热点
张筱竼2 小时前
Android开发中的MVC、MVP与MVVM详解
android
阿赛工作室2 小时前
AI时代WEB开发人员生存与发展报告
前端·人工智能·node.js