第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 URL
static 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.release
minifyEnabled true
useProguard true
proguardFiles 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 Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- name: Install dependencies
run: flutter pub get
- name: Run tests
run: flutter test
- name: Analyze code
run: flutter analyze
build-android:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- name: Install dependencies
run: flutter pub get
- name: Build APK
run: flutter build apk --release
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: release-apk
path: build/app/outputs/flutter-apk/app-release.apk
build-ios:
needs: test
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- name: Install dependencies
run: flutter pub get
- name: Build iOS
run: flutter build ios --release --no-codesign
- name: Upload iOS build
uses: actions/upload-artifact@v3
with:
name: release-ios
path: 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: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- name: Install dependencies
run: flutter pub get
- name: Decode keystore
run: |
echo "${
{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > android/app/my-release-key.keystore
- name: Create key.properties
run: |
cat > android/key.properties << EOF
storePassword=${
{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyPassword=${
{ secrets.ANDROID_KEY_PASSWORD }}
keyAlias=${
{ secrets.ANDROID_KEY_ALIAS }}
storeFile=my-release-key.keystore
EOF
- name: Build signed APK
run: flutter build apk --release
- name: Build App Bundle
run: flutter build appbundle --release
iOS签名配置
iOS的签名配置更加复杂,需要配置证书和描述文件:
build-ios-signed:
needs: test
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- name: Install dependencies
run: flutter pub get
- name: Import certificates
<