Flutter 打包发布
按提示填写密钥库密码、姓名、组织等信息。生成后妥善保管 .jks 文件,丢失无法恢复。 在 android/ 目录下创建 key.properties(不要提交到 Git): 将 key.properties 加入 .gitignore: 编辑 android/app/build.gradle: flutter build appbundle --release 默认启用 R8 混淆。如果第三方库需要保留特定类,在 android/app/proguard-rules.pro 中添加规则: 在 build.gradle 中引用: 1. 在 Google
官方文档:https://docs.flutter.dev/deployment/android
适用版本:Flutter 3.x(2026-05-08 核实)
Android 打包
生成签名 Keystore
keytool -genkey -v \
-keystore ~/upload-keystore.jks \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias upload
按提示填写密钥库密码、姓名、组织等信息。生成后妥善保管 .jks 文件,丢失无法恢复。
key.properties 配置
在 android/ 目录下创建 key.properties(不要提交到 Git):
storePassword=你的密钥库密码
keyPassword=你的密钥密码
keyAlias=upload
storeFile=/Users/yourname/upload-keystore.jks
将 key.properties 加入 .gitignore:
android/key.properties
build.gradle 签名配置
编辑 android/app/build.gradle:
// 在 android {} 块之前加载 key.properties
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
}
}
}
打包命令
| 命令 | 说明 |
|---|---|
flutter build apk --release |
生成单一 APK(所有 ABI 合并) |
flutter build apk --release --split-per-abi |
按 ABI 分包(推荐,体积更小) |
flutter build appbundle --release |
生成 AAB,用于 Google Play |
flutter build apk --debug |
构建 Debug 包 |
flutter build apk --profile |
构建 Profile 包(性能分析) |
构建命令参数
| 参数 | 说明 |
|---|---|
--release |
发布模式,开启优化,禁用调试 |
--debug |
调试模式,包含调试信息 |
--profile |
性能分析模式 |
--split-per-abi |
按 CPU 架构拆分 APK(arm64-v8a、armeabi-v7a、x86_64) |
--obfuscate |
混淆 Dart 代码(需配合 --split-debug-info) |
--split-debug-info=<dir> |
将调试符号输出到指定目录,用于还原崩溃堆栈 |
--dart-define=KEY=VALUE |
注入编译时常量 |
--target-platform |
指定目标平台(android-arm、android-arm64、android-x64) |
# 完整发布示例
flutter build appbundle \
--release \
--obfuscate \
--split-debug-info=build/debug-info
混淆(R8)配置
flutter build appbundle --release 默认启用 R8 混淆。如果第三方库需要保留特定类,在 android/app/proguard-rules.pro 中添加规则:
# 保留所有 Flutter 插件
-keep class io.flutter.** { *; }
# 保留特定第三方库(示例)
-keep class com.google.gson.** { *; }
-keepattributes Signature
-keepattributes *Annotation*
在 build.gradle 中引用:
buildTypes {
release {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
App Bundle(AAB)上传 Google Play
- 在 Google Play Console 创建应用
- 进入「发布」→「正式版」→「创建新版本」
- 上传
build/app/outputs/bundle/release/app-release.aab - 填写版本说明,提交审核
iOS 打包
Apple Developer 账号配置
- 在 developer.apple.com 注册 Apple Developer Program(年费 $99)
- 在 Xcode 中登录账号:Xcode → Settings → Accounts → 添加 Apple ID
- 在 Apple Developer 后台创建 App ID(Bundle Identifier 需与
ios/Runner.xcodeproj中一致)
Xcode 证书与 Provisioning Profile
- 打开
ios/Runner.xcworkspace(必须用.xcworkspace,不是.xcodeproj) - 选择 Runner Target → Signing & Capabilities
- 勾选 Automatically manage signing,选择对应 Team
- Xcode 会自动创建证书和 Provisioning Profile
手动管理时需在 Apple Developer 后台创建:
- Distribution Certificate(App Store Distribution)
- App Store Provisioning Profile(关联 App ID 和证书)
打包命令
# 构建 IPA
flutter build ipa --release
# 指定 export options
flutter build ipa --release --export-options-plist=ExportOptions.plist
ExportOptions.plist 配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string> <!-- app-store / ad-hoc / development / enterprise -->
<key>teamID</key>
<string>XXXXXXXXXX</string> <!-- Apple Developer Team ID -->
<key>uploadSymbols</key>
<true/>
<key>compileBitcode</key>
<false/>
</dict>
</plist>
| 字段 | 可选值 | 说明 |
|---|---|---|
method |
app-store, ad-hoc, development, enterprise |
分发方式 |
teamID |
Team ID 字符串 | 苹果开发者团队 ID |
uploadSymbols |
true/false |
是否上传符号表用于崩溃解析 |
compileBitcode |
true/false |
新版 Xcode 已弃用,设为 false |
App Store Connect 上传
方式一:Transporter(推荐命令行场景)
xcrun altool --upload-app \
--type ios \
--file build/ios/ipa/Runner.ipa \
--username [email protected] \
--password app_specific_password
方式二:Xcode Organizer
- Xcode → Window → Organizer
- 选择对应 Archive → Distribute App
- 选择 App Store Connect → 按向导完成上传
版本管理
pubspec.yaml 版本号
version: 1.2.3+45
# ^^^^^ ^^
# versionName versionCode
versionName(1.2.3):显示给用户的版本号,对应 iOSCFBundleShortVersionString和 AndroidversionNameversionCode(45):内部递增整数,对应 iOSCFBundleVersion和 AndroidversionCode,每次发布必须递增
自动化版本号
在 CI/CD 中根据 Git tag 或构建号自动设置:
# 使用 Git tag 作为版本名,构建号使用 CI 环境变量
BUILD_NUMBER=${GITHUB_RUN_NUMBER:-1}
VERSION_NAME=$(git describe --tags --abbrev=0 2>/dev/null || echo "1.0.0")
flutter build appbundle \
--build-name=$VERSION_NAME \
--build-number=$BUILD_NUMBER
环境配置
--dart-define 传入环境变量
flutter run --dart-define=API_URL=https://api.example.com --dart-define=ENV=prod
flutter build apk --release --dart-define=API_URL=https://api.example.com
在 Dart 代码中读取:
const apiUrl = String.fromEnvironment('API_URL', defaultValue: 'http://localhost:3000');
const env = String.fromEnvironment('ENV', defaultValue: 'dev');
--dart-define-from-file(推荐用于多环境)
创建 .env.prod.json:
{
"API_URL": "https://api.example.com",
"ENV": "prod",
"APP_NAME": "MyApp"
}
flutter build apk --release --dart-define-from-file=.env.prod.json
将环境文件加入 .gitignore(含敏感信息时):
.env.*.json
flutter_dotenv 方案
# pubspec.yaml
dependencies:
flutter_dotenv: ^5.1.0
// main.dart
await dotenv.load(fileName: '.env');
// 读取
final apiKey = dotenv.env['API_KEY'] ?? '';
.env 文件需要在 pubspec.yaml 中声明为资产:
flutter:
assets:
- .env
CI/CD
GitHub Actions 自动打包(Android APK + iOS IPA)
# .github/workflows/release.yml
name: Flutter Release Build
on:
push:
tags:
- 'v*'
jobs:
build-android:
runs-on: ubuntu-latest
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.22.0'
channel: 'stable'
- name: 写入 key.properties
run: |
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/key.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties
echo "keyAlias=upload" >> android/key.properties
echo "storeFile=/home/runner/upload-keystore.jks" >> android/key.properties
- name: 解码 Keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > /home/runner/upload-keystore.jks
- run: flutter pub get
- name: 构建 APK
run: flutter build apk --release --split-per-abi
- uses: actions/upload-artifact@v4
with:
name: android-apk
path: build/app/outputs/flutter-apk/*.apk
build-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.22.0'
channel: 'stable'
- run: flutter pub get
- name: 安装证书和 Profile
uses: apple-actions/import-codesign-certs@v3
with:
p12-file-base64: ${{ secrets.P12_BASE64 }}
p12-password: ${{ secrets.P12_PASSWORD }}
- name: 构建 IPA
run: flutter build ipa --release --export-options-plist=ExportOptions.plist
- uses: actions/upload-artifact@v4
with:
name: ios-ipa
path: build/ios/ipa/*.ipa
GitHub Actions Secrets 配置
| Secret 名称 | 内容 |
|---|---|
STORE_PASSWORD |
Keystore 密码 |
KEY_PASSWORD |
Key 密码 |
KEYSTORE_BASE64 |
Keystore 文件的 Base64 编码(base64 upload-keystore.jks) |
P12_BASE64 |
iOS 证书 .p12 文件的 Base64 编码 |
P12_PASSWORD |
iOS 证书密码 |
Fastlane 自动化
# fastlane/Fastfile
lane :android_release do
gradle(
task: 'bundle',
build_type: 'Release',
project_dir: 'android/',
)
upload_to_play_store(
aab: 'build/app/outputs/bundle/release/app-release.aab',
track: 'internal',
)
end
lane :ios_release do
build_app(
workspace: 'ios/Runner.xcworkspace',
scheme: 'Runner',
export_method: 'app-store',
)
upload_to_app_store(
skip_metadata: true,
skip_screenshots: true,
)
end
踩坑与注意事项
iOS 证书过期
Apple 证书有效期为 1 年(Development)或 3 年(Distribution)。证书过期后已安装的 App 不受影响,但无法提交新构建。
- 定期检查证书有效期:Xcode → Settings → Accounts → 查看 Certificates
- CI/CD 中设置到期提醒(日历提醒或监控脚本)
- 证书更新后需要重新生成 Provisioning Profile 并更新 CI/CD 中的 Secrets
Android minSdkVersion 兼容性
minSdkVersion 决定支持的最低 Android 版本。部分 Flutter 插件要求较高的 minSdkVersion:
// android/app/build.gradle
defaultConfig {
minSdkVersion 21 // Android 5.0,大多数插件的最低要求
targetSdkVersion 34
}
升级 minSdkVersion 前检查各插件的要求,避免发布后因版本限制导致用户无法安装。
包体积优化
| 优化手段 | 说明 | 参考收益 |
|---|---|---|
--split-per-abi |
按 CPU 架构拆分 APK | 减少 30-50% |
--obfuscate |
混淆 Dart 代码 | 减少 5-10% |
| 图片压缩 | 使用 TinyPNG 或 flutter_image_compress |
依图片数量而定 |
| 使用 WebP 格式 | 替换 PNG/JPG | 减少 25-35% |
| 移除未使用的依赖 | 定期清理 pubspec.yaml |
依情况而定 |
flutter build apk --analyze-size |
分析包体积构成 | 辅助诊断 |
--obfuscate 必须配合 --split-debug-info
单独使用 --obfuscate 而不指定 --split-debug-info 时,崩溃堆栈将完全不可读。必须保存生成的符号文件用于还原:
flutter build apk --release \
--obfuscate \
--split-debug-info=build/symbols # 保存此目录
# 还原崩溃堆栈
flutter symbolize \
--debug-info=build/symbols/app.android-arm64.symbols \
--input=crash_stack.txt
Android Keystore 丢失无法更新应用
上传到 Google Play 的 APK/AAB 签名后,如果 Keystore 文件丢失,将永远无法发布新版本更新(只能重新上架新应用)。
- 将 Keystore 备份到至少两个独立位置(云存储 + 本地加密备份)
- 推荐使用 Google Play App Signing(Google 托管 Keystore,上传密钥丢失仍可恢复)
最佳实践
Keystore 单独管理,不提交到版本控制:将 Keystore 文件、key.properties 加入 .gitignore,通过 CI 环境变量或 GitHub Actions Secrets 注入,避免签名密钥泄漏。
使用 flutter build appbundle 而非 apk 上传 Play Store:AAB 格式让 Google Play 按设备架构交付最小安装包,比 fat APK 小 30–50%,且 Play Store 要求新应用必须上传 AAB。
每次发布前递增版本号并记录变更:pubspec.yaml 的 version: 1.2.3+45 中 1.2.3 是用户可见版本,+45 是 versionCode(必须单调递增)。建立 CHANGELOG 记录每次发布内容,便于审核和回滚决策。
iOS 发布通过 Xcode Organizer 验证后再提交:使用 flutter build ipa 生成后,在 Xcode Organizer 中执行 Validate App,提前发现证书过期、entitlements 缺失等问题,避免 App Store Connect 审核被拒。
CI/CD 流水线分离签名和构建步骤:构建步骤无需签名(加速缓存),签名步骤只在发布分支触发,减少签名密钥在 CI 环境中的暴露时间。
常见陷阱
陷阱:flutter build apk --release 后安装到真机崩溃,Debug 正常
现象: Release 构建启动即崩溃,日志显示 ClassNotFoundException 或方法找不到。
原因: Release 构建默认开启代码混淆(R8/ProGuard),某些反射调用的类名被混淆后找不到。
解决: 在 android/app/proguard-rules.pro 中为相关类添加 -keep 规则;或临时用 --no-obfuscate 确认是混淆问题,再针对性添加规则。
陷阱:iOS 真机调试证书可用,但 App Store 构建报 Provisioning profile doesn't match
现象: Development 证书正常,Archive 时报 Provisioning Profile 不匹配。
原因: App Store 发布需要 Distribution 证书 + App Store Provisioning Profile,与开发用的 Development 配置文件不同。
解决: 在 Apple Developer 后台创建 App Store Distribution Profile,在 Xcode 的 Signing & Capabilities 中选择正确 Profile;使用 fastlane match 统一管理多环境证书。
陷阱:versionCode 未递增导致 Play Store 拒绝上传
现象: 上传 AAB 时 Play Console 报 Version code 45 has already been used。
原因: pubspec.yaml 的 +45 与已发布版本相同,Play Store 要求每次上传的 versionCode 严格递增。
解决: 每次发布前更新 pubspec.yaml 中的 build number;在 CI 中用 flutter build appbundle --build-number=$CI_BUILD_NUMBER 自动注入流水线编号,避免手动遗忘。