CI/CD:自动构建与分发

移动端的 CI 比其他项目多两道坎:构建环境要装 Flutter 与 Android SDK,iOS 还必须有 macOS 机器与签名证书。把这两件事解决后,CI 的价值立刻体现出来——每次合并自动跑测试、出包、分发到内测渠道,发版从“提心吊胆两小时”变成“点一下等十分钟”。本章以 GitHub Actions 为主线,给出可用的工作流模板与常见坑。

CI/CD 要解决的四件事

环节目标落地手段
质量门禁提交即验证,坏代码不合并flutter analyze + flutter test
构建产出可安装包flutter build apk/appbundle/ipa
分发让测试同学拿到包Firebase App Distribution、TestFlight、内网下载页
通知结果可达群机器人 webhook、CI 状态徽章

发布到应用商店不是必须自动化的目标:Google Play 有 API(fastlane supply、Gradle Play Publisher)可以自动提审,App Store 也支持 fastlane deliver,但首次上架与隐私问卷仍需人工处理。

GitHub Actions 工作流

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'   # 锁定版本,避免上游升级导致构建突然失败
          channel: stable
          cache: true                 # 缓存 Flutter SDK 与 pub 依赖
      - run: flutter pub get
      - run: flutter analyze --fatal-infos
      - run: flutter test --coverage
      - uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/lcov.info

  build-android:
    runs-on: ubuntu-latest
    needs: test                        # 测试不过就不出包
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '17'
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'
          cache: true
      - run: flutter pub get
      # 签名材料由 Secrets 注入,绝不能提交到仓库
      - run: |
          echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > android/upload-keystore.jks
          cat > android/key.properties <<EOF
          storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
          keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
          keyAlias=upload
          storeFile=../upload-keystore.jks
          EOF
      - run: flutter build appbundle --release --dart-define=ENV=prod
      - uses: actions/upload-artifact@v4
        with:
          name: android-release
          path: build/app/outputs/bundle/release/app-release.aab

要点:subosito/flutter-action 负责装 SDK,cache: true 能省下大半时间;needs: test 让构建依赖测试通过;产物用 upload-artifact 留档,配合内测分发步骤上传。

iOS 构建的额外要求

项目说明
Runner 类型必须 macos-latest,Linux 无法编译 iOS
证书与描述文件导出 .p12.mobileprovision,Base64 存 Secrets 后在 CI 里还原到 ~/Library/MobileDevice/Provisioning Profiles
CocoaPodsmacOS Runner 自带,必要时显式 pod install
出包命令flutter build ipa --release --export-options-plist=ExportOptions.plist
上传xcrun altool(旧)或 xcrun notarytool / Transporter,也可用 fastlane pilot 传 TestFlight

Codemagic 对 Flutter 更友好:内置 Flutter 镜像、有可视化的签名配置与 TestFlight 分发步骤,适合不想手写签名脚本的团队;代价是免费额度有限、依赖第三方平台。

版本号自动递增

# 用构建号覆盖 pubspec 里的 +N,保证每次上传的 versionCode/buildNumber 都递增
BUILD_NUMBER=${GITHUB_RUN_NUMBER}
flutter build appbundle --release --build-number=$BUILD_NUMBER
flutter build ipa --release --build-number=$BUILD_NUMBER

--build-number 会覆盖 pubspec.yamlversion: 1.0.0+1+ 部分,语义化版本号仍由人工在发版时提升。iOS 的构建号必须严格递增且不能重复上传,用 CI 的运行序号最稳妥。

内测分发与通知

渠道适用说明
Firebase App DistributionAndroid + iOS 内测有 Gradle/CLI 插件,可指定测试者邮箱组
TestFlightiOS 内测与灰度需上传 App Store Connect,外部测试要审核
蒲公英、fir.im 类平台国内 Android 分发上传即有二维码,适合快速发给测试同学
企业内网下载页自有渠道配合 Jenkins/CI 产物目录,需自己做权限

分发完成后用群机器人(企业微信、钉钉、飞书)发 webhook,消息里带上版本号、变更摘要与下载链接,比让人去 CI 页面找包高效得多。

CI 常见问题

问题原因处理
构建超时未缓存 SDK 与 Gradle 依赖cache: true,缓存 ~/.gradle,Android 用 --split-per-abi 减少产物
缓存失效频繁缓存 key 未绑定 Flutter 版本key 里带上 Flutter 版本号
本地能过 CI 失败Dart/SDK/JDK 版本不一致.fvmrc 或固定 flutter-version,JDK 固定 17
签名报错Secrets 未配置或 Base64 还原错误base64 -d 校验,临时打印文件大小
密钥泄露把 keystore 提交进仓库全部走 Secrets,仓库加 .gitignore 与扫描
iOS 只在发版时才发现问题iOS 构建未进 PR 流程至少每日跑一次 macOS 构建

常见坑

现象正确做法
不锁 Flutter 版本上游升级后构建突然失败flutter-version 固定,本地用 FVM 对齐
测试与构建放同一 Job一个失败全流程重跑拆 Job 并用 needs 串联
直接把密钥写进 yaml泄露即被冒名发版使用 Secrets,日志里也不要打印
只发 APK 不做渠道标记出问题无法回溯版本产物名带版本号与 commit 短哈希
忽略构建耗时每次提交等十几分钟缓存 + 并行 + 只在 main 分支出包

小结:CI 的最小闭环是“flutter analyzeflutter test 做门禁 → 按环境注入 --dart-define → 出 AAB/IPA → 上传内测渠道 → 群机器人通知”;GitHub Actions 用 subosito/flutter-actioncache: true 起步,iOS 必须用 macOS Runner 并把证书、描述文件、密钥全部托管为 Secrets;版本号用 --build-number 由 CI 注入保证递增,商店提审保留人工环节,缓存与版本锁定是让流水线稳定的两个关键。

笔记加载中…