CI/CD 与发布
Codemagic、Fastlane 与商店。
CI/CD 与发布
这一课你将能做到
- 给 Android App 配置签名并打出 release APK / AAB
- 给 iOS App 配置证书和描述文件,打出 IPA 并上传到 App Store Connect
- 用 GitHub Actions 或 Codemagic 写 CI/CD 流水线:push 后自动跑测试、打 release 包、上传到 Firebase App Distribution / TestFlight
为什么要学
代码写得再好,打不出包、上不了架就是零。签名、版本号、CI 是每个上架 App 都绕不开的硬技能,也是中高级 Flutter 工程师的分水岭。
人话:CI/CD 就是「按下按钮自动做菜端上桌」——你只需 push 代码,云端自动测试、打包、分发。
先建立图像
git push
│
▼
GitHub Actions / Codemagic
├── flutter test ← 跑测试
├── flutter build apk/aab ← 打 Android 包
├── flutter build ipa ← 打 iOS 包
└── 上传 → Firebase App Distribution / TestFlight / Play Console
跟着做
步骤 1:Android 签名
1.1 生成 keystore
keytool -genkey -v -keystore %USERPROFILE%\my-release.keystore ^
-alias myapp -keyalg RSA -keysize 2048 -validity 10000
macOS/Linux 用 ~/my-release.keystore。保存好这个文件和密码,丢失意味着永远无法更新已上架的 App(Google Play 提供 Play App Signing 可以救回,但麻烦)。
1.2 配置 Flutter 项目
新建 android/key.properties(不要提交 git):
storePassword=你的store密码
keyPassword=你的key密码
keyAlias=myapp
storeFile=C:/Users/leebf/my-release.keystore
.gitignore 加一行:android/key.properties 和 *.keystore。
编辑 android/app/build.gradle:
// 在 android { } 块之前
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
// ...
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
1.3 打 release 包
flutter build apk --release # 输出 build/app/outputs/flutter-apk/app-release.apk
flutter build appbundle --release # 输出 build/app/outputs/bundle/release/app-release.aab
上架 Google Play 必须用 AAB(Android App Bundle),Google 会按设备自动拆分。
步骤 2:iOS 签名与打包(macOS)
2.1 准备工作
- 加入 Apple Developer Program(个人 99 美元/年)
- 在 Xcode 里登录 Apple ID:Xcode → Settings → Accounts
- 打开
ios/Runner.xcworkspace,在 Signing & Capabilities 选 Team,让 Xcode 自动管理证书
2.2 设置 Bundle ID 和版本号
ios/Runner.xcodeproj/project.pbxproj 中的 PRODUCT_BUNDLE_IDENTIFIER 改成一个全球唯一的反向域名,如 com.example.myapp。
version: 1.2.0+5 # 1.2.0 是 versionName,5 是 versionCode
2.3 打包并上传
flutter build ipa --release
# 输出 build/ios/ipa/*.ipa
打开 Xcode → Window → Organizer → 选中刚构建的 archive → Distribute App → App Store Connect → Upload。或者用 Transporter App 直接拖 ipa 上传。
步骤 3:版本号管理
Flutter 版本号规则 version: x.y.z+buildNumber:
x.y.z:展示给用户的版本名(semver)
+buildNumber:单调递增的构建号,每次上传商店必须比上次大
常用技巧:CI 中用 git commit 数量当 buildNumber:
BUILD_NUMBER=$(git rev-list --count HEAD)
flutter build apk --release --build-number=$BUILD_NUMBER
步骤 4:GitHub Actions CI
.github/workflows/flutter.yml:
name: Flutter 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'
cache: true
- run: flutter pub get
- run: flutter analyze
- run: flutter test
build-android:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.24.0' }
- run: flutter pub get
# 解码 keystore(base64 存在 secrets 里)
- run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/my-release.keystore
- run: |
cat > android/key.properties <<EOF
storePassword=${{ secrets.STORE_PASSWORD }}
keyPassword=${{ secrets.KEY_PASSWORD }}
keyAlias=myapp
storeFile=my-release.keystore
EOF
- run: flutter build appbundle --release
- uses: actions/upload-artifact@v4
with:
name: app-release
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.24.0' }
- run: flutter pub get
- run: flutter build ios --release --no-codesign # CI 上不签名,签名交给 fastlane
把 keystore base64 编码后存到 GitHub Secrets:
base64 -i my-release.keystore | pbcopy # macOS
# 在仓库 Settings → Secrets and variables → Actions 添加 KEYSTORE_BASE64
步骤 5:Codemagic(零配置 CI,专为 Flutter 设计)
- 打开 <https://codemagic.io>,用 GitHub 登录
- 选仓库,选 Flutter 模板
- 在 UI 里勾选:跑测试、构建 Android/iOS、上传到 Firebase App Distribution / TestFlight
- 提交后自动生成
codemagic.yaml,可视化编辑或手写:
workflows:
android-release:
name: Android Release
environment:
flutter: stable
scripts:
- flutter pub get
- flutter test
- flutter build appbundle --release
artifacts:
- build/app/outputs/**/*.aab
publishing:
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT
track: internal
步骤 6:分发给测试人员
- Android:Firebase App Distribution(
firebase appdistribution:distribute app-release.apk)或 Google Play 内部测试轨道
- iOS:TestFlight(自动从 App Store Connect 邀请)
讲清楚要点
- 签名是身份:Android keystore、iOS 证书,丢失 = 无法更新已上架 App。多人备份 + 密码管理器。
- AAB 替代 APK 上架 Google Play:体积更小、按需下发。
- 版本号双轨:versionName 给用户看,versionCode 给系统看,code 必须单调递增。
- CI 的核心是自动化测试 + 打包:本地能跑通的命令,CI 就能跑通;本地跑不通,CI 一定挂。
- 敏感信息进 Secrets:keystore、密码、API Key 永远不进 git。
你可能会懵的地方
-
debug 包能直接上架吗?
不能。debug 包没签名(用的是 debug.keystore)、没混淆、性能差。商店只接受 release。
-
为什么 iOS 构建要 macOS?
签名依赖 Keychain 和 Xcode 工具链,仅 macOS 提供。Windows 用户可以靠 Codemagic / GitHub Actions macos runner。
-
fastlane 还需要吗?
简单场景 Codemagic/GitHub Actions 够了;复杂证书管理、多 Target、自动截图上架才上 fastlane。
-
怎么热更新?
Flutter 官方不允许下发可执行代码(违反 Apple 政策)。可用的方案是 Shorebird(Code Push),但需评估合规风险。
-
R8/ProGuard 会误删代码吗?
会,特别是反射调用的类。在 proguard-rules.pro 里加 keep 规则;用 Flutter 时大部分常用插件已经处理好。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 把 keystore 提交到 git | 任何人都能伪造你的 App | 加 .gitignore,密码进 Secrets |
| versionCode 不递增 | 商店拒绝上传 | 每次发布自动 +1,CI 用 commit 数生成 |
| 用 debug 包上架 | 商店直接拒绝 | flutter build appbundle --release |
| 不上架前真机测试 | 真机才暴露的 bug 全漏 | 至少在 release 模式下跑一遍 flutter run --release |
| iOS Bundle ID 用默认 com.example | 无法上架 | 改成自己域名的反向形式 |
卡点急救
flutter build apk 报签名错:检查 key.properties 路径、密码、alias;Windows 路径用正斜杠 C:/...。
- iOS 报 "No signing certificate":Xcode → Settings → Accounts → 下载证书;或勾选 "Automatically manage signing"。
- GitHub Actions 上 iOS 构建失败:iOS 构建必须在
macos-latest runner 上跑。
- AAB 上传到 Play Console 报签名不一致:你之前上传的 App 用了别的 keystore。如果开了 Play App Signing,可以联系 Google 重置;否则无解,只能换包名重新上架。
- 构建很慢:CI 里开启 flutter-action 的
cache: true;本地用 --split-debug-info 减少包体积。
面试常问
-
Android 签名的作用?
证明 App 的发布者身份,确保更新包来自同一开发者。同一 App 的所有版本必须用同一 keystore 签名。
-
AAB 和 APK 的区别?
AAB 是发布格式,包含所有 ABI/语言/屏幕密度资源;Google Play 按设备动态生成最优 APK。体积更小、下发更智能。
-
CI/CD 在 Flutter 项目里的关键步骤?
检出代码 → 装 Flutter SDK → pub get → analyze → test → build release → 上传 artifacts → 分发(Firebase App Distribution / TestFlight / Play Console)。
-
iOS 上架要准备什么?
Apple Developer 账号、Bundle ID、App Store Connect 创建 App、图标/截图/隐私清单、签名证书、IPA 上传、审核(一般 24-48 小时)。
自测三问
- 为什么 keystore 文件不能丢?丢了有什么后果?
version: 1.2.0+5 里 1.2.0 和 +5 分别是什么?哪个必须递增?
- 写一段 GitHub Actions workflow:push 到 main 时跑 flutter test 和 flutter build appbundle。
小结一句话
发布 = 签名 + 版本号 + 打包;CI/CD = 自动化测试 + 云端打包 + 一键分发。GitHub Actions 适合通用,Codemagic 适合 Flutter 专属。
延伸