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

  1. 在 Google Play Console 创建应用
  2. 进入「发布」→「正式版」→「创建新版本」
  3. 上传 build/app/outputs/bundle/release/app-release.aab
  4. 填写版本说明,提交审核

iOS 打包

Apple Developer 账号配置

  1. developer.apple.com 注册 Apple Developer Program(年费 $99)
  2. 在 Xcode 中登录账号:Xcode → Settings → Accounts → 添加 Apple ID
  3. 在 Apple Developer 后台创建 App ID(Bundle Identifier 需与 ios/Runner.xcodeproj 中一致)

Xcode 证书与 Provisioning Profile

  1. 打开 ios/Runner.xcworkspace(必须用 .xcworkspace,不是 .xcodeproj
  2. 选择 Runner Target → Signing & Capabilities
  3. 勾选 Automatically manage signing,选择对应 Team
  4. 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

  1. Xcode → Window → Organizer
  2. 选择对应 Archive → Distribute App
  3. 选择 App Store Connect → 按向导完成上传

版本管理

pubspec.yaml 版本号

version: 1.2.3+45
#        ^^^^^  ^^
#        versionName  versionCode
  • versionName1.2.3):显示给用户的版本号,对应 iOS CFBundleShortVersionString 和 Android versionName
  • versionCode45):内部递增整数,对应 iOS CFBundleVersion 和 Android versionCode,每次发布必须递增

自动化版本号

在 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.yamlversion: 1.2.3+451.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 自动注入流水线编号,避免手动遗忘。


参见

Flutter入门
GoRouter完全指南
Flutter状态管理

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog