Flutter: 多平台发布与商店审核
📋 前置知识:需要先掌握以下内容
- 第24课:CI/CD 自动化
1. 你将学到
2. 一个被拒审的真实故事
3. 多平台发布流程
4. Android 发布
5. iOS 发布
6. Web 发布
7. 隐私合规
8. 完整示例:ShopApp 发布清单
9. ShopApp Release Checklist v1.2.3
Bob 按清单准备后,App Store 和 Google Play 首次提审即通过,Web 版 1 小时内上线。
graph TD
REL[Release Process] --> ANDROID2[Android AAB]
REL --> IOS2[iOS IPA]
REL --> WEB2[Web PWA]
ANDROID2 --> PC[Play Console]
IOS2 --> ASC[App Store Connect]
WEB2 --> FH[Firebase Hosting / Vercel]
PC --> REVIEW1[Google Review]
ASC --> REVIEW2[Apple Review]
REVIEW1 --> LIVE1[Live on Google Play]
REVIEW2 --> LIVE2[Live on App Store]
FH --> LIVE3[Live on Web]
(1) 签名与构建
▶ 示例:生成 Keystore
BASH
# Generate signing keystore
keytool -genkey -v -keystore shopapp-release.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias shopapp
# Verify keystore
keytool -list -v -keystore shopapp-release.jks -alias shopapp
build.gradle 签名配置
GROOVY
// android/app/build.gradle
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
defaultConfig {
applicationId "com.shopapp.app"
minSdkVersion 21
targetSdkVersion 34
versionCode 42
versionName "1.2.3"
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['keyFile'] ? file(keystoreProperties['keyFile']) : null
storePassword keystoreProperties['keyPassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
构建与发布
BASH
# Build release AAB (Android App Bundle)
flutter build appbundle --release
# Output: build/app/outputs/bundle/release/app-release.aab
# Build release APK (for testing)
flutter build apk --release
# Upload to Play Console manually or via CI/CD
(2) Play Console 上架清单
| 项目 | 要求 |
|---|---|
| Application ID | 唯一包名 com.shopapp.app |
| AAB 文件 | 签名的 release bundle |
| 图标 | 512x512 PNG |
| 截图 | 手机+7 寸平板各 2-8 张 |
| 隐私政策 URL | 必须可访问 |
| 内容分级 | 填写 IARC 问卷 |
| 目标受众 | 选择年龄范围 |
(1) Apple Developer 配置
| 步骤 | 操作 |
|---|---|
| 1. 注册 | Apple Developer Program ($99/年) |
| 2. 证书 | 创建 Distribution Certificate |
| 3. Profile | 创建 App Store Provisioning Profile |
| 4. App ID | 注册 Bundle Identifier |
| 5. App Record | App Store Connect 创建应用 |
Xcode 构建配置
XML
<!-- ios/Runner/Info.plist -->
`<key>`CFBundleShortVersionString</key>
`<string>`1.2.3</string>
`<key>`CFBundleVersion</key>
`<string>`42</string>
`<key>`NSAppTransportSecurity</key>
`<dict>`
`<key>`NSAllowsArbitraryLoads</key>
`<false/>`
`</dict>`
`<key>`NSUserTrackingUsageDescription</key>
`<string>`We use tracking to personalize your shopping experience.</string>
构建与上传
BASH
# Build IPA
flutter build ipa --release \
--export-options-plist=ios/ExportOptions.plist
# Output: build/ios/ipa/shopapp.ipa
# Upload via Xcode or command line
xcrun altool --upload-app \
--type ios \
--file build/ios/ipa/shopapp.ipa \
--apiKey YOUR_API_KEY \
--apiIssuer YOUR_ISSUER_ID
# Or via Transporter app
(2) App Store Connect 上架清单
| Bundle ID | 与 Xcode 项目一致 | | IPA 文件 | Release 构建 | | 截图 | 6.7"/6.5"/5.5" 三种尺寸 | | 预览视频 | 可选,≤30s | | 描述 | ≤4000 字符 |
▶ 示例:Apple 隐私清单合规
DART
import 'package:flutter/material.dart';
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
// ⚙️ 依赖:app_tracking_transparency: ^0.2.0, firebase_analytics: ^10.0.0(添加到 dependencies)
Future<void> requestTrackingPermission() async {
final status = await AppTrackingTransparency.requestTrackingAuthorization();
if (status == TrackingStatus.authorized) {
await FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(true);
} else {
await FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(false);
}
}
输出:
TEXT
📖 仅展示
App Tracking Transparency dialog displayed
Status: authorized (or denied)
Analytics collection enabled/disabled accordingly
(1) 合规要求对比
| 平台 | 必备项 |
|---|---|
| Google Play | 隐私政策 / 数据安全表单 / 内容分级 |
| App Store | 隐私政策 / ATT / 加密合规声明 |
| Web (PWA) | GDPR Cookie 同意 / 隐私政策 |
(2) 隐私合规清单
发布前检查
- [ ] 隐私政策页面已部署且链接可访问
- [ ] 数据收集声明清晰(用途、保留期、第三方共享)
- [ ] 用户可请求数据导出与删除
- [ ] Cookie Banner / ATT 弹窗已实现
- [ ] 未满 13 岁用户特殊保护(COPPA)
- [ ] App Store 数据类型标签已勾选
- [ ] Google Play 数据安全表单已提交
- [ ] GDPR / CCPA 合规审查通过
三平台发布清单
(1) Android
- [ ] Application ID 唯一
- [ ] Keystore 签名验证
- [ ] AAB 文件 ≤150MB
- [ ] 隐私政策 URL 可访问
- [ ] contentRating 问卷已填
- [ ] Target API Level 34
- [ ] Test AAB on device via
bundletool - [ ] Upload AAB to Play Console
- [ ] Fill store listing (screenshots, description, privacy policy)
- [ ] Start rollout to 10% → 50% → 100%
(2) iOS
- [ ] Distribution certificate valid
- [ ] Provisioning profile matches Bundle ID
- [ ] Build IPA:
flutter build ipa --release - [ ] Upload IPA via Transporter
- [ ] Fill App Store Connect listing
- [ ] Submit for review (allow 24-48 hours)
(3) Web
- [ ] Build:
flutter build web --wasm - [ ] Test in Chrome/Safari/Firefox
- [ ] Deploy to Firebase Hosting
- [ ] Verify URL routing works
- [ ] Test PWA install prompt
(4) Post-release
- [ ] Create GitHub Release with notes
- [ ] Notify team via Slack
- [ ] Monitor Crashlytics for 24 hours
- [ ] Monitor user reviews
❓ 常见问题
Q AAB 和 APK 有什么区别?
A AAB (Android App Bundle) 是上传格式,Google Play 自动生成优化的 APK。AAB 体积更小,只包含设备需要的资源。
Q App Store 审核一般多久?
A 首次提交通常 24-48 小时,更新通常 12-24 小时。被拒后重新提交需再次等待。
Q 版本号 versionCode 和 versionName 怎么管理?
A versionCode 必须递增(整数),每次发布 +1;versionName 是用户可见的(如 1.2.3)。用 SemVer 规范。
Q Web 版需要备案吗?
A 中国大陆服务器需要 ICP 备案。海外服务器(Firebase/Vercel)不需要。
Q GDPR 合规需要做什么?
A 1) 首次启动弹隐私同意;2) 提供数据删除入口;3) 隐私政策链接;4) 数据最小化收集;5) 用户可撤销同意。
Q 被 App Store 拒审后怎么办?
A 仔细阅读拒审原因,修改后重新提交。常见原因:缺失隐私政策、ATT 未弹窗、崩溃、内容违规。
📖 小节
(1) 示例:版本号发布节奏
TEXT
📖 仅展示
1.0.0 → MVP 上线(首次)
1.0.1 → 紧急 Bug 修复(patch)
1.1.0 → 新功能(minor)
1.2.3 → ShopApp 发布(minor + patch)
2.0.0 → 重大重构(major)
输出:
TEXT
📖 仅展示
SemVer: MAJOR.MINOR.PATCH
- MAJOR 破坏性变更
- MINOR 兼容新增
- PATCH 兼容修复
(2) 示例:Firebase Crashlytics 集成
DART
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'dart:ui' show PlatformDispatcher;
// ⚙️ 依赖:firebase_core: ^2.0.0, firebase_crashlytics: ^3.0.0(添加到 dependencies)
// ShopApp 来自项目 lib/app.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FlutterError.onError = (details) {
FirebaseCrashlytics.instance.recordFlutterFatalError(details);
};
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(const ShopApp());
}
输出:
TEXT
📖 仅展示
Crashlytics initialized
Fatal errors auto-reported
Non-fatal errors auto-reported
(3) 示例:发布监控仪表盘
BASH
# Firebase Crashlytics
firebase functions:log --only publishMonitor
# Expected output:
# {"status":"ok","version":"1.2.3","crashRate":"0.05%"}
输出:
TEXT
📖 仅展示
Crash rate after rollout:
- 1h: 0.05% (baseline 0.10%)
- 24h: 0.04% (healthy)
- ANR rate: 0.01% (acceptable)
📝 作业
- 基础题(难度⭐):为ShopApp生成Android Keystore并配置build.gradle签名,成功构建release AAB
- 进阶题(难度⭐⭐):完成ShopApp双平台发布准备——Android AAB签名+iOS证书配置,编写多平台发布清单
- 挑战题(难度⭐⭐⭐):实现ShopApp全平台发布流程(Android+iOS+Web),包含隐私合规声明、多渠道截图生成、App Store描述撰写