Flutter 生产环境打包记录

Sep 14·6 min
AI 生成的摘要
记录商业 IM 软件的 Flutter 打包脚本:Release 模式、dart-define 后端参数、Xcode 生成配置残留,以及 IPA 导出检查。

最后整理一下商业 IM 软件的 Flutter 打包脚本,主要是 Android 和 iOS。

平时用 flutter run 比较多,正式打包还得多看几个地方:这次编译选了哪个后端,Xcode 有没有沿用之前的参数,最后生成的是归档还是要交付的 IPA。下面按脚本的处理顺序记一下。

Debug、Profile、Release 分别解决什么问题

Flutter 的构建模式会影响调试、断言、优化和性能分析能力。Debug 适合开发和热重载,Profile 适合性能分析,Release 面向发布。性能判断应当使用真实设备上的合适模式,而不是直接用 Debug 的表现代表发布包。参见 Flutter 构建模式

在移动端,Dart 的开发体验与 JIT、增量编译相关,发布时则可以通过 AOT 提前生成机器码。JIT 和 AOT 解决的是编译执行方式,不是测试后端与生产后端的选择。参见 Dart 平台说明

也就是说,--release 不会替业务代码判断应该连接哪个服务器。构建模式和业务环境是两个维度。

同时,Flutter 包里还涉及原生宿主、插件、资源和平台打包过程。Dart 编译结束以后,Android 的构建工具和 iOS 的 Xcode 仍然有自己的工作,最后还涉及签名与分发产物。

用编译参数固定这次产物的环境

项目的两个生产构建脚本分别使用下面的命令:

snippet
bash
flutter build apk --release --dart-define=APP_BACKEND_ENV=prod
snippet
bash
flutter build ipa --release --dart-define=APP_BACKEND_ENV=prod

它们是实际脚本里的构建行,执行时还需要各自的依赖、签名和平台环境。

App 通过常量环境声明读取这个值:

snippet
dart
static const String backendEnv = String.fromEnvironment(
  EnvState.backendEnvKey,
  defaultValue: kDebugMode ? EnvState.backendDev : EnvState.backendProd,
);

Dart 的编译环境声明与运行时操作系统环境变量不是同一机制;Flutter 可以通过 --dart-define 传入这些值。参见 Dart 编译环境声明

这种做法让环境选择成为构建输入,不需要打包前手动修改请求地址,再记得改回去。

它也不等同于把参数隐藏起来。环境标识适合放在这里,但不应该把编译参数当成保存客户端秘密的保险箱。

默认值是一项项目策略

当前实现按是否为 Debug 选择默认值,并对显式参数继续判断:

输入Debug 的后端选择Release/Profile 的后端选择
未注入测试生产
prod生产生产
dev测试测试
其他值或空串测试生产

这里说的是 Dart 层的选择,后面还有 iOS 构建门禁。

项目希望避免正式产物因为漏传或拼错参数,意外连到测试后端,于是让 Release/Profile 的未知值回落生产。

这不是适用于所有项目的唯一策略。未知值直接阻断构建,也是一种选择;回落生产意味着拼写错误仍可能产出一个连接生产的包。选择哪种方式,需要结合团队的发布约束,并让构建日志或产物信息能够说明最终选择。

为什么直接去 Xcode Archive 还要检查一次?

有一条打包路径需要注意:联调时传入测试环境参数,Flutter 生成了 iOS 构建配置;随后直接从 Xcode 归档,可能继续沿用之前生成的参数。

因此,代码仓库里没有修改服务器地址,不代表当前机器的生成配置一定符合这次发布意图。

项目给 Runner 加了 Verify Backend Environment 构建阶段,在运行 Flutter 构建脚本之前检查 DART_DEFINES。它会解码逗号分隔的值,在 ReleaseProfile 配置下遇到精确的 APP_BACKEND_ENV=dev 时失败退出。

这个检查有明确范围:它识别的是这两个确切配置名和这个精确参数。未来增加自定义配置名称时,需要一起检查规则是否仍然覆盖。

它也不是拒绝所有未知输入的通用校验器。未注入或未知值允许继续,由前面的 App 选择规则处理。

这样从 Xcode 直接归档时,也能挡住残留的 dev 参数。

clean 能清理什么,不能证明什么?

当前 Android 和 iOS 脚本都会执行 flutter clean,然后获取依赖。Android 还清理 Gradle 构建,iOS 执行 pod install,最后显式注入生产参数进行打包。

这是项目对生成状态的处理方式,不意味着每次日常开发都必须全部清理,也不能把 clean 当成解决任何构建失败的通用答案。

Flutter 构建、原生依赖、签名配置属于不同层次。如果失败发生在证书或导出阶段,重新编译 Dart 并不能替代相应配置。

同样,清理后能构建,不代表构建已经可复现。可复现还需要明确 SDK、原生工具链、依赖锁文件、构建参数和签名条件。当前脚本本身没有锁住所有这些输入。

归档成功,为什么还要检查 IPA?

iOS 的归档和分发文件需要分开看。Flutter 官方文档说明 flutter build ipa 会生成归档和 IPA,分别位于 build/ios/archive/build/ios/ipa/。参见 iOS 构建与发布

项目的 iOS 脚本在构建前检查分发证书,缺少时提示,但不会立即退出。构建命令结束后,还会查找 IPA;没有找到就按失败处理。

这体现了一个实际的验收标准:准备交付 IPA 时,只看到归档不够,还要确认需要的文件确实产生了。

不过“找到了一个 IPA”仍然只是产物存在性检查,不等于已经验证签名、安装、版本号和运行时后端。项目脚本也没有逐项验证归档中所有符号文件。调试符号还需要另外检查。

留下构建记录

如果需要进一步完善交付,我会让每次构建记录对应代码版本、模式、业务环境、工具版本和产物位置,再用实际安装包核对版本与启动后的环境。

交付前还得安装一次实际生成的包,核对版本和后端环境。脚本找到了 IPA,只能说明文件存在,不能代替这一步。

最后修改时间: Sep 15
cd ..