Flutter: CI/CD 自动化
手动发布是人类错误的温床——自动化让每一步可重复、可追溯、可信任。
📋 前置知识:需要先掌握以下内容
- 第23课:Web 与 Desktop 多平台部署
1. 你将学到
- GitHub Actions 工作流:flutter test / flutter build 多矩阵构建
- Code Signing 自动化:Android keystore / iOS 证书与 Provisioning Profile 管理
- Fastlane 集成:自动截图 / 自动上传 TestFlight / Play Console
- Codemagic / Bitrise 云端 CI 方案对比
- ShopApp:GitHub Actions 全平台自动构建 + Slack 通知 + 自动 Tag 发布
2. 一个手动发布灾难的真实故事
(1) 痛点:手动发布 4 小时 + 3 次返工
Bob 每次发布 ShopApp 新版本需要:手动 flutter build → 签名 → 上传 Play Console → 截图 → 填写发布说明 → 提审。iOS 流程更复杂:Archive → 导出 IPA → 上传 App Store Connect → 等审核。整个过程 4 小时,至少返工 2 次(忘记改版本号 / 签名配置错 / 截图不合规)。每次发版,Bob 都要加班到深夜。
(2) CI/CD 流水线的解法
GitHub Actions 自动化:推送 tag → 自动测试 → 自动构建 → 自动签名 → 自动上传 → Slack 通知。全程无需人工干预。
YAML
# Push tag v1.2.3 → automatic release pipeline
on: push: tags: ['v*']
# → flutter test → flutter build → sign → upload → notify
(3) 收益:1 小时发版 + 零返工
Bob 建立 CI/CD 后,发版从 4 小时降到 1 小时(主要是审核等待),返工率从 60% 降到 0%。
3. CI/CD 流水线全景
graph LR
subgraph GitHub Actions Pipeline
P[Push / PR] --> LINT[Lint & Analyze]
LINT --> TEST[flutter test]
TEST --> BUILD[Build APK/IPA/Web]
BUILD --> SIGN[Code Signing]
SIGN --> DIST[Distribute]
DIST --> |Slack| NOTIFY[Team Notification]
end
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
(1) CI/CD 方案对比
| 方案 | 价格 | 平台支持 | 配置方式 | 适用场景 |
|---|---|---|---|---|
| GitHub Actions | 免费(2000min/月) | 全平台 | YAML | 首选 |
| Codemagic | 免费(500min/月) | 全平台 | YAML | macOS 构建更好 |
| Bitrise | 免费(200min/月) | 全平台 | UI+YAML | iOS 专项 |
| Fastlane | 免费(本地) | iOS/Android | Ruby | 本地自动化 |
| Cirrus CI | 免费(开源) | 全平台 | YAML | 社区项目 |
4. GitHub Actions 工作流
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:PR 检查工作流
YAML
# .github/workflows/pr-check.yml
name: PR Check
on:
pull_request:
branches: [main]
jobs:
analyze-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
channel: 'stable'
- name: Install dependencies
run: flutter pub get
- name: Analyze code
run: flutter analyze --no-fatal-infos
- name: Run unit and widget tests
run: flutter test --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: coverage/lcov.info
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:多矩阵构建工作流
YAML
# .github/workflows/build.yml
name: Build All Platforms
on:
push:
tags: ['v*']
jobs:
build-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- run: flutter test
- name: Build APK
run: flutter build apk --release
- name: Build AAB
run: flutter build appbundle --release
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
- name: Upload to Play Console
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT }}
packageName: com.shopapp.app
releaseFiles: build/app/outputs/bundle/release/app-release.aab
track: production
build-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- name: Install CocoaPods
run: cd ios && pod install
- name: Build IPA
run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist
- name: Upload to App Store Connect
uses: apple/upload-testflight@v1
with:
app-path: build/ios/ipa/shopapp.ipa
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
build-web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- run: flutter build web --wasm
- name: Deploy to Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
notify:
needs: [build-android, build-ios, build-web]
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: slackapi/slack-github-action@v1
with:
payload: |
{"text": "ShopApp ${{ github.ref_name }} released successfully!"}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
5. Code Signing 自动化
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:Android 签名配置
YAML
# GitHub Secrets needed:
# KEYSTORE_BASE64: base64 encoded .jks file
# KEY_PASSWORD: keystore password
# KEY_ALIAS: key alias
- name: Decode keystore
run: echo "$KEYSTORE_BASE64" | base64 --decode > android/app/keystore.jks
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
GROOVY
// android/app/build.gradle
android {
signingConfigs {
release {
storeFile file('keystore.jks')
storePassword System.getenv("KEY_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
6. Fastlane 集成
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
:Fastlane 配置
RUBY
# ios/fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Build and upload to TestFlight"
lane :beta do
build_app(
workspace: "Runner.xcworkspace",
scheme: "Runner",
export_method: "app-store",
)
upload_to_testflight(
skip_waiting_for_build_processing: true,
)
end
desc "Take screenshots"
lane :screenshots do
capture_screenshots(
scheme: "Runner",
devices: ["iPhone 15 Pro", "iPad Pro (12.9-inch)"],
)
end
end
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
RUBY
# android/fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Build and upload to Play Console"
lane :beta do
gradle(task: "bundleRelease")
upload_to_play_store(
track: "internal",
aab: "../build/app/outputs/bundle/release/app-release.aab",
)
end
end
TEXT
📖 仅展示
> **输出:** 在本地 Flutter SDK(Flutter 3.x / Dart 3.x)运行。Piston 服务器未安装 Flutter,请在本机 `flutter run` 实操对照。实际 UI/状态会因平台略有差异。
7. 完整示例:ShopApp CI/CD 配置
YAML
# .github/workflows/release.yml
name: Release ShopApp
on:
push:
tags: ['v*.*.*']
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x', channel: 'stable' }
- run: flutter pub get
- run: flutter analyze --no-fatal-infos
- run: flutter test --coverage
- uses: codecov/codecov-action@v3
build-android:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- name: Decode keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks
- name: Build AAB
run: flutter build appbundle --release
env:
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
- name: Upload AAB artifact
uses: actions/upload-artifact@v4
with: { name: app-release-aab, path: build/app/outputs/bundle/release/app-release.aab }
build-ios:
needs: test
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- run: cd ios && pod install
- name: Build IPA
run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist
- uses: actions/upload-artifact@v4
with: { name: app-release-ipa, path: build/ios/ipa/*.ipa }
build-web:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- run: flutter build web --wasm --base-href "/app/"
- uses: actions/upload-artifact@v4
with: { name: web-release, path: build/web/ }
release:
needs: [build-android, build-ios, build-web]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
draft: false
- name: Slack notify
uses: slackapi/slack-github-action@v1
with:
payload: '{"text": "ShopApp ${{ github.ref_name }} released to all platforms!"}'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
❓ 常见问题
Q GitHub Actions 免费额度够用吗?
A 开源项目无限制。私有项目 2000 分钟/月,一次全平台构建约 30-40 分钟,每月约 50 次构建。
Q iOS 构建必须用 macOS runner 吗?
A 是的,Xcode 只能在 macOS 上运行。GitHub 提供 macOS runner,但计费为 Linux 的 10x。可用 Codemagic 节省 macOS 分钟。
Q 签名密钥怎么安全存储?
A 用 GitHub Secrets(Base64 编码存储 keystore/证书),绝不提交到代码仓库。启用 Secret 扫描。
Q Flutter 构建缓存怎么加速?
A 用
actions/cache 缓存 pub-cache 和 gradle-cache,构建时间从 5 分钟降到 2 分钟。Q Fastlane 和 GitHub Actions 怎么配合?
A GitHub Actions 触发构建,Fastlane 处理签名和上传。Fastlane 在 macOS runner 上运行最方便。
Q 怎样回滚线上版本?
A Google Play Console 支持回滚到上一版本;App Store 需要提交新版本。CI/CD 应保留所有历史构建产物。
📖 小节
- GitHub Actions 是 Flutter CI/CD 首选,YAML 配置 + 多矩阵构建
- Android 签名用 Base64 编码 keystore 存 GitHub Secrets
- iOS 构建必须 macOS runner,证书存 Secrets
- Fastlane 自动化截图、上传 TestFlight/Play Console
- 完整流水线:测试 → 构建 → 签名 → 上传 → 通知
📝 作业
- 基础题(难度⭐):配置 GitHub Actions PR 检查工作流:flutter analyze + flutter test。
- 进阶题(难度⭐⭐):添加 Android AAB 构建步骤,用 GitHub Secrets 管理签名密钥,构建后上传 artifact。
- 挑战题(难度⭐⭐⭐):实现完整 CI/CD:PR 检查(lint+test) + Tag 触发全平台构建(Android AAB + iOS IPA + Web) + 自动签名 + GitHub Release + Slack 通知。