开发完成只是起点,将App交付到用户手中才是终点。本课覆盖Flutter App从签名配置到商店上架的完整发布流程。你将学会创建签名密钥、配置构建变体、搭建CI/CD自动化管线,以及应对各应用商店的审核要求。
# 1. 创建上传密钥(upload keystore)
keytool -genkeypair -v \
-keystore upload-keystore.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias upload \
-storepass YOUR_STORE_PASSWORD \
-keypass YOUR_KEY_PASSWORD
# 2. 查看密钥信息
keytool -list -v -keystore upload-keystore.jks -alias upload
# 3. 生成SHA-1和SHA-256指纹(Firebase/Google Sign-In需要)
keytool -list -v \
-keystore upload-keystore.jks \
-alias upload \
-storepass YOUR_STORE_PASSWORD
// android/key.properties (不要提交到版本控制!)
storePassword=YOUR_STORE_PASSWORD
keyPassword=YOUR_KEY_PASSWORD
keyAlias=upload
storeFile=/path/to/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 keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
# iOS签名流程(需要Apple Developer账号)
# 1. 创建证书签名请求(CSR)
# 在"钥匙串访问" → 证书助理 → 从证书颁发机构请求证书
# 2. 在Apple Developer创建:
# - Development Certificate (开发)
# - Distribution Certificate (发布)
# - Provisioning Profile (配置描述文件)
# 3. Xcode自动签名(推荐)
# Xcode → Signing & Capabilities →
# ✅ Automatically manage signing
# Team: 选择你的开发者团队
# Bundle Identifier: com.yourcompany.appname
# 4. 手动签名(企业分发等场景)
# 下载.cer证书 → 双击安装到钥匙串
# 下载.mobileprovision → 放到 ~/Library/MobileDevice/Provisioning Profiles/
key.properties和.jks文件绝不能提交到Git!在.gitignore中添加# Android APK (单个通用包)
flutter build apk --release
# Android App Bundle (推荐,Google Play要求)
flutter build appbundle --release
# 分架构APK (体积更小)
flutter build apk --split-per-abi
# 输出:
# app-armeabi-v7a-release.apk (~5MB)
# app-arm64-v8a-release.apk (~6MB)
# app-x86_64-release.apk (~6MB)
# iOS
flutter build ios --release
# 或直接用Xcode: Product → Archive
# 查看包大小分析
flutter build apk --analyze-size
flutter build appbundle --analyze-size
// android/app/build.gradle - 多渠道配置
android {
flavorDimensions "channel"
productFlavors {
google {
dimension "channel"
applicationIdSuffix ".google"
manifestPlaceholders = [CHANNEL: "google"]
}
huawei {
dimension "channel"
applicationIdSuffix ".huawei"
manifestPlaceholders = [CHANNEL: "huawei"]
}
xiaomi {
dimension "channel"
applicationIdSuffix ".xiaomi"
manifestPlaceholders = [CHANNEL: "xiaomi"]
}
oppo {
dimension "channel"
applicationIdSuffix ".oppo"
manifestPlaceholders = [CHANNEL: "oppo"]
}
}
}
// android/app/src/main/AndroidManifest.xml
// 在application标签内添加渠道meta-data
<meta-data
android:name="CHANNEL"
android:value="${CHANNEL}" />
// Dart端读取渠道信息
import 'package:flutter/services.dart';
class ChannelInfo {
static const _channel = MethodChannel('app_info');
static Future<String> getChannel() async {
try {
return await _channel.invokeMethod('getChannel') ?? 'unknown';
} on PlatformException {
return 'unknown';
}
}
}
// 构建各渠道包
// flutter build apk --flavor google
// flutter build apk --flavor huawei
// flutter build appbundle --flavor google
# .github/workflows/build.yml
name: Flutter CI/CD
on:
push:
branches: [main, release/*]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.22.0'
channel: 'stable'
- name: Install dependencies
run: flutter pub get
- name: Run tests
run: flutter test --coverage
- name: Check formatting
run: dart format --set-exit-if-changed .
- name: Analyze code
run: flutter analyze --no-fatal-infos
build-android:
needs: test
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/heads/release/')
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.22.0'
- name: Decode keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/upload-keystore.jks
echo "storePassword=${{ secrets.KEYSTORE_PASSWORD }}" > android/key.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties
echo "keyAlias=upload" >> android/key.properties
echo "storeFile=upload-keystore.jks" >> android/key.properties
- name: Build AAB
run: flutter build appbundle --flavor google --release
- name: Upload to Google Play
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
packageName: com.yourcompany.appname
releaseFiles: build/app/outputs/bundle/googleRelease/app-google-release.aab
track: internal # internal, alpha, beta, production
whatsNewDirectory: distribution/whatsnew
build-ios:
needs: test
runs-on: macos-latest
if: startsWith(github.ref, 'refs/heads/release/')
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.22.0'
- name: Install Apple certificates
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
p12-password: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
- name: Install provisioning profile
run: |
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
echo "${{ secrets.IOS_PROVISION_PROFILE_BASE64 }}" | base64 -d > ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision
- name: Build IPA
run: |
flutter build ios --release --no-codesign
xcodebuild -workspace ios/Runner.xcworkspace \
-scheme Runner \
-configuration Release \
-archivePath build/Runner.xcarchive \
archive \
CODE_SIGN_IDENTITY="Apple Distribution" \
DEVELOPMENT_TEAM="${{ secrets.DEVELOPMENT_TEAM }}"
xcodebuild -exportArchive \
-archivePath build/Runner.xcarchive \
-exportOptionsPlist ios/ExportOptions.plist \
-exportPath build/ipa
- name: Upload to App Store Connect
run: |
xcrun altool --upload-app \
--type ios \
--file build/ipa/Runner.ipa \
--apiKey "${{ secrets.APP_STORE_API_KEY }}" \
--apiIssuer "${{ secrets.APP_STORE_API_ISSUER }}"
| 平台 | 要求 | 注意事项 |
|---|---|---|
| Google Play | AAB格式、目标API 33+、隐私政策 | App Signing、内容分级、数据安全声明 |
| App Store | IPA格式、Xcode构建、Review Guidelines | Human Interface Guidelines、隐私清单、ATS |
| 华为应用市场 | APK/AAB、隐私声明 | 华为账号体系、HMS Core适配 |
| 小米/OPPO/vivo | APK、隐私合规 | 各平台开发者认证流程不同 |
// pubspec.yaml 版本号管理
# format: major.minor.patch+buildNumber
version: 1.2.3+45 # 1.2.3是版本号, 45是构建号
// Dart代码中获取版本号
import 'package:package_info_plus/package_info_plus.dart';
class AppInfo {
static Future<String> get version async {
final info = await PackageInfo.fromPlatform();
return '${info.version}+${info.buildNumber}';
}
}
// Semantic Versioning策略:
// MAJOR: 不兼容的API变更(2.0.0)
// MINOR: 向后兼容的功能新增(1.1.0)
// PATCH: 向后兼容的问题修复(1.0.1)
// Build Number: 每次构建递增(CI自动维护)
--analyze-size分析包体积,移除不必要的资源minifyEnabled+shrinkResources减小APK体积flutter test和flutter drive跑完所有测试fastlane自动化截图上传和多语言商店信息发布后的App需要数据驱动——了解用户行为、监控崩溃、优化性能。
// Firebase Analytics 埋点
class AnalyticsService {
final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
// 页面浏览
Future<void> logPageView(String pageName) async {
await _analytics.logEvent(
name: 'page_view',
parameters: {'page_name': pageName},
);
}
// 自定义事件
Future<void> logAddToCart(Product product, int quantity) async {
await _analytics.logEvent(
name: 'add_to_cart',
parameters: {
'item_id': product.id,
'item_name': product.name,
'item_category': product.category,
'price': product.price,
'quantity': quantity,
},
);
}
// 购买完成
Future<void> logPurchase(Order order) async {
await _analytics.logPurchase(
value: order.total,
currency: 'CNY',
items: order.items.map((i) => AnalyticsEventItem(
itemId: i.product.id,
itemName: i.product.name,
itemCategory: i.product.category,
price: i.product.price,
quantity: i.quantity,\ )).toList(),
);
}
// 设置用户属性
Future<void> setUserProperty(String name, String value) async {
await _analytics.setUserProperty(name: name, value: value);
}
}
// Crashlytics 崩溃监控
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// 初始化Crashlytics
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(const MyApp());
}
// 自定义错误上报
try {
await processPayment(order);
} catch (e, stack) {
await FirebaseCrashlytics.instance.recordError(
e, stack,
reason: '支付失败: order=${order.id}',
information: ['用户ID: ${user.id}', '订单金额: ${order.total}'],
);
}
// Flutter Performance Overlay
// 在MaterialApp中开启
MaterialApp(
showPerformanceOverlay: kDebugMode, // 仅调试模式
// ...
);
// 自定义性能追踪
class PerformanceTrace {
static Future<T> trace<T>(String name, Future<T> Function() fn) async {
final stopwatch = Stopwatch()..start();
try {
final result = await fn();
stopwatch.stop();
// 上报性能数据
await FirebaseAnalytics.instance.logEvent(
name: 'performance',
parameters: {
'trace_name': name,
'duration_ms': stopwatch.elapsedMilliseconds,
},
);
return result;
} catch (e) {
stopwatch.stop();
debugPrint('❌ $name failed in ${stopwatch.elapsedMilliseconds}ms');
rethrow;
}
}
}
// 使用示例
final products = await PerformanceTrace.trace('load_products', () {
return ProductService().getProducts(page: 1);
});
你已掌握App发布的完整流程!
下一课预告:社交App——Feed流、关注系统、通知推送、图片分享,综合运用所有技能!