第12章:CI/CD與發布流程
在前面的章節中,我們學習了Flutter應用開發的各個方面,從基礎UI構建到復雜的狀態管理,從網絡請求到本地存儲。現在,我們將探討一個同樣重要但常被忽視的話題:如何將我們精心開發的應用高效、可靠地發布到各大應用商店。
想象一下,你花費了數月時間開發出一款功能完善的Flutter應用,但每次發布新版本時都需要手動打包、簽名、上傳,這不僅耗時耗力,還容易出錯。本章將帶你建立一套完整的自動化發布流程,讓發布應用變得像點擊一個按鈕一樣簡單。
12.1 多環境配置管理
在實際開發中,我們通常需要維護多個環境:開發環境(dev)、測試環境(test)、預發布環境(staging)和生產環境(prod)。每個環境可能使用不同的API地址、數據庫連接、第三方服務密鑰等配置。
12.1.1 環境配置的重要性
為什么需要多環境配置?想象一下這樣的場景:
- 開發環境:使用本地或開發服務器的API,可以隨意測試和調試
- 測試環境:使用穩定的測試數據,供QA團隊進行功能測試
- 預發布環境:與生產環境配置幾乎相同,用于最終驗證
- 生產環境:真實用戶使用的環境,配置最為嚴格
如果沒有合理的環境配置管理,你可能會遇到以下問題:
- 開發時誤連生產數據庫,造成數據污染
- 測試環境的配置意外發布到生產環境
- 不同環境的切換需要手動修改代碼
12.1.2 創建環境配置文件
首先,我們在項目根目錄下創建不同環境的配置文件:
// lib/config/app_config.dart
class AppConfig {static const String appName = String.fromEnvironment('APP_NAME', defaultValue: 'MyApp');static const String apiBaseUrl = String.fromEnvironment('API_BASE_URL', defaultValue: 'https://api.example.com');static const String environment = String.fromEnvironment('ENVIRONMENT', defaultValue: 'dev');static const bool enableDebugMode = bool.fromEnvironment('DEBUG_MODE', defaultValue: true);static const String analyticsKey = String.fromEnvironment('ANALYTICS_KEY', defaultValue: '');// 環境判斷方法static bool get isDevelopment => environment == 'dev';static bool get isProduction => environment == 'prod';static bool get isStaging => environment == 'staging';// 獲取完整的API URLstatic String getApiUrl(String endpoint) {return '$apiBaseUrl$endpoint';}
}
然后創建環境特定的配置文件:
// lib/config/environments/dev_config.dart
class DevConfig {static const Map<String, String> config = {'APP_NAME': 'MyApp Dev','API_BASE_URL': 'https://dev-api.example.com','ENVIRONMENT': 'dev','DEBUG_MODE': 'true','ANALYTICS_KEY': 'dev_analytics_key',};
}// lib/config/environments/prod_config.dart
class ProdConfig {static const Map<String, String> config = {'APP_NAME': 'MyApp','API_BASE_URL': 'https://api.example.com','ENVIRONMENT': 'prod','DEBUG_MODE': 'false','ANALYTICS_KEY': 'prod_analytics_key',};
}
12.1.3 使用環境變量啟動應用
為了在不同環境下啟動應用,我們需要修改啟動腳本。在項目根目錄創建啟動腳本:
# scripts/run_dev.sh
#!/bin/bash
flutter run --dart-define=APP_NAME="MyApp Dev" \--dart-define=API_BASE_URL="https://dev-api.example.com" \--dart-define=ENVIRONMENT="dev" \--dart-define=DEBUG_MODE="true"# scripts/run_prod.sh
#!/bin/bash
flutter run --release \--dart-define=APP_NAME="MyApp" \--dart-define=API_BASE_URL="https://api.example.com" \--dart-define=ENVIRONMENT="prod" \--dart-define=DEBUG_MODE="false"
在應用中使用配置:
// lib/main.dart
import 'package:flutter/material.dart';
import 'config/app_config.dart';void main() {runApp(MyApp());
}class MyApp extends StatelessWidget { Widget build(BuildContext context) {return MaterialApp(title: AppConfig.appName,debugShowCheckedModeBanner: AppConfig.enableDebugMode,home: HomeScreen(),);}
}// lib/services/api_service.dart
import '../config/app_config.dart';class ApiService {static Future<Map<String, dynamic>> fetchUserData() async {final url = AppConfig.getApiUrl('/users/profile');if (AppConfig.isDevelopment) {print('DEV: Fetching from $url');}// 網絡請求邏輯// ...}
}
12.2 代碼簽名與證書配置
代碼簽名是移動應用發布的關鍵步驟,它確保應用的完整性和來源可信度。簡單來說,代碼簽名就像是給你的應用蓋上一個官方印章,證明這個應用確實是你開發的,并且沒有被惡意篡改。
12.2.1 Android代碼簽名
Android使用密鑰庫(keystore)進行應用簽名。我們需要創建一個簽名密鑰并配置構建腳本。
創建簽名密鑰
# 創建密鑰庫文件
keytool -genkey -v -keystore ~/my-release-key.keystore \-alias my-key-alias \-keyalg RSA \-keysize 2048 \-validity 10000
這個命令會詢問你一系列問題,包括密碼、組織信息等。請務必記住密碼和別名,并將密鑰庫文件保存在安全的地方。
配置簽名信息
在android/app/build.gradle
文件中配置簽名信息:
android {...signingConfigs {release {if (project.hasProperty('myapp.signing.keystore')) {storeFile file(project.property('myapp.signing.keystore'))storePassword project.property('myapp.signing.store_password')keyAlias project.property('myapp.signing.key_alias')keyPassword project.property('myapp.signing.key_password')}}}buildTypes {release {signingConfig signingConfigs.releaseminifyEnabled trueuseProguard trueproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}
}
創建android/gradle.properties
文件存儲簽名配置:
# 簽名配置(敏感信息,不要提交到版本控制)
myapp.signing.keystore=../my-release-key.keystore
myapp.signing.store_password=your_store_password
myapp.signing.key_alias=my-key-alias
myapp.signing.key_password=your_key_password
重要提醒: gradle.properties
文件包含敏感信息,應該添加到.gitignore
文件中,避免提交到版本控制系統。
12.2.2 iOS代碼簽名
iOS的代碼簽名相對復雜,需要在蘋果開發者中心配置證書、標識符和描述文件。
配置開發者賬號
- 注冊Apple Developer賬號:訪問developer.apple.com注冊賬號(年費99美元)
- 創建App ID:在開發者中心創建應用標識符
- 生成證書:創建開發和發布證書
- 創建Provisioning Profile:關聯證書、設備和App ID
在Xcode中配置簽名
打開ios/Runner.xcworkspace
,在Xcode中配置簽名:
Target: Runner
-> Signing & Capabilities
-> Team: 選擇你的開發團隊
-> Bundle Identifier: 輸入你的應用包名
對于自動化構建,我們還需要配置ios/Runner/Info.plist
:
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleDisplayName</key>
<string>$(APP_DISPLAY_NAME)</string>
12.2.3 證書管理最佳實踐
- 使用環境變量存儲敏感信息:
# 在CI/CD系統中設置環境變量
export ANDROID_KEYSTORE_PASSWORD="your_password"
export IOS_CERTIFICATE_PASSWORD="your_password"
-
定期更新證書:
- Android密鑰庫建議25年有效期
- iOS證書每年需要更新
-
備份重要文件:
- 密鑰庫文件
- 證書文件
- 密碼信息
-
使用專用的簽名服務器:
對于企業級應用,考慮使用專門的簽名服務器,避免在開發機器上存儲生產環境的簽名證書。
12.3 GitHub Actions自動化構建
GitHub Actions是GitHub提供的CI/CD服務,可以自動化構建、測試和部署流程。對于Flutter項目,我們可以配置Actions來自動構建Android和iOS應用。
12.3.1 創建基礎工作流
在項目根目錄創建.github/workflows/build.yml
文件:
name: Build and Teston:push:branches: [ main, develop ]pull_request:branches: [ main ]jobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v3- name: Setup Flutteruses: subosito/flutter-action@v2with:flutter-version: '3.16.0'- name: Install dependenciesrun: flutter pub get- name: Run testsrun: flutter test- name: Analyze coderun: flutter analyzebuild-android:needs: testruns-on: ubuntu-lateststeps:- uses: actions/checkout@v3- name: Setup Flutteruses: subosito/flutter-action@v2with:flutter-version: '3.16.0'- name: Install dependenciesrun: flutter pub get- name: Build APKrun: flutter build apk --release- name: Upload APKuses: actions/upload-artifact@v3with:name: release-apkpath: build/app/outputs/flutter-apk/app-release.apkbuild-ios:needs: testruns-on: macos-lateststeps:- uses: actions/checkout@v3- name: Setup Flutteruses: subosito/flutter-action@v2with:flutter-version: '3.16.0'- name: Install dependenciesrun: flutter pub get- name: Build iOSrun: flutter build ios --release --no-codesign- name: Upload iOS builduses: actions/upload-artifact@v3with:name: release-iospath: build/ios/iphoneos/Runner.app
12.3.2 配置簽名自動化
為了在CI/CD中進行簽名,我們需要將簽名文件和密碼作為secrets存儲在GitHub中。
Android簽名配置
- 上傳密鑰庫文件:
# 將密鑰庫文件轉換為base64編碼
base64 my-release-key.keystore > keystore.base64
-
在GitHub設置secrets:
ANDROID_KEYSTORE_BASE64
:密鑰庫文件的base64編碼ANDROID_KEYSTORE_PASSWORD
:密鑰庫密碼ANDROID_KEY_ALIAS
:密鑰別名ANDROID_KEY_PASSWORD
:密鑰密碼
-
更新工作流配置:
build-android-signed:needs: testruns-on: ubuntu-lateststeps:- uses: actions/checkout@v3- name: Setup Flutteruses: subosito/flutter-action@v2with:flutter-version: '3.16.0'- name: Install dependenciesrun: flutter pub get- name: Decode keystorerun: |echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > android/app/my-release-key.keystore- name: Create key.propertiesrun: |cat > android/key.properties << EOFstorePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}storeFile=my-release-key.keystoreEOF- name: Build signed APKrun: flutter build apk --release- name: Build App Bundlerun: flutter build appbundle --release
iOS簽名配置
iOS的簽名配置更加復雜,需要配置證書和描述文件:
build-ios-signed:needs: testruns-on: macos-lateststeps:- uses: actions/checkout@v3- name: Setup Flutteruses: subosito/flutter-action@v2with:flutter-version: '3.16.0'- name: Install dependenciesrun: flutter pub get- name: Import certificates<