Appearance
Flutter发布与部署
Flutter应用开发完成后,需要将其发布到各个应用商店或部署到目标环境中。本章将详细介绍Flutter应用的发布与部署流程,包括构建配置、应用商店发布、自动化部署等。
构建配置
构建模式
Flutter提供三种构建模式:
- Debug模式:用于开发和调试
- Profile模式:用于性能分析
- Release模式:用于发布
bash
# Debug模式(默认)
flutter run
# Profile模式
flutter run --profile
# Release模式
flutter run --release
构建配置文件
在android/app/build.gradle中配置构建选项:
groovy
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// 应用ID
applicationId "com.example.myapp"
// 最低支持的Android版本
minSdkVersion flutter.minSdkVersion
// 目标Android版本
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
// 多Dex支持(如果应用超过65K方法)
multiDexEnabled true
}
signingConfigs {
release {
// 从local.properties读取签名信息
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// 启用代码混淆
minifyEnabled true
// 启用资源压缩
shrinkResources true
// 使用release签名配置
signingConfig signingConfigs.release
// 混淆规则文件
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
// 构建变体配置
flavorDimensions "version"
productFlavors {
dev {
dimension "version"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
}
prod {
dimension "version"
// 生产环境配置
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.multidex:multidex:2.0.1'
}
iOS构建配置
在ios/Runner.xcodeproj/project.pbxproj中配置构建选项:
// 示例配置片段
/* Begin XCBuildConfiguration section */
1B12E1A122C8AFE200321753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
FLUTTER_BUILD_MODE = release;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.example.myapp;
PRODUCT_NAME = $(TARGET_NAME);
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "SWIFT_PACKAGE";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
应用签名
Android应用签名
- 创建密钥库:
bash
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload
- 在
android/key.properties中存储密钥信息:
storePassword=your-store-password
keyPassword=your-key-password
keyAlias=upload
storeFile=/home/user/upload-keystore.jks
- 在
android/app/build.gradle中引用密钥信息(如上所示)
iOS应用签名
- 在Apple Developer网站创建证书和配置文件
- 使用Xcode自动管理签名或手动配置
- 配置Bundle Identifier和其他必要设置
构建APK/IPA
构建Android APK
bash
# 构建Release版APK
flutter build apk --release
# 构建分架构APK(减小APK大小)
flutter build apk --split-per-abi
# 构建App Bundle(推荐用于Google Play)
flutter build appbundle --release
构建iOS IPA
bash
# 在iOS目录下使用Xcode构建
cd ios
xcodebuild -workspace Runner.xcworkspace -scheme Runner archive
# 或使用Flutter命令(需要在macOS上)
flutter build ios --release
应用商店发布
Google Play发布
准备应用商店素材:
- 应用图标(512x512 PNG)
- 屏幕截图(各种设备尺寸)
- 应用描述和关键词
- 分类和内容分级
创建发布清单:
yaml
# fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Submit a new Beta Build to Crashlytics Beta"
lane :beta do
gradle(task: "clean assembleRelease")
crashlytics
end
desc "Deploy a new version to the Google Play"
lane :deploy do
gradle(task: "clean assembleRelease")
upload_to_play_store
end
end
- 发布流程:
bash
# 1. 构建App Bundle
flutter build appbundle --release
# 2. 构建分离APK(可选)
flutter build apk --split-per-abi --release
# 3. 使用bundletool验证
# bundletool build-apks --bundle=build/app/outputs/bundle/release/app-release.aab --output=my_app.apks
# 4. 上传到Google Play Console
Apple App Store发布
- 使用Transporter上传:
bash
# 构建iOS应用
flutter build ios --release
# 在Xcode中归档并上传
# Product -> Archive -> Distribute App
- 使用命令行工具:
bash
# 使用fastlane自动化发布
fastlane init
fastlane deliver --skip_binary_upload --skip_screenshots --skip_metadata
应用配置管理
环境配置
dart
// config/environment.dart
enum Environment { dev, staging, prod }
class AppConfig {
static Environment _env = Environment.prod;
static void setEnvironment(Environment env) {
_env = env;
}
static String get baseUrl {
switch (_env) {
case Environment.dev:
return 'https://api-dev.example.com';
case Environment.staging:
return 'https://api-staging.example.com';
case Environment.prod:
return 'https://api.example.com';
}
}
static bool get enableLogging {
return _env != Environment.prod;
}
static bool get enableDebugFeatures {
return _env != Environment.prod;
}
}
构建时配置
dart
// main_dev.dart
import 'config/environment.dart';
void main() {
AppConfig.setEnvironment(Environment.dev);
runApp(MyApp());
}
// main_prod.dart
import 'config/environment.dart';
void main() {
AppConfig.setEnvironment(Environment.prod);
runApp(MyApp());
}
构建命令:
bash
# 使用不同入口文件构建不同环境
flutter build apk --flavor dev --target lib/main_dev.dart
flutter build apk --flavor prod --target lib/main_prod.dart
自动化部署
使用GitHub Actions
yaml
# .github/workflows/deploy.yml
name: Deploy to App Stores
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
deploy-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: '11'
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
channel: 'stable'
- name: Get dependencies
run: flutter pub get
- name: Run tests
run: flutter test
- name: Build APK
run: flutter build apk --release
- name: Build App Bundle
run: flutter build appbundle --release
- name: Deploy to Google Play
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.example.myapp
releaseFiles: app/build/outputs/bundle/release/app-release.aab
track: internal
status: completed
deploy-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
channel: 'stable'
- name: Get dependencies
run: flutter pub get
- name: Build iOS
run: flutter build ios --release --no-codesign
- name: Deploy to App Store Connect
run: |
xcrun altool --upload-app -f build/ios/ipa/Runner.ipa -u ${{ secrets.APPLE_ID }} -p ${{ secrets.APP_PASSWORD }}
使用Fastlane
ruby
# fastlane/lanes/deploy_android.rb
desc "Deploy Android app to Google Play"
lane :deploy_android do
gradle(
task: "clean",
build_type: "Release"
)
# Build Flutter app
sh("flutter", "build", "appbundle", "--release")
# Upload to Google Play
supply(
track: "internal",
aab: "build/app/outputs/bundle/release/app-release.aab",
json_key: ENV["GOOGLE_PLAY_JSON_KEY"]
)
end
# fastlane/lanes/deploy_ios.rb
desc "Deploy iOS app to App Store Connect"
lane :deploy_ios do
# Build Flutter app
sh("flutter", "build", "ios", "--release", "--no-codesign")
# Build with xcodebuild
build_app(
workspace: "ios/Runner.xcworkspace",
scheme: "Runner",
export_method: "app-store"
)
# Upload to App Store Connect
upload_to_app_store(
skip_waiting_for_build_processing: true
)
end
版本管理
版本号管理
yaml
# pubspec.yaml
name: my_app
description: A new Flutter project.
# 版本号格式:主版本号.次版本号.修订号
version: 1.0.0+1
environment:
sdk: '>=2.19.0 <4.0.0'
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
# ... 其他依赖
flutter:
uses-material-design: true
# ... 其他配置
自动化版本管理
bash
#!/bin/bash
# scripts/bump_version.sh
# 获取当前版本
current_version=$(grep 'version:' pubspec.yaml | cut -d ' ' -f 2 | cut -d '+' -f 1)
build_number=$(grep 'version:' pubspec.yaml | cut -d '+' -f 2)
# 解析版本号
IFS='.' read -ra VERSION_PARTS <<< "$current_version"
major=${VERSION_PARTS[0]}
minor=${VERSION_PARTS[1]}
patch=${VERSION_PARTS[2]}
case $1 in
major)
major=$((major + 1))
minor=0
patch=0
;;
minor)
minor=$((minor + 1))
patch=0
;;
patch)
patch=$((patch + 1))
;;
*)
echo "Usage: $0 {major|minor|patch}"
exit 1
;;
esac
new_version="$major.$minor.$patch"
new_build_number=$((build_number + 1))
# 更新pubspec.yaml
sed -i.bak "s/version: $current_version+$build_number/version: $new_version+$new_build_number/" pubspec.yaml
echo "Version bumped from $current_version+$build_number to $new_version+$new_build_number"
应用商店配置
Google Play Console配置
应用商店列表页面:
- 应用名称
- 短描述
- 长描述
- 关键词
- 应用类别
- 内容分级
图形资产:
- 应用图标
- 功能图像
- 屏幕截图
- 宣传视频(可选)
政策合规:
- 隐私政策URL
- 应用内容说明
- 数据安全表单
App Store Connect配置
应用信息:
- 应用名称
- 副标题
- 描述
- 关键词
- 支持URL
- 营销URL
分类和评级:
- 主要类别
- 次要类别
- 年龄分级
隐私:
- 数据使用说明
- 隐私政策URL
发布后监控
错误监控
dart
// services/error_reporting_service.dart
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
class ErrorReportingService {
static Future<void> init() async {
// 启用Firebase Crashlytics
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
// 设置错误处理
FlutterError.onError = (FlutterErrorDetails details) {
FirebaseCrashlytics.instance.recordFlutterError(details);
};
// 捕获未处理异常
PlatformDispatcher.instance.onError = (Object error, StackTrace stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
}
static void recordError(dynamic error, StackTrace stackTrace) {
FirebaseCrashlytics.instance.recordError(error, stackTrace);
}
static void recordNonFatalError(dynamic error, StackTrace stackTrace) {
FirebaseCrashlytics.instance.recordError(error, stackTrace);
}
}
性能监控
dart
// services/performance_monitoring_service.dart
import 'package:firebase_performance/firebase_performance.dart';
class PerformanceMonitoringService {
static final _perf = FirebasePerformance.instance;
static Future<void> startTrace(String traceName) async {
final trace = _perf.newTrace(traceName);
await trace.start();
}
static Future<void> stopTrace(String traceName) async {
final trace = _perf.getTrace(traceName);
await trace.stop();
}
static Future<String> observeHttpTracing(Uri uri) async {
final httpMetric = _perf.newHttpMetric(uri, HttpMethod.Get);
await httpMetric.start();
// 执行HTTP请求
final response = await http.get(uri);
// 设置响应数据
httpMetric.responseContentType = response.headers['content-type'];
httpMetric.responsePayloadSize = response.body.length;
httpMetric.httpResponseCode = response.statusCode;
await httpMetric.stop();
return response.body;
}
}
热更新和动态化
使用CodePush(已停止服务,可考虑替代方案)
dart
// 现代替代方案:使用Flutter的动态功能或云函数
class DynamicFeatureService {
static Future<void> checkForUpdates() async {
// 实现动态更新检查逻辑
// 可以结合Firebase Remote Config或其他后端服务
}
}
使用Firebase Remote Config
dart
// services/remote_config_service.dart
import 'package:firebase_remote_config/firebase_remote_config.dart';
class RemoteConfigService {
static final RemoteConfig _remoteConfig = RemoteConfig.instance;
static Future<void> init() async {
// 设置默认值
await _remoteConfig.setDefaults(<String, dynamic>{
'show_feature_x': true,
'feature_x_threshold': 100,
'welcome_message': 'Welcome!',
});
// 获取配置(有超时设置)
await _remoteConfig.fetchAndActivate();
}
static bool getBool(String key) {
return _remoteConfig.getBool(key);
}
static int getInt(String key) {
return _remoteConfig.getInt(key);
}
static String getString(String key) {
return _remoteConfig.getString(key);
}
}
发布检查清单
发布前检查清单
- [ ] 测试所有功能在Release模式下正常工作
- [ ] 验证应用在不同设备和屏幕尺寸上的表现
- [ ] 检查应用权限请求是否合理
- [ ] 验证应用在弱网络环境下的表现
- [ ] 确认应用符合各应用商店的政策要求
- [ ] 检查应用包大小是否合理
- [ ] 验证崩溃报告和错误监控正常工作
- [ ] 确认隐私政策和用户协议已准备就绪
- [ ] 准备好应用商店发布的所有素材
- [ ] 验证付费功能和订阅设置(如果有)
性能验证
bash
# 验证APK大小
flutter build apk --release
du -h build/app/outputs/apk/release/app-release.apk
# 验证启动时间
adb shell am start -W -n com.example.myapp/.MainActivity
# 验证内存使用
adb shell dumpsys meminfo com.example.myapp
发布后维护
版本回滚计划
markdown
# 版本回滚计划
## 触发条件
- 关键功能出现严重bug
- 性能严重下降
- 大量用户投诉
## 回滚步骤
1. 立即停止新版本推广
2. 在应用商店暂停新版本分发
3. 必要时提交紧急修复版本
4. 通知用户暂时回退到旧版本
5. 分析问题原因并制定长期解决方案
用户反馈处理
dart
// services/feedback_service.dart
import 'package:firebase_analytics/firebase_analytics.dart';
class FeedbackService {
static final analytics = FirebaseAnalytics.instance;
static Future<void> logUserAction(String action, Map<String, dynamic>? params) async {
await analytics.logEvent(name: action, parameters: params);
}
static Future<void> logScreenView(String screenName) async {
await analytics.logScreenView(screenName: screenName);
}
static Future<void> setUserProperty(String name, String value) async {
await analytics.setUserProperty(name: name, value: value);
}
}
常见部署问题及解决方案
1. 构建失败
bash
# 检查Flutter环境
flutter doctor
# 清理构建缓存
flutter clean
flutter pub get
# 检查依赖冲突
flutter pub deps
2. 应用商店审核被拒
markdown
常见拒绝原因及解决方案:
1. 应用崩溃或功能异常
- 解决:彻底测试所有功能
2. 隐私政策不合规
- 解决:提供详细隐私政策
3. 应用描述与实际功能不符
- 解决:确保描述准确反映功能
4. 未声明数据收集用途
- 解决:在应用商店填写数据使用说明
3. 性能问题
dart
// 性能监控工具类
class PerformanceChecker {
static void measureFunction(String name, Function fn) {
final start = DateTime.now();
fn();
final end = DateTime.now();
final duration = end.difference(start);
if (duration.inMilliseconds > 16) {
print('Performance warning: $name took ${duration.inMilliseconds}ms');
}
}
}
总结
Flutter应用的发布与部署是一个复杂但关键的过程,涉及多个环节:
- 构建配置:正确配置构建参数和签名
- 应用商店发布:遵循各平台的发布规范
- 自动化部署:使用CI/CD工具提高效率
- 版本管理:实施有效的版本控制策略
- 发布监控:持续监控应用性能和错误
- 维护更新:建立完善的后续维护机制
通过系统性的发布流程和持续的监控优化,可以确保Flutter应用在各个平台上稳定运行并提供良好的用户体验。