Flutter热更新实战:Shorebird接入、双端差异与回滚方案

发布时间:2026/9/16 20:01:57
Flutter热更新实战:Shorebird接入、双端差异与回滚方案 Flutter圈子里一直有个老生常谈的话题热更新到底能不能做怎么做。尤其是线上突然发现一个必现崩溃或者一个按钮文案写错导致用户投诉这时候如果告诉你“提审排队两天审核再等两天”估计血压直接拉满。我团队从去年开始正式把Shorebird接入生产项目经历了从技术选型、环境踩坑、双端发布到线上灰度回滚的完整链路这篇就把整个实战过程拆开揉碎讲清楚包括iOS和Android之间那些容易被忽略的差异点。如果你正在调研Flutter热更新方案或者已经决定接Shorebird但不确定怎么落地这篇文章应该能帮你少走不少弯路。1. 决定接Shorebird之前先想清楚这几个问题热更新不是“把代码推到线上”这么简单。Flutter的热更新和原生热修在原理上有本质区别如果没想清楚就开始集成后面大概率要返工。1.1 Flutter热更新的核心原理Dart代码快照替换Flutter应用在构建时Dart代码会被编译成两部分一部分是AOT编译产物libapp.soAndroid上在APK里iOS上在App.framework里另一部分是引擎层Flutter.framework/engine。Shorebird做的热更新本质是只替换Dart代码编译出来的那一份产物引擎和原生层代码不动。这个机制决定了它的边界它能修的是纯Dart逻辑和Flutter UI层的问题不能修的是原生代码、插件原生实现、Gradle配置、Info.plist这类东西。如果你线上Bug出在原生插件调用上或者需要改AndroidManifest权限那Shorebird也无能为力得走正常发版流程。我见过有人把Shorebird理解成“Flutter版的JSPatch”其实不太准确。JSPatch能动态调用任意OC方法Shorebird只能替换Dart层代码但它有一个巨大的优势Dart代码是真正运行的逻辑不是脚本解释执行性能损耗几乎为零。1.2 什么项目适合接什么项目不适合先说适合的线上Crash频率高、修复时效要求高的业务型App运营活动页面频繁调整、不想每次都走审核的团队小团队发版资源有限需要一条“救火通道”不太适合的纯原生功能占比极高的AppDart层代码很少对包体积极其敏感的AppShorebird会引入一些额外产物完全离线环境部署、无法访问外网的应用补丁需要通过Shorebird的CDN下发另外要注意Shorebird使用了fork版本的Flutter引擎和官方Flutter SDK不完全等价这意味着接入之后Flutter版本升级、第三方插件兼容性排查都要额外多花了心思。这是选型时就要接受的隐性成本。1.3 和其他方案的对比为什么是Shorebird圈子里还有几类方案自己搭一个Dart层动态化框架、用平台的脚本引擎比如iOS的JavaScriptCore配合运行时解释Dart或JS、或者干脆每次发版走加急审核。自己搭动态化框架工程量巨大而且要处理AOT模式下代码不能被运行时解释的难题脚本引擎方案写起来倒是快但性能和类型安全性差Flutter官方也不推荐这种架构。Shorebird是目前市面上唯一一个同时支持Android和iOS、且能保持Flutter原生开发体验的成熟方案。它提供的命令原文是“patch”也就是增量补丁只有Dart代码变更的部分会被下载用户侧几乎无感。综合对比下来对于大多数Flutter团队它是性价比最高的选项。2. 环境准备注册、安装CLI与Flutter版本对齐环境准备这一步看着简单实际踩坑的人不在少数。我按照最稳妥的路径讲一遍。2.1 注册账号并安装Shorebird CLI先去Shorebird官网注册账号这一步没什么好说的。CLI的安装方式在macOS和Linux上略有不同官方推荐用脚本curl --proto https --tlsv1.2 -sSf https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh | sh安装完成后执行shorebird --version确认一下。如果命令找不到多半是环境变量没配好把Shorebird的bin目录加到PATH里就行。注意Windows上目前支持度有限iOS相关的命令只能在macOS上执行Android的构建在三个平台都可以跑。所以如果你主力开发机是Windows建议准备一台macOS做iOS发布用这属于硬性门槛。网络这块多说一句安装脚本和后续的SDK下载都依赖GitHub和Google的存储服务如果网络不稳定会看到各种奇怪的下载失败。我建议在干净的网络环境下执行遇到超时可以多试几次或者将git的代理配置好。2.2 Flutter版本对齐fvm是必须的Shorebird和Flutter版本是强绑定的。每个Shorebird SDK版本对应一个特定范围的Flutter版本如果你用官方的flutter命令极有可能遇到版本不匹配的报错。我团队从第一天起就用fvmFlutter Version Management管理Flutter版本这个习惯在接入Shorebird后被证明了极其重要。# 安装fvm dart pub global activate fvm # 创建一个固定版本的Flutter并启用 fvm install 3.19.0 fvm use 3.19.0然后在项目根目录执行shorebird init时Shorebird会自动识别当前使用的Flutter版本并记录到配置里。如果检测到不兼容会明确提示你切换到指定版本。2.3 登录并验证环境shorebird login这个命令会打开浏览器完成OAuth授权。登录成功后shorebird account whoami应该能看到你的账号信息。验证整个环境是否可用的最简单办法是跑一个已有的Flutter项目shorebird flutter doctor这个命令会检查Flutter环境、Android SDK、XcodemacOS等前置条件也顺便验证CLI能不能正常调用Flutter工具链。这一步有问题的话先解决环境问题再往下走。3. 项目接入改造Flutter工程的最小步骤Shorebird的接入比我想象中轻量核心就几步初始化、加配置、改入口、跑通验证。但细节不少尤其是双端原生的微小改动容易漏。3.1 执行shorebird init生成配置文件在Flutter项目根目录执行shorebird init命令执行完会生成一个shorebird.yaml内容类似这样app_id: 你的应用唯一ID version: 0.1.0这个app_id等同于你在这个Shorebird账号下的应用身份标识以后所有补丁、发布、渠道管理都以它为单位。它对应的是Shorebird后台创建的一个应用而不是你Android的applicationId或iOS的Bundle Identifier这个概念要区分清楚。如果项目之前没有关联过Shorebird后台init的时候会自动创建。如果你的App已经在线上运行后来又决定接入Shorebird那么第一次init时要选择“关联已有应用”不要新建否则后台里会出现两个相同包名的应用后续管理会乱。3.2 原生入口的改动Android和iOS各一行Shorebird的SDK需要一个初始化调用来启动补丁拉取逻辑。改动位置分别在Android端在MainActivity的onCreate里加package com.example.app import io.flutter.embedding.android.FlutterActivity import com.shorebird.engine.Shorebird class MainActivity : FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { Shorebird.init(this) super.onCreate(savedInstanceState) } }iOS端在AppDelegate.swift的didFinishLaunchingWithOptions里加import UIKit import Flutter import ShorebirdEngine main objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) - Bool { Shorebird.initialize() GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }核心逻辑都一样在Flutter引擎启动之前先让Shorebird把本地已有的补丁产物准备好这样引擎加载Dart代码时拿到的就已经是补丁后的版本。3.3 本地验证先跑通一个“假补丁”接入完毕后我强烈建议在本地做一次完整的模拟验证而不是直接上生产。具体做法代码里写一个明显的标识比如首页显示一个版本号字符串然后用shorebird patch把版本号改掉再shorebird preview看一下效果确认补丁确实是生效的。# 本地生成补丁 shorebird patch android -- --dart-defineENVdev # 预览效果这个命令只在你本地构建的App里生效 shorebird previewpreview是Shorebird提供的本地预览机制它会把补丁内容注入到本地App中让你在不上线的情况下验证修改内容是否符合预期。这个功能相当实用尤其适合团队协作时给测试同学验证。3.4 检查清单接入阶段最容易漏的三件事pubspec.yaml中确保shorebird相关依赖在release模式也保留不要随手用了kDebugMode包裹导致release分支不执行初始化。Android的proguard混淆规则。如果你的release构建开了代码混淆需要给Shorebird SDK的包名加上keep规则否则运行时反射调用会直接崩溃。iOS的ATSApp Transport Security。Shorebird补丁下载走的是HTTPS一般没影响但如果你的内网打包机强制改了ATS配置要注意别把Shorebird的域名也禁了。4. 发布第一个Release把基线版本部署到线上Shorebird的热更新不是凭空来的它必须基于一个“基线release”。也就是说你先用Shorebird发布一个带补丁能力的版本到应用商店之后所有线上Bug都用补丁修而不是重新发版。4.1 用shorebird release代替普通的flutter build发布基线版本时不要再用flutter build apk或flutter build ipa这种命令而是用# Android shorebird release android -- --dart-defineAPI_BASE_URLhttps://api.example.com # iOS必须在macOS上执行 shorebird release ios -- --dart-defineAPI_BASE_URLhttps://api.example.comshorebird release的背后逻辑是构建出标准的Flutter应用产物同时生成一份“构建记录”上传到Shorebird后台记录里包含了当前Dart代码的hash、关联的Flutter版本、渠道信息等。之后发布的每一个补丁都基于这份记录做差异。命令执行完后产物会在正常的位置输出Android是build/app/outputs/flutter-apk/iOS需要配合Xcode做Archive。直接用这份产物上架应用商店即可。4.2 channel是什么先理解再使用Shorebird引入了一个叫channel渠道的概念它对应的是App内的一个字符串标识。默认情况下有三个内置渠道stable、beta、development。channel的设计意图是同一个release版本不同渠道的用户可以接收到不同的补丁。这在灰度场景下特别好用。举例来说stable渠道是正式用户beta渠道是内部测试development渠道是开发联调发布补丁时指定哪个渠道只有该渠道的App才会拉取到补丁。这就解决了“全量上线怕出事”的核心痛点。4.3 Android和iOS发布时的签名问题这是双端差异最大的环节。Android侧shorebird release android默认用debug签名你必须改成release签名。要么通过--build-number和--build-name配合Gradle配置要么在shorebird.yaml里指定签名信息。实际项目中我推荐直接依赖Gradle自带的签名配置再在命令里传-- --release参数这样最省心。iOS侧shorebird release ios依赖Xcode的Archive流程证书、描述文件都得提前配置好。有一个坑如果你用自动管理签名Xcode可能生成一个新的描述文件导致Shorebird后台记录的签名信息和实际App Store上用的不一致进而导致后续补丁校验失败。稳妥做法是手动选择固定的开发者证书和描述文件并且每次发布都用同一套。5. 紧急Bug场景从修改代码到补丁上线的完整链路纸上谈兵没用真正检验方案的是线上出Bug的时候。我拿一个半年前的真实案例来演示完整链路。5.1 问题复现与定位当时线上报了一个问题Android端部分用户点击“我的订单”直接闪退iOS端没有这个问题。通过Crash日志定位到是某个机型上Dart层解析订单时间字段时出现了空字符串转换为DateTime的异常。复现路径很清晰代码改动也很小就一个方法。5.2 修改代码并生成补丁在修复代码合入主干后我直接在release分支上修改了这个方法然后执行# 打Android补丁只推给beta渠道做内验 shorebird patch android --channelbeta -- --dart-defineAPI_BASE_URLhttps://api.example.com命令运行期间Shorebird会先构建一个新的Dart产物再和基线版本做diff生成一个增量补丁包。补丁包的内容只有那一个方法的差异体积通常只有几KB到几十KB用户侧下载几乎无感。补丁生成后我让测试同学用beta渠道的包验证线上场景确认问题解决。然后把同一个补丁推到stable渠道shorebird patch android --channelstable -- --dart-defineAPI_BASE_URLhttps://api.example.com就这样从定位问题到全量修复前后不到半小时。如果走传统发版流程等提审、等审核、等用户更新48小时都算快的。5.3 iOS的补丁发布有一点点不同iOS侧修同一个Bug命令长这样shorebird patch ios --channelstable -- --dart-defineAPI_BASE_URLhttps://api.example.com逻辑是一样的但有个差异点值得注意iOS补丁的生成和验证依赖Apple的开发环境如果你的机器上没有正确配置证书和描述文件命令会在签名环节报错。而且iOS补丁的下载时机比Android更受限制因为iOS的App在后台运行时系统会限制网络请求补丁往往要等App恢复到前台后才会真正完成下载和切换。Android的补丁下载更激进一些体验上会更“无感”。5.4 补丁发布后的验证不要看了一眼就完事补丁推上去以后验证工作不能省。我的习惯是用shorebird preview在本地模拟一个用户环境验证补丁逻辑正确。在beta渠道小范围放量观察Crash率、接口错误率等指标。确认无误后再推stable。如果你在同一个发布周期内连续打了多个补丁要注意补丁之间是叠加关系不是替换关系。Shorebird内部会根据Dart代码的hash来决定加载哪个补丁如果两个补丁改了同一个文件后生成的会覆盖前面的但这个覆盖不一定符合你的预期所以同一周期内尽量控制补丁数量改完一批统一发布。6. iOS与Android差异逐个拆解审核、签名、产物与渠道标题里专门点了iOS/Android差异解析这块我用一个表格先做整体对比然后逐个细说。对比维度AndroidiOS命令执行环境Windows/Linux/macOS都可以必须在macOS上执行补丁产物形式替换libapp.so中的Dart快照替换App.framework中的Dart快照签名要求需要配置Gradle签名信息需要Apple开发者证书和描述文件审核影响补丁发布无需应用商店审核补丁发布无需重新提审但仍受Apple审核条款约束补丁下载时机相对灵活后台也可以下载受限较多通常需App恢复前台后完成渠道管理原生支持多渠道配置原生支持但端上无法直接切换渠道只能服务器端指定灰度能力channel维度灰度channel维度灰度能力一致回滚方案发布新补丁覆盖或关闭补丁下发同左但覆盖速度受下载时机影响6.1 审核策略差异热更新不等于可以无视规则先说结论Shorebird的补丁发布不需要重新经过应用商店提审。Android这边不用说任何渠道都能随时推送。iOS这边Apple的审核条款对“代码热更新”一直比较敏感但Shorebird使用的方案是在“开发者自有账户下更新应用内代码”并且只更新Dart层不涉及原生接口动态调用目前的实操中不会触发审核。不过Apple的条款随时可能收紧所以我的建议是不要用补丁推送一些明显违反审核指南的功能比如偷偷收集用户隐私、绕过内购等。保持App描述文件和隐私政策文档的完整性避免被抽查时发现和线上版本不一致。每次iOS补丁发布前看一眼改动的代码是不是只涉及UI和业务逻辑别为了省事把一些灰色逻辑也推上去。6.2 补丁下载时机为什么iOS感觉“慢半拍”技术底层上iOS的App在后台会被系统挂起网络请求的优先级被压得很低补丁下载往往要等用户主动打开App并停留几秒后才会触发。Android这边没有这么严格的限制后台服务可以在一定程度上继续下载。这个差异导致的直接后果是如果你用iOS做A/B实验或者运营活动需要提前把补丁推到线上给用户留出下载时间而不是等到活动开始那一刻才推。6.3 签名机制一个细节影响整个发布流程Shorebird的补丁在生成时会基于基线release的签名信息做校验。Android端如果发布release时用的签名文件和后来打补丁时用的不一致补丁会被客户端拒绝。iOS同理如果基线的描述文件和后续补丁的场景不匹配也可能出现校验失败。我团队的规范是在shorebird.yaml里写明Android的keystore路径并且每次patch前检查签名配置是否被改动。iOS的证书和描述文件放到CI的钥匙串管理里保证每次构建用的都是同一套。6.4 渠道在双端的使用建议虽然两端能力一致但我的实际感受是Android端用起来更“随意”因为Android包分发渠道多样测试包、灰度包、正式包可以分开管理。iOS端由于只能通过TestFlight和App Store分发渠道间的切换通常依赖于后台配置端上不能动态变所以尽量把iOS的渠道设计得简单一点例如只有stable和beta两个就够了太多反而增加管理成本。7. 灰度发布和线上回滚Shorebird channel的生产实践光能发补丁还不行生产环境必须考虑灰度策略和出问题后的回滚手段否则一次补丁事故就足够把整个团队打回原形。7.1 一套可落地的灰度流程我现在的流程是这样设计的开发渠道验证代码改完后先打development渠道补丁开发自己手机上验证。beta渠道小规模验证通过后台把测试账号切到beta渠道测试同学验证业务功能和核心链路。stable渠道逐步放开stable渠道不打全量分批次放开比如先10%用户再50%最后全量。后两步的实现方式Shorebird本身没有提供百分比放量能力需要在业务侧配合通过在App启动时记录本地标识或者在请求接口时由服务端动态下发渠道标识控制哪些客户端去拉取新补丁。最简单的做法是不同渠道使用不同的补丁服务端按比例下发渠道标识。7.2 回滚方案三种手段按需选择回滚分三个层级发布一个“反补丁”如果新补丁本身有Bug直接改代码生成一个新补丁覆盖上去让用户回到上一个稳定逻辑。这是最灵活的方式前提是你的代码仓库能快速切到上一个可用的业务状态。关闭补丁下发在Shorebird后台把对应渠道的补丁停掉已经拿到补丁的用户不受影响但新拉取的客户端会回到基线release版本。这个方案适合“不想继续扩散问题”的场景。强制App版本更新如果问题已经严重到补丁都无法解决例如原生层Bug只能请求用户升级App。Shorebird在这里帮不上忙但你可以提前把这套提示逻辑做在App里避免事故发生时手足无措。7.3 版本联动让App知道自己在哪个补丁上生产环境排查问题时你往往需要知道“某个用户的App当前跑的是哪个补丁版本”。Shorebird SDK提供了获取当前补丁版本号的接口我建议把它展示在设置页的“关于”里同时上报到日志体系。import package:shorebird_code_push/shorebird_code_push.dart; final codePush ShorebirdCodePush(); final currentPatch await codePush.currentPatchNumber(); // 上报到日志平台这样线上反馈问题时客服和技术支持能第一时间拿到准确版本不用再靠猜。8. 我踩过的几个坑和一些经验总结最后这一部分分享几个实际开发中遇到过的坑。不一定每个项目都会碰到但碰到了往往就会卡住半天。8.1 “unable to find suitable visual studio toolc”这类环境报错这个问题看起来是Flutter环境问题和Shorebird无关但接入Shorebird后出现概率更高因为它会重新触发一次完整构建。解决方案是检查Visual Studio的C工具链是否安装完整Windows上还需要安装最新的Windows SDK。macOS上对应的则是Xcode Command Line Tools是否齐全。8.2 补丁内容过大用户下载超时Shorebird的补丁虽然只包含差异内容但如果你在版本间改了大量资源文件图片、字体等补丁包可能会膨胀到几十MB。这时候下载体验就不好了。我的建议是资源文件能不走补丁就不走补丁图片字体等资源尽量通过CDN动态下发Dart层只负责渲染这样补丁永远保持在几KB到几百KB的合理范围。8.3 千万不要在补丁里改原生插件前面说过Shorebird只能更新Dart代码。但我第一次给同事培训时特别强调改原生插件的调用方式可以改插件版本不行。比如你从shared_preferences 2.0升级到2.3这个改动即使在Dart层看起来只是多传了一个参数但它依赖的原生代码变了补丁并不能覆盖必须重新发版。这条规则一定要写进团队的开发规范里。8.4 免费版限额和商业化考量Shorebird有免费版和商业版免费版每月有补丁数量、渠道数量、应用数量等限制。对于个人开发者或小团队免费版基本够用但团队协作的话要注意如果每个成员每天打几个补丁很容易触顶。建议在CI里统一管理patch流程避免开发环境频繁打补丁浪费额度。8.5 关于Flutter版本升级的连锁反应只要你还在用Shorebird就不能像以前那样随心所欲升级Flutter。每次Flutter大版本升级都要等Shorebird跟进适配。我的策略是Flutter版本以“稳定优先”为原则优先选择Shorebird官方明确支持的版本不要追新。回到开头那个场景App线上崩溃提审要等两天群里用户已经开骂。接入Shorebird之后这个问题变成了“定位Bug、改一行代码、跑一下patch、吃饭回来已经全量修复”。热更新不是银弹但有这么一条救火通道至少能让团队在突发事故面前淡定很多。如果你正在犹豫要不要上建议先拿一个小业务模块试点跑通完整流程后再逐步铺开。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询