← 返回学习路线
⚙️

工程化与发布

掌握 Flutter 多环境配置、CI/CD 流水线、应用商店发布、国际化与监控分析的完整工程化流程

📦 第一章:多环境配置

为什么需要多环境?

在实际开发中,应用通常需要在多个环境下运行:开发环境(Development)用于日常开发调试,预发布环境(Staging)用于内部测试验证,生产环境(Production)用于正式用户。每个环境可能对应不同的 API 地址、第三方 SDK 密钥、应用图标甚至应用名称。

Flutter Flavors 概念

Flutter Flavors 类似于 Android 的 Build Variants 和 iOS 的 Schemes,允许你从同一套代码构建不同版本的应用。每个 Flavor 可以拥有独立的 应用 ID应用名称图标配置参数

使用 --dart-define 传递编译时变量

最简单的多环境方案是通过命令行参数 --dart-define 在编译时注入变量:

Shell
# 开发环境构建
flutter run --dart-define=ENV=dev --dart-define=API_URL=https://dev.api.example.com

# 生产环境构建
flutter run --dart-define=ENV=prod --dart-define=API_URL=https://api.example.com

使用 --dart-define-from-file

当变量较多时,可以将配置写入 JSON 文件,通过 --dart-define-from-file 一次性加载:

config/dev.json
{
  "ENV": "dev",
  "API_URL": "https://dev.api.example.com",
  "ENABLE_LOGGING": "true",
  "APP_NAME": "MyApp Dev"
}
Shell
# 从 JSON 文件读取所有配置
flutter run --dart-define-from-file=config/dev.json

Android: build.gradle Flavor 配置

android/app/build.gradle
// 在 android 块中配置 Flavors
android {
    ...
    flavorDimensions "environment"

    productFlavors {
        dev {
            dimension "environment"
            applicationIdSuffix ".dev"
            resValue "string", "app_name", "MyApp Dev"
        }
        staging {
            dimension "environment"
            applicationIdSuffix ".staging"
            resValue "string", "app_name", "MyApp Staging"
        }
        prod {
            dimension "environment"
            resValue "string", "app_name", "MyApp"
        }
    }
}

iOS: Xcode Schemes 配置

在 iOS 端,每个 Flavor 对应一个 Xcode Scheme。需要在 Xcode 中为 Runner 项目创建不同的 Scheme(如 dev、staging、prod),并为每个 Scheme 设置不同的 Bundle Identifier 和构建配置。

  • 打开 ios/Runner.xcworkspace
  • 点击 Product → Scheme → Manage Schemes
  • 为每个环境创建独立的 Scheme
  • 在每个 Scheme 的 Build Configuration 中指定 Debug / Release

环境配置类

Dart
// 环境配置类:在运行时读取编译时注入的变量
class EnvConfig {
  // 从 --dart-define 读取环境名称
  static const String env = String.fromEnvironment(
    'ENV',
    defaultValue: 'dev',
  );

  // 读取 API 地址
  static const String apiUrl = String.fromEnvironment(
    'API_URL',
    defaultValue: 'https://dev.api.example.com',
  );

  // 读取布尔值配置
  static const bool enableLogging = bool.fromEnvironment(
    'ENABLE_LOGGING',
    defaultValue: true,
  );

  // 读取应用名称
  static const String appName = String.fromEnvironment(
    'APP_NAME',
    defaultValue: 'MyApp',
  );

  // 判断是否为生产环境
  static bool get isProduction => env == 'prod';

  // 判断是否为开发环境
  static bool get isDevelopment => env == 'dev';
}

// 使用示例
void main() {
  print('当前环境: ${EnvConfig.env}');
  print('API 地址: ${EnvConfig.apiUrl}');

  if (EnvConfig.enableLogging) {
    // 仅在启用日志时初始化日志系统
    setupLogger();
  }

  runApp(const MyApp());
}
提示:使用 const 声明的 String.fromEnvironment 在编译时求值,这意味着未使用的环境分支代码会被 Tree-Shaking 移除,不会增加最终包体积。

🔨 第二章:构建与打包

Android 构建

构建 APK

Shell
# Debug 模式构建(用于开发调试)
flutter build apk --debug

# Release 模式构建(用于发布)
flutter build apk --release

# Profile 模式构建(用于性能分析)
flutter build apk --profile

# 按 ABI 分割构建,减小包体积
flutter build apk --split-per-abi

构建 App Bundle(推荐)

Shell
# Google Play 推荐使用 App Bundle 格式
flutter build appbundle --release

# 输出路径: build/app/outputs/bundle/release/app-release.aab

签名配置

Shell
# 第一步:生成密钥库文件
keytool -genkey -v \
  -keystore ~/upload-keystore.jks \
  -keyalg RSA -keysize 2048 -validity 10000 \
  -alias upload
android/key.properties
# 密钥库配置文件(不要提交到版本控制!)
storePassword=你的密钥库密码
keyPassword=你的密钥密码
keyAlias=upload
storeFile=/Users/yourname/upload-keystore.jks
android/app/build.gradle
// 读取签名配置
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 file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            // 启用代码缩减和混淆
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
                          'proguard-rules.pro'
        }
    }
}

ProGuard / R8 配置

android/app/proguard-rules.pro
# Flutter 相关规则
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }

# 保留 Gson 序列化相关类(如使用了 Gson)
-keepattributes Signature
-keepattributes *Annotation*

版本号管理

pubspec.yaml
# 格式:主版本.次版本.修订号+构建号
version: 1.2.3+45

# 1.2.3 → versionName(显示给用户)
# 45    → versionCode / buildNumber(每次发布递增)
Shell
# 也可以在构建时通过命令行覆盖版本号
flutter build apk --build-name=1.2.3 --build-number=45

iOS 构建

构建 IPA

Shell
# 构建 iOS 发布包
flutter build ipa --release

# 使用导出选项
flutter build ipa --export-options-plist=ios/ExportOptions.plist

# 输出路径: build/ios/ipa/

构建前需要确保:

  • 拥有有效的 Apple Developer 账号
  • 配置了正确的 Provisioning Profile(开发/分发)
  • 安装了对应的 签名证书(Development / Distribution)
  • 在 Xcode 中设置了正确的 Bundle Identifier 和 Team

Web 构建

Shell
# 构建 Web 版本
flutter build web --release

# 使用 CanvasKit 渲染器(更好的一致性)
flutter build web --web-renderer canvaskit

# 使用 HTML 渲染器(更小的包体积)
flutter build web --web-renderer html

# 输出路径: build/web/
# 可部署到 Firebase Hosting、Nginx、GitHub Pages 等

桌面构建

Shell
# macOS 构建
flutter build macos --release

# Windows 构建
flutter build windows --release

# Linux 构建
flutter build linux --release

代码混淆

Shell
# 启用 Dart 代码混淆并分离调试信息
flutter build apk \
  --obfuscate \
  --split-debug-info=build/debug-info

# iOS 同样支持
flutter build ipa \
  --obfuscate \
  --split-debug-info=build/debug-info

# 保存 debug-info 目录!崩溃日志解析时需要用到
# 使用 flutter symbolize 还原混淆后的堆栈
flutter symbolize -i stack_trace.txt -d build/debug-info
注意:key.properties 文件和密钥库文件包含敏感信息,务必添加到 .gitignore 中,切勿提交到版本控制系统。

🔄 第三章:CI/CD with GitHub Actions

工作流 YAML 结构

GitHub Actions 使用 YAML 文件定义自动化工作流。文件存放在 .github/workflows/ 目录下。每个工作流由触发条件(on)作业(jobs)步骤(steps)组成。

Flutter Setup Action

使用社区维护的 subosito/flutter-action 快速在 CI 环境中安装 Flutter SDK:

YAML
# 安装指定版本的 Flutter
- uses: subosito/flutter-action@v2
  with:
    flutter-version: '3.24.0'
    channel: 'stable'

在 CI 中运行测试

YAML
# 代码分析和测试步骤
- name: 代码分析
  run: flutter analyze

- name: 运行单元测试
  run: flutter test --coverage

- name: 检查代码格式
  run: dart format --set-exit-if-changed .

缓存依赖

YAML
# 缓存 pub 依赖以加快构建速度
- uses: actions/cache@v3
  with:
    path: |
      ~/.pub-cache
      .dart_tool
    key: ${{ runner.os }}-pub-${{ hashFiles('pubspec.lock') }}
    restore-keys: |
      ${{ runner.os }}-pub-

完整工作流文件:测试 → 构建 → 部署

.github/workflows/flutter-ci.yml
name: Flutter CI/CD

# 触发条件
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  # 第一个作业:测试
  test:
    name: 测试与分析
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'
          channel: 'stable'

      # 缓存依赖
      - uses: actions/cache@v3
        with:
          path: ~/.pub-cache
          key: ${{ runner.os }}-pub-${{ hashFiles('pubspec.lock') }}

      - name: 安装依赖
        run: flutter pub get

      - name: 代码分析
        run: flutter analyze

      - name: 代码格式检查
        run: dart format --set-exit-if-changed .

      - name: 运行测试
        run: flutter test --coverage

      # 上传测试覆盖率报告
      - name: 上传覆盖率
        uses: codecov/codecov-action@v3
        with:
          file: coverage/lcov.info

  # 第二个作业:构建 Android
  build-android:
    name: 构建 Android
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'

      # 设置 Java 环境
      - uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: '17'

      # 解码签名密钥(从 GitHub Secrets 中读取)
      - name: 解码密钥库
        run: |
          echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/keystore.jks
          echo "${{ secrets.KEY_PROPERTIES }}" > android/key.properties

      - name: 构建 APK
        run: flutter build apk --release --split-per-abi

      # 上传构建产物
      - name: 上传 APK
        uses: actions/upload-artifact@v4
        with:
          name: android-release
          path: build/app/outputs/flutter-apk/*.apk

  # 第三个作业:构建 iOS
  build-ios:
    name: 构建 iOS
    needs: test
    runs-on: macos-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'

      - name: 构建 IPA
        run: flutter build ipa --release --no-codesign

      - name: 上传 IPA
        uses: actions/upload-artifact@v4
        with:
          name: ios-release
          path: build/ios/ipa/*.ipa
提示:将签名密钥等敏感信息存储在 GitHub Secrets 中,通过 ${{ secrets.XXX }} 在工作流中引用,避免泄露。

🚀 第四章:Fastlane 自动化

安装 Fastlane

Shell
# 通过 Homebrew 安装(macOS 推荐)
brew install fastlane

# 或通过 RubyGems 安装
gem install fastlane

# 在项目的 android 或 ios 目录下初始化
cd android && fastlane init
cd ios && fastlane init

Lanes 概念

Lane 是 Fastlane 中的核心概念,类似于任务流水线。每个 Lane 定义了一系列按顺序执行的操作(Action),如构建、测试、签名和上传。你可以为不同场景(如内测、正式发布)定义不同的 Lane。

Android Fastfile

android/fastlane/Fastfile
# 定义 Android 构建和发布流程
default_platform(:android)

platform :android do

  # 内部测试 Lane
  desc "构建并上传到 Google Play 内部测试"
  lane :internal do
    # 递增版本号
    increment_version_code(
      gradle_file_path: "app/build.gradle"
    )

    # 使用 Flutter 构建 App Bundle
    sh("cd ../.. && flutter build appbundle --release")

    # 上传到 Google Play 内部测试轨道
    upload_to_play_store(
      track: "internal",
      aab: "../build/app/outputs/bundle/release/app-release.aab",
      json_key_file: "fastlane/play-store-key.json",
      skip_upload_metadata: true,
      skip_upload_screenshots: true
    )
  end

  # 正式发布 Lane
  desc "构建并上传到 Google Play 生产轨道"
  lane :production do
    sh("cd ../.. && flutter build appbundle --release")

    upload_to_play_store(
      track: "production",
      aab: "../build/app/outputs/bundle/release/app-release.aab",
      json_key_file: "fastlane/play-store-key.json"
    )
  end
end

iOS Fastfile

ios/fastlane/Fastfile
# 定义 iOS 构建和发布流程
default_platform(:ios)

platform :ios do

  # TestFlight 内测 Lane
  desc "构建并上传到 TestFlight"
  lane :beta do
    # 使用 Match 管理证书和描述文件
    match(type: "appstore")

    # 递增构建号
    increment_build_number(
      xcodeproj: "Runner.xcodeproj"
    )

    # 使用 Flutter 构建 IPA
    sh("cd ../.. && flutter build ipa --release")

    # 上传到 TestFlight
    upload_to_testflight(
      ipa: "../build/ios/ipa/Runner.ipa",
      skip_waiting_for_build_processing: true
    )
  end

  # App Store 正式发布 Lane
  desc "构建并上传到 App Store"
  lane :release do
    match(type: "appstore")

    sh("cd ../.. && flutter build ipa --release")

    upload_to_app_store(
      ipa: "../build/ios/ipa/Runner.ipa",
      skip_metadata: false,
      skip_screenshots: false,
      submit_for_review: true,
      automatic_release: true
    )
  end
end

Match 证书管理

Match 是 Fastlane 的证书和描述文件管理工具。它将所有证书存储在一个私有 Git 仓库中,团队成员通过同步仓库来共享证书,避免手动管理的混乱。

Shell
# 初始化 Match
fastlane match init

# 生成开发证书
fastlane match development

# 生成分发证书
fastlane match appstore

# 使用 Matchfile 配置
# ios/fastlane/Matchfile:
# git_url("https://github.com/your-org/certificates")
# storage_mode("git")
# type("appstore")
# app_identifier("com.example.myapp")
说明:Fastlane 是 Ruby 生态的工具,在 CI/CD 中与 GitHub Actions、GitLab CI 等可以很好地配合使用,实现全自动化的构建发布流程。

🏗️ 第五章:Codemagic & 其他 CI

Codemagic: Flutter 原生 CI/CD

Codemagic 是专为 Flutter 设计的 CI/CD 平台,提供开箱即用的 Flutter 构建环境,无需额外配置即可编译 Android、iOS、Web 和桌面应用。

  • 支持可视化配置和 YAML 配置两种模式
  • 内建 Apple 证书管理和 Code Signing
  • 自动分发到 Google Play、App Store、Firebase App Distribution
  • 提供 macOS 和 Linux 构建机器

codemagic.yaml 配置

codemagic.yaml
workflows:
  flutter-app:
    name: Flutter App 构建
    max_build_duration: 60

    environment:
      flutter: stable
      xcode: latest
      cocoapods: default
      groups:
        - google_play_credentials
        - app_store_credentials

    triggering:
      events:
        - push
      branch_patterns:
        - pattern: 'main'
          include: true

    scripts:
      - name: 安装依赖
        script: flutter pub get

      - name: 运行测试
        script: flutter test

      - name: 构建 Android
        script: flutter build appbundle --release

      - name: 构建 iOS
        script: flutter build ipa --release

    artifacts:
      - build/**/outputs/**/*.aab
      - build/ios/ipa/*.ipa

    publishing:
      google_play:
        credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
        track: internal
      app_store_connect:
        api_key: $APP_STORE_CONNECT_PRIVATE_KEY
        key_id: $APP_STORE_CONNECT_KEY_IDENTIFIER
        issuer_id: $APP_STORE_CONNECT_ISSUER_ID

GitLab CI for Flutter

.gitlab-ci.yml
image: ghcr.io/cirruslabs/flutter:stable

stages:
  - test
  - build

test:
  stage: test
  script:
    - flutter pub get
    - flutter analyze
    - flutter test --coverage

build_android:
  stage: build
  script:
    - flutter build apk --release
  artifacts:
    paths:
      - build/app/outputs/flutter-apk/
  only:
    - main

CI/CD 平台对比

平台 Flutter 支持 iOS 构建 免费额度 特点
GitHub Actions 通过 Action 支持 支持(macOS Runner) 2000 分钟/月 生态丰富、与 GitHub 深度集成
Codemagic 原生支持 内建支持 500 分钟/月 Flutter 专用、配置简单
GitLab CI Docker 镜像 需自托管 Runner 400 分钟/月 与 GitLab 仓库集成
Bitrise 内建 Step 支持 有限 移动开发专用、可视化配置
CircleCI Orb 支持 支持 有限 高度可定制、并行任务
建议:对于个人项目或开源项目,GitHub Actions 是最佳选择(免费额度充足)。对于商业团队、尤其是需要频繁构建 iOS 的项目,Codemagic 的 Flutter 原生支持可以大幅节省配置时间。

🤖 第六章:发布到 Google Play

Google Play Console 账户设置

  1. 访问 play.google.com/console 并注册开发者账号
  2. 支付一次性注册费 $25
  3. 完成账户身份验证
  4. 创建新应用并选择默认语言

应用商店列表信息

发布前需要准备以下素材:

  • 应用名称:最多 30 个字符
  • 简短描述:最多 80 个字符
  • 完整描述:最多 4000 个字符
  • 截图:至少 2 张,推荐手机 + 平板各提供
  • 高分辨率图标:512 x 512px PNG
  • 特征图:1024 x 500px
  • 隐私政策 URL

发布轨道

Google Play 提供多个发布轨道,建议按以下顺序逐步推进:

1. 内部测试(Internal Testing)

最多 100 名测试人员,审核速度快(通常几分钟),适合团队内部快速验证。

2. 封闭测试(Closed Testing)

通过邮件列表或 Google Groups 邀请特定用户,适合小规模外部测试。

3. 开放测试(Open Testing)

任何用户都可以加入测试,适合大规模公开 Beta 测试。需要先通过审核。

4. 正式发布(Production)

面向所有用户发布。可以选择分阶段发布(如先推送给 10% 的用户)。

App Bundle 要求

自 2021 年 8 月起,新应用必须使用 App Bundle(.aab)格式提交。App Bundle 由 Google Play 动态生成针对各设备优化的 APK,通常可以减少 15%-20% 的下载体积。

发布步骤指南

  1. 确保应用已通过全部测试
  2. pubspec.yaml 中更新版本号
  3. 运行 flutter build appbundle --release
  4. 登录 Google Play Console,进入你的应用
  5. 选择目标发布轨道(建议先从内部测试开始)
  6. 上传 .aab 文件
  7. 填写发布说明(更新内容)
  8. 完成内容合规声明
  9. 提交审核
  10. 审核通过后,根据需要提升到更高轨道

应用内评价

Dart
// 使用 in_app_review 包请求用户评价
// pubspec.yaml: in_app_review: ^2.0.0
import 'package:in_app_review/in_app_review.dart';

Future<void> requestReview() async {
  final inAppReview = InAppReview.instance;

  // 检查是否可以请求评价
  if (await inAppReview.isAvailable()) {
    // 弹出系统原生评价弹窗
    await inAppReview.requestReview();
  }
}
注意:Google Play 会对应用签名密钥进行管理(App Signing by Google Play)。首次上传时,你的上传密钥和签名密钥可以不同。上传密钥丢失可以重置,但签名密钥不可更改。

🍎 第七章:发布到 App Store

Apple Developer 账户

  • 访问 developer.apple.com 注册
  • 个人开发者年费 $99/年
  • 企业开发者 $299/年(仅限企业内部分发)
  • 注册后需等待 Apple 审核账户(通常 24-48 小时)

App Store Connect 设置

  1. App Store Connect 中创建新 App
  2. 填写应用名称、Bundle ID、SKU
  3. 准备应用截图(iPhone 6.7"、6.5"、5.5",iPad 12.9")
  4. 编写应用描述、关键词、支持 URL
  5. 设置定价和可用区域
  6. 填写年龄分级问卷

TestFlight Beta 测试

TestFlight 是 Apple 官方的测试分发平台,可以在正式上架前邀请用户参与测试:

  • 内部测试:最多 25 名 App Store Connect 用户,无需审核
  • 外部测试:最多 10,000 名测试人员,需要 Beta App 审核(通常 24 小时内)
  • 测试人员通过 TestFlight App 安装测试版本
  • 每个 Build 的测试有效期为 90 天

App 审核要点

Apple 的应用审核非常严格,以下是需要特别注意的几个方面:

性能要求

  • 应用不得崩溃或出现严重 Bug
  • 不得包含未完成的 UI 或占位内容
  • 必须在所有支持的设备上正常运行

隐私政策

  • 必须提供隐私政策链接
  • 正确声明数据收集类型
  • 遵守 App Tracking Transparency

设计规范

  • 遵循 Human Interface Guidelines
  • 不得模仿系统 UI 误导用户
  • 应用图标不得包含 Alpha 通道

内容规范

  • 应用内购必须使用 Apple IAP
  • 不得包含违法或有害内容
  • 截图必须真实反映应用功能

常见被拒原因及应对

被拒原因 描述 解决方案
Guideline 2.1 应用崩溃或存在 Bug 充分测试后再提交
Guideline 2.3 截图与实际功能不符 使用真实截图
Guideline 3.1.1 未使用 Apple IAP 处理应用内购 数字内容必须使用 IAP
Guideline 4.0 应用功能过于简单 增加核心功能和价值
Guideline 5.1.1 缺少隐私政策 添加完整的隐私政策页面

发布步骤指南

  1. 确保 Xcode 中配置了正确的 Bundle ID 和 Team
  2. 运行 flutter build ipa --release
  3. 使用 Transporter 或 xcrun altool 上传 IPA
  4. 在 App Store Connect 中选择刚上传的 Build
  5. 填写发布说明
  6. 完成 App 审核信息(如登录凭据供审核员使用)
  7. 提交审核
  8. 审核通过后选择自动发布或手动发布

🌍 第八章:国际化实战

flutter_localizations 基础配置

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^0.19.0

# 启用代码生成
flutter:
  generate: true
l10n.yaml
# 国际化代码生成配置文件
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

ARB 文件

ARB(Application Resource Bundle)是 Flutter 推荐的本地化资源格式,本质上是 JSON 文件:

lib/l10n/app_en.arb
{
  "@@locale": "en",
  "appTitle": "My App",
  "@appTitle": {
    "description": "应用标题"
  },
  "hello": "Hello, {name}!",
  "@hello": {
    "description": "问候语",
    "placeholders": {
      "name": {
        "type": "String",
        "example": "World"
      }
    }
  },
  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCount": {
    "description": "物品数量(复数形式)",
    "placeholders": {
      "count": {
        "type": "int"
      }
    }
  }
}
lib/l10n/app_zh.arb
{
  "@@locale": "zh",
  "appTitle": "我的应用",
  "hello": "你好,{name}!",
  "itemCount": "{count, plural, =0{没有物品} other{{count} 个物品}}"
}

在 MaterialApp 中配置

Dart
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 应用标题
      title: 'My App',

      // 本地化代理
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],

      // 支持的语言列表
      supportedLocales: const [
        Locale('en'),    // 英语
        Locale('zh'),    // 中文
        Locale('ja'),    // 日语
      ],

      home: const HomePage(),
    );
  }
}

// 在页面中使用本地化字符串
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    // 获取本地化实例
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.appTitle)),
      body: Column(
        children: [
          Text(l10n.hello('Flutter')),    // "你好,Flutter!"
          Text(l10n.itemCount(5)),       // "5 个物品"
        ],
      ),
    );
  }
}

easy_localization 方案

easy_localization 是一个流行的第三方国际化包,提供更灵活的 API 和更多功能:

Dart
// pubspec.yaml: easy_localization: ^3.0.0
import 'package:easy_localization/easy_localization.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();

  runApp(
    EasyLocalization(
      // 支持的语言
      supportedLocales: const [
        Locale('en'),
        Locale('zh'),
      ],
      // 翻译文件路径
      path: 'assets/translations',
      // 默认语言
      fallbackLocale: const Locale('en'),
      child: const MyApp(),
    ),
  );
}

// 使用方式:
Text('app_title'.tr())                    // 简单翻译
Text('hello'.tr(args: ['Flutter']))       // 带参数
Text('items'.plural(5))                    // 复数形式

// 切换语言
context.setLocale(const Locale('zh'));

RTL 语言支持

对于阿拉伯语、希伯来语等从右到左(RTL)的语言,Flutter 会自动处理大部分布局翻转。但需要注意:

  • 使用 Directionality widget 或依赖 MaterialApp 自动检测
  • 使用 EdgeInsetsDirectional 替代 EdgeInsets
  • 使用 AlignmentDirectional 替代 Alignment
  • 图标和图片可能需要根据方向进行镜像

语言切换 UI

Dart
// 语言切换下拉菜单示例
class LanguageSwitcher extends StatelessWidget {
  const LanguageSwitcher({super.key});

  @override
  Widget build(BuildContext context) {
    return DropdownButton<Locale>(
      // 当前语言
      value: context.locale,
      items: const [
        DropdownMenuItem(
          value: Locale('zh'),
          child: Text('中文'),
        ),
        DropdownMenuItem(
          value: Locale('en'),
          child: Text('English'),
        ),
        DropdownMenuItem(
          value: Locale('ja'),
          child: Text('日本語'),
        ),
      ],
      // 切换语言回调
      onChanged: (locale) {
        if (locale != null) {
          context.setLocale(locale);
        }
      },
    );
  }
}
说明:运行 flutter gen-l10n 可以手动生成本地化代码。当 pubspec.yaml 中设置了 generate: true 时,flutter run 会自动生成。

🔥 第九章:热更新

Shorebird Code Push 概述

Shorebird 是目前 Flutter 生态中最成熟的热更新(OTA 代码推送)解决方案。它允许你在不经过应用商店审核的情况下,直接向用户设备推送 Dart 代码更新。

  • 仅支持 Dart 代码更新,不支持原生代码和资源文件变更
  • 支持 Android 和 iOS 平台
  • 补丁体积通常只有几十 KB 到几百 KB
  • 更新在下次应用启动时生效

安装与集成

Shell
# 安装 Shorebird CLI
curl --proto '=https' --tlsv1.2 https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh -sSf | bash

# 登录
shorebird login

# 在项目中初始化
shorebird init

# 这会在 pubspec.yaml 中添加 shorebird 配置
# 并在 shorebird.yaml 中生成应用 ID

创建发布版本和补丁

Shell
# 第一步:创建基线发布版本
shorebird release android
shorebird release ios

# 第二步:修改代码后,创建补丁
shorebird patch android
shorebird patch ios

# 查看发布版本列表
shorebird releases list

# 查看补丁列表
shorebird patches list

版本管理

Dart
// 在应用中检查更新状态
import 'package:shorebird_code_push/shorebird_code_push.dart';

final shorebirdCodePush = ShorebirdCodePush();

// 检查是否有新补丁可用
final isUpdateAvailable =
    await shorebirdCodePush.isNewPatchAvailableForDownload();

if (isUpdateAvailable) {
  // 下载并安装补丁
  await shorebirdCodePush.downloadUpdateIfAvailable();
  // 提示用户重启应用以应用更新
}

// 获取当前补丁版本号
final patchNumber = await shorebirdCodePush.currentPatchNumber();

限制

  • 只能更新 Dart 代码,无法更新原生代码(Java/Kotlin/Swift/ObjC)
  • 无法更新 资源文件(图片、字体等)
  • 无法添加新的 原生插件
  • iOS 平台需要符合 Apple 的相关政策
  • 需要订阅 Shorebird 服务(有免费额度)

替代方案:服务端驱动 UI

另一种实现"动态更新"的方式是服务端驱动 UI(Server-Driven UI)。将 UI 结构以 JSON 等格式从服务器下发,客户端解析并渲染。这种方式不依赖热更新框架,但实现成本较高,且灵活性受限于预定义的组件库。

注意:使用热更新技术需要确保符合 Apple 和 Google 的应用商店政策。Apple 要求代码更新不得改变应用的主要功能或绕过审核。

📊 第十章:监控与分析

Firebase Crashlytics 集成

Crashlytics 是 Firebase 提供的崩溃报告工具,可以实时收集和分析应用崩溃信息。

pubspec.yaml
dependencies:
  firebase_core: ^3.0.0
  firebase_crashlytics: ^4.0.0
Dart
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  // 捕获 Flutter 框架内的错误
  FlutterError.onError = (errorDetails) {
    FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
  };

  // 捕获异步错误
  PlatformDispatcher.instance.onError = (error, stack) {
    FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
    return true;
  };

  // 设置用户标识(可选,便于追踪特定用户的崩溃)
  FirebaseCrashlytics.instance.setUserIdentifier('user_123');

  // 添加自定义日志
  FirebaseCrashlytics.instance.log('应用启动');

  // 设置自定义键值对
  FirebaseCrashlytics.instance.setCustomKey('env', 'production');

  runApp(const MyApp());
}

// 手动上报非致命错误
try {
  // 可能出错的操作
  await riskyOperation();
} catch (e, stackTrace) {
  // 记录非致命异常
  await FirebaseCrashlytics.instance.recordError(
    e,
    stackTrace,
    reason: 'riskyOperation 执行失败',
    fatal: false,
  );
}

Sentry for Flutter

Sentry 是另一个广泛使用的错误监控平台,提供更丰富的错误上下文和性能监控:

Dart
// pubspec.yaml: sentry_flutter: ^8.0.0
import 'package:sentry_flutter/sentry_flutter.dart';

Future<void> main() async {
  await SentryFlutter.init(
    (options) {
      options.dsn = 'https://your-dsn@sentry.io/project-id';
      // 采样率:1.0 = 100% 的事件会被上报
      options.tracesSampleRate = 1.0;
      // 环境标识
      options.environment = 'production';
    },
    appRunner: () => runApp(const MyApp()),
  );
}

// 手动捕获异常
try {
  doSomething();
} catch (exception, stackTrace) {
  await Sentry.captureException(exception, stackTrace: stackTrace);
}

// 添加面包屑(操作轨迹记录)
Sentry.addBreadcrumb(Breadcrumb(
  message: '用户点击了购买按钮',
  category: 'ui.click',
));

Firebase Analytics 自定义事件

Dart
// pubspec.yaml: firebase_analytics: ^11.0.0
import 'package:firebase_analytics/firebase_analytics.dart';

final analytics = FirebaseAnalytics.instance;

// 记录自定义事件
await analytics.logEvent(
  name: 'purchase_completed',
  parameters: {
    'item_id': 'SKU_12345',
    'item_name': 'Flutter 课程',
    'price': 99.0,
    'currency': 'CNY',
  },
);

// 设置用户属性
await analytics.setUserProperty(
  name: 'membership_level',
  value: 'premium',
);

// 记录页面浏览
await analytics.logScreenView(
  screenName: '商品详情页',
  screenClass: 'ProductDetailScreen',
);

性能监控

Dart
// pubspec.yaml: firebase_performance: ^0.10.0
import 'package:firebase_performance/firebase_performance.dart';

// 自定义性能追踪
final trace = FirebasePerformance.instance.newTrace('data_loading');
await trace.start();

// 执行需要监控的操作
final data = await fetchDataFromApi();

// 添加指标
trace.setMetric('data_size', data.length);
trace.putAttribute('endpoint', '/api/products');

await trace.stop();

// HTTP 请求性能自动追踪
// firebase_performance 会自动监控 HTTP 请求的延迟和成功率

用户反馈收集

除了自动化的崩溃收集,主动收集用户反馈也很重要:

  • 应用内反馈表单(截图 + 文字描述)
  • 集成 shake 摇一摇反馈(如 Instabug、Shake SDK)
  • 应用内评分请求(使用 in_app_review
  • 将反馈数据发送到内部工单系统或 Slack/飞书通知
最佳实践:在生产环境中同时启用 Crashlytics(崩溃监控)和 Analytics(行为分析),结合两者数据可以更全面地了解用户使用情况和应用稳定性。

✏️ 第十一章:实践练习

练习 1:多环境 CI/CD 流水线

为一个 Flutter 项目搭建完整的 CI/CD 流水线:

  1. 创建 config/dev.jsonconfig/staging.jsonconfig/prod.json 三个环境配置文件
  2. 编写 EnvConfig 类读取编译时变量
  3. 编写 GitHub Actions 工作流:PR 时运行测试和分析,合并到 main 时自动构建 Release APK
  4. 在工作流中使用缓存加速依赖安装
  5. 将构建产物上传为 Artifacts

练习 2:完整国际化应用

为一个待办事项应用添加完整的国际化支持:

  1. 使用 flutter_localizations + intl 配置中文和英文
  2. 编写 ARB 文件,包含简单文本、带参数文本和复数形式
  3. 实现语言切换功能,并使用 SharedPreferences 持久化用户的语言选择
  4. 确保所有 UI 文本都使用本地化字符串,不存在硬编码文本

练习 3:应用监控与发布准备

为一个即将上线的应用完成发布前的工程化准备:

  1. 集成 Firebase Crashlytics,配置全局错误捕获
  2. 添加 3-5 个关键业务的自定义 Analytics 事件
  3. 配置代码混淆 --obfuscate 并保存调试信息
  4. 编写 Fastlane 的 Fastfile,定义 beta 和 release 两个 Lane
  5. 准备 Google Play 或 App Store 所需的所有素材清单(截图尺寸、描述、隐私政策等)