第18课完成了测试策略的学习。本课进入部署与发布模块:四端发布全景与约束差异、Android的签名配置与Google Play发布、iOS的Xcode归档与TestFlight/App Store发布、Desktop的jpackage原生安装包打包、Web(Wasm)的静态托管部署,以及CI/CD流水线设计与版本管理策略。本课内容基于CMP 1.12.1、Kotlin 2.3.20与AGP 9.0。
目录
- 一、发布全景:四端分发的差异与约束
- 二、Android:签名配置与Google Play发布
- 三、iOS:Xcode归档与TestFlight发布
- 四、Desktop:jpackage原生安装包
- 五、Web(Wasm):静态托管部署
- 六、CI/CD流水线设计
- 配套习题(15题)
- 本课知识点总结
文末附有15道配套习题与本课知识点总结。
一、发布全景:四端分发的差异与约束
CMP应用发布到四个平台,每个平台的分发机制、约束条件和产物格式都不同。理解这些差异,是规划发布流程的前提。
| 平台 | 分发渠道 | 产物格式 | 核心约束 | 成熟度 |
|---|---|---|---|---|
| Android | Google Play / 国内应用商店 | APK / AAB | keystore签名,versionCode递增 | Stable |
| iOS | App Store / TestFlight | IPA | 必须在macOS上构建,需要Apple Developer账号、Xcode签名和公证 | Stable(CMP 1.8.0起) |
| Desktop | 官网 / GitHub Releases | .msi/.exe(Windows)、.dmg/.pkg(macOS)、.deb/.rpm(Linux) | 不支持交叉编译,各平台需在对应操作系统上构建,打包需JDK 17+ | Stable |
| Web | 静态托管(GitHub Pages、Cloudflare Pages、Vercel、Netlify) | HTML + JS + Wasm | 需配置SPA重写规则和.wasm MIME类型,Web目标为Beta | Beta |
三个关键跨平台约束:
桌面端不支持交叉编译。jpackage工具只能为当前操作系统生成安装包——只能在macOS上构建.dmg,在Windows上构建.msi,在Linux上构建.deb。发布全平台桌面应用需要三种操作系统(或CI环境)。官方打包工具jpackage要求JDK 17或更高版本。
iOS构建必须在macOS上进行。这是苹果生态的硬性限制,CMP代码需要编译为Native二进制并通过Xcode签名和归档。
Web目标为Beta状态。CMP的Web目标(Wasm)在CMP 1.9.0中进入Beta,建议先发布Android/iOS/Desktop,Web作为试点。
下面是四端发布全景图:
flowchart TD
A["CMP 应用源码"] --> B{"选择发布平台"}
B -->|"Android"| C["Google Play / 国内商店"]
B -->|"iOS"| D["App Store / TestFlight"]
B -->|"Desktop"| E["官网 / GitHub Releases"]
B -->|"Web"| F["静态托管"]
C --> C1["产物: APK / AAB"]
C1 --> C2["约束: keystore签名, versionCode递增"]
D --> D1["产物: IPA"]
D1 --> D2["约束: 必须在macOS上构建"]
E --> E1["产物: .dmg / .msi / .deb"]
E1 --> E2["约束: 不支持交叉编译, JDK 17+"]
F --> F1["产物: HTML + JS + Wasm"]
F1 --> F2["约束: SPA重写规则, .wasm MIME类型"]
C2 & D2 & E2 & F2 --> G["CI/CD 流水线统一管理"]
二、Android:签名配置与Google Play发布
2.1 生成签名密钥
Android应用必须用密钥签名才能安装到设备或上传到商店。使用keytool生成:
keytool -genkey -v -keystore my-release-key.keystore \
-alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
2.2 配置签名
在androidApp/build.gradle.kts中配置签名,通过环境变量读取敏感信息:
android {
signingConfigs {
create("release") {
storeFile = file(System.getenv("KEYSTORE_PATH") ?: "my-release-key.keystore")
storePassword = System.getenv("KEYSTORE_PASSWORD")
keyAlias = System.getenv("KEYSTORE_ALIAS")
keyPassword = System.getenv("KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
关键实践:签名密钥文件(.keystore/.jks)绝不提交到版本控制,应加入.gitignore。密码通过环境变量注入,在CI中通过GitHub Secrets管理。
2.3 构建AAB
Google Play要求上传AAB(Android App Bundle),而非APK。AAB包含所有编译代码和资源,Play Store会根据设备配置生成优化的APK,减小下载体积。
# 构建Release AAB
./gradlew :androidApp:bundleRelease
# 输出位置:androidApp/build/outputs/bundle/release/androidApp-release.aab
Google Play支持最大4GB的累计下载大小限制。如果应用启用了代码混淆,最佳实践是在发布时上传解混淆文件(mapping文件)。
2.4 本地验证AAB
上传前可使用bundletool在本地验证AAB的正确性:
# 从AAB生成APK集合
java -jar bundletool.jar build-apks \
--bundle=androidApp-release.aab \
--output=my_app.apks \
--ks=my-release-key.keystore \
--ks-pass=pass:KEYSTORE_PASSWORD \
--ks-key-alias=my-key-alias \
--key-pass=pass:KEY_PASSWORD
# 安装到连接的设备
java -jar bundletool.jar install-apks --apks=my_app.apks
2.5 使用Fastlane自动化Google Play发布
Fastlane的supply lane可以自动化上传AAB到Google Play。配置需要Google Play Console的服务账号JSON密钥:
# fastlane/Fastfile
platform :android do
desc "Deploy to Internal Testing Track"
lane :deployInternal do
upload_to_play_store(
track: 'internal',
aab: lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH],
json_key: 'secrets/playStorePublishServiceCredentialsFile.json',
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
end
下面是 Android 发布流程:
flowchart LR
A["keytool 生成签名密钥"] --> B["build.gradle.kts 配置签名"]
B --> C["构建 AAB 产物"]
C --> D["bundletool 本地验证"]
D --> E{"验证通过?"}
E -->|"否"| C
E -->|"是"| F["Fastlane supply 上传"]
F --> G["Google Play Internal Track"]
三、iOS:Xcode归档与TestFlight发布
3.1 构建前置条件
iOS发布需要以下准备:
- Apple Developer账号(个人或组织)
- macOS主机——iOS构建必须在macOS上进行
- Xcode——用于归档、签名和上传
- App ID与Provisioning Profile——在Apple Developer Portal中配置
3.2 在Xcode中归档
iOS的发布流程通过Xcode的Organizer完成:
- 在Xcode中打开
iosApp/iosApp.xcodeproj - 选择 Product → Archive,构建Release版本并生成归档
- 在Organizer中,点击 Validate App 验证归档
- 验证通过后,点击 Distribute App 上传到App Store Connect
关键配置:在Xcode的Signing & Capabilities中,确保选择正确的Team和Provisioning Profile。对于Compose Multiplatform iOS应用,需要确保Kotlin/Native生成的framework被正确嵌入并签名(Embed & Sign)。
macOS App Store的额外约束:Apple要求每个二进制文件都被签名——包括应用主体、JVM运行时、Kotlin原生库、每个.dylib。每个都需要Provisioning Profile,一个未签名的文件就会导致拒绝。推荐的做法是跳过Mac App Store,通过签名并公证的DMG在GitHub Releases分发。
3.3 使用Fastlane自动化iOS发布
Fastlane的pilot lane可以自动化TestFlight上传,使用App Store Connect API Key进行认证:
# fastlane/Fastfile
platform :ios do
desc "Build and upload to TestFlight"
lane :upload_testflight do
build_app(
scheme: "iosApp",
export_method: "app-store",
output_directory: "build/ios"
)
upload_to_testflight(
api_key_path: "secrets/asc_api_key.json",
skip_waiting_for_build_processing: true
)
end
end
已知陷阱:版本注入静默失败。当KMP项目中GENERATE_INFOPLIST_FILE = true时,Xcode会动态生成Info.plist,导致Fastlane的increment_version_number等操作无效。解决方案有两种:将GENERATE_INFOPLIST_FILE设为false,或直接在Fastfile中通过xcargs注入版本属性:
build_app(
xcargs: [
"MARKETING_VERSION=#{sanitized_version}",
"CURRENT_PROJECT_VERSION=#{build_number}"
].join(" ")
)
3.4 版本号管理
iOS的版本号由MARKETING_VERSION(市场版本号,如1.0.0)和CURRENT_PROJECT_VERSION(构建号,如42)组成。构建号必须在每次上传时递增。在CI中,可以通过脚本自动计算下一个构建号——取App Store Connect最新构建号和GitHub Release构建号的最大值加1。
下面是 iOS 发布流程:
flowchart LR
A["构建前置条件"] --> B["Xcode Product → Archive"]
B --> C["Organizer Validate App"]
C --> D{"验证通过?"}
D -->|"否"| B
D -->|"是"| E["Distribute App"]
E --> F["Fastlane pilot 上传"]
F --> G["TestFlight / App Store"]
H["版本号管理"] --> I["MARKETING_VERSION"]
H --> J["CURRENT_PROJECT_VERSION 递增"]
四、Desktop:jpackage原生安装包
4.1 Compose Multiplatform Gradle插件的nativeDistributions配置
Compose Multiplatform Gradle插件基于jpackage和jlink生成自包含的原生安装包,无需目标系统安装JDK。打包需要JDK 17或更高版本。
compose.desktop {
application {
mainClass = "com.example.app.MainKt"
nativeDistributions {
targetFormats(
TargetFormat.Dmg, // macOS
TargetFormat.Msi, // Windows
TargetFormat.Deb // Linux
)
packageName = "com.example.app"
packageVersion = "1.0.0"
}
}
}
支持的安装包格式:
| 平台 | 格式 | TargetFormat |
|---|---|---|
| macOS | .dmg / .pkg | TargetFormat.Dmg / TargetFormat.Pkg |
| Windows | .msi / .exe | TargetFormat.Msi / TargetFormat.Exe |
| Linux | .deb / .rpm | TargetFormat.Deb / TargetFormat.Rpm |
4.2 构建命令
# 构建当前操作系统的原生安装包
./gradlew :desktopApp:packageDistributionForCurrentOS
# 构建指定格式
./gradlew :desktopApp:packageDmg
./gradlew :desktopApp:packageMsi
./gradlew :desktopApp:packageDeb
关键约束:jpackage不支持交叉编译。必须在macOS上构建.dmg,在Windows上构建.msi,在Linux上构建.deb。在CI中,需要为每个平台配置对应的runner。
JDK版本一致性:在Mac和Windows上必须使用相同的JDK版本(推荐JDK 17)。如果开发阶段Mac使用JDK 17而Windows使用不同版本,可能在release阶段出现编译错误。
4.3 jDeploy:免jpackage的替代方案
jDeploy是一个第三方工具,可以将Kotlin Compose Multiplatform桌面应用打包为原生安装包,并自动发布到GitHub Releases。jDeploy的优势在于简化了配置流程和自动更新支持。使用jDeploy Desktop App或CLI,可以自动完成Shadow插件配置、跨平台依赖设置和GitHub Actions工作流生成。
4.4 macOS签名与公证
macOS的.dmg分发需要进行代码签名和公证(Notarization),否则用户打开时会看到安全警告:
macOS {
signing {
sign.set(true)
identity.set("Developer ID Application: Your Name (TEAM_ID)")
}
notarization {
appleID.set(System.getenv("APPLE_ID"))
password.set(System.getenv("APPLE_APP_PASSWORD"))
teamID.set(System.getenv("APPLE_TEAM_ID"))
}
}
下面是 Desktop 打包流程:
flowchart TD
A["Compose Desktop 配置"] --> B["nativeDistributions"]
B --> C{"目标平台"}
C -->|"macOS"| D["构建 .dmg / .pkg"]
C -->|"Windows"| E["构建 .msi / .exe"]
C -->|"Linux"| F["构建 .deb / .rpm"]
D --> G["macOS 签名与公证"]
G --> H["GitHub Releases 分发"]
E --> H
F --> H
I["jDeploy 替代方案"] --> J["自动生成 GitHub Actions 工作流"]
J --> H
五、Web(Wasm):静态托管部署
5.1 构建Wasm产物
Web端CMP应用通过wasmJsBrowserDistribution任务构建:
./gradlew :webApp:wasmJsBrowserDistribution
构建产物位于webApp/build/dist/wasmJs/productionExecutable/,包含index.html、.js和.wasm文件。将该目录的内容复制到任意静态托管服务即可。
5.2 部署到静态托管
SPA重写规则是必需的配置。CMP的Web应用使用前端路由,服务器需要将所有路径重写到index.html。官方文档明确要求“服务器必须为任何不匹配文件的路径提供index.html”。
在Vercel中,通过vercel.json配置:
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
],
"headers": [
{
"source": "/**.wasm",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
}
]
}
MIME类型配置:确保服务器正确识别.wasm文件的MIME类型为application/wasm。Apache服务器需要在.htaccess中添加AddType application/wasm .wasm。如果服务器未正确配置,页面会白屏。
缓存策略:.wasm文件的文件名包含内容哈希,可以长期缓存(immutable)。webApp.js文件名固定,需要每次检查更新。
5.3 部署到GitHub Pages
# .github/workflows/deploy-web.yml
- name: Build Wasm
run: ./gradlew :webApp:wasmJsBrowserDistribution
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: webApp/build/dist/wasmJs/productionExecutable
5.4 部署到Cloudflare Pages
已知陷阱:tag部署需要指定branch。从git tag部署时,Cloudflare不知道对应的分支,部署会到preview URL而非production。必须在Wrangler Action中指定branch: main:
- name: Deploy to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy ./webApp/build/dist/wasmJs/productionExecutable --project-name=${{ github.event.repository.name }} --branch=main
下面是 Web 部署流程:
flowchart LR
A["wasmJsBrowserDistribution 构建"] --> B["产物目录"]
B --> C{"选择托管平台"}
C -->|"Vercel"| D["vercel.json 配置"]
C -->|"GitHub Pages"| E["peaceiris/actions-gh-pages"]
C -->|"Cloudflare Pages"| F["wrangler-action"]
D --> G["SPA 重写规则"]
E --> G
F --> G
G --> H["MIME 类型配置"]
H --> I["缓存策略"]
I --> J["上线"]
六、CI/CD流水线设计
6.1 三种主流方案对比
| 方案 | 优势 | 适用场景 |
|---|---|---|
| GitHub Actions | 灵活、贴近仓库、成本效益高 | 代码托管在GitHub的团队 |
| Bitrise | 移动发布管理成熟、签名和商店交付开箱即用 | 需要简化移动发布流程的团队 |
| Fastlane + 自建Runner | 完全控制、可定制 | 需要深度定制发布流程的团队 |
对于大多数CMP项目,GitHub Actions是首选——它灵活、贴近仓库,且对许多团队而言成本效益更高。
6.2 三阶段发布流程
生产级CMP项目的CI/CD采用三阶段发布流程:
| 阶段 | 触发方式 | 执行内容 |
|---|---|---|
| Cut(切分支) | 手动触发 | 从main创建prod/{version}分支,main的版本号递增 |
| Deploy RC | 推送到prod/*自动触发 | 打RC标签,上传AAB到Play Internal Track,上传IPA到TestFlight |
| Tag Final | QA通过后手动触发 | 在prod分支打最终v{version}标签,创建GitHub Release |
6.3 完整的GitHub Actions工作流
JetBrains官方推荐的KMP CI配置建议创建复合Action来统一Java和Gradle设置,主工作流在每次push或PR到main分支时运行共享测试(jvmTest)并触发各平台构建。
# .github/actions/gradle-setup/action.yml
name: gradle-setup
description: Setup Java and Gradle
runs:
using: "composite"
steps:
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
# .github/workflows/build.yml
name: Build & Release
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4096M -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.caching=true"
jobs:
# Job 1: 共享测试
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Gradle setup
uses: ./.github/actions/gradle-setup
- name: Run unit tests
run: ./gradlew jvmTest
- name: Upload test reports
uses: actions/upload-artifact@v4
with:
name: test-reports
path: "**/build/reports/tests/"
# Job 2: 代码质量
code-quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Detekt
run: ./gradlew detekt
# Job 3: Android构建与签名
build-android:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Decode Keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.jks
- name: Build Release AAB
env:
KEYSTORE_PATH: keystore.jks
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEYSTORE_ALIAS: ${{ secrets.KEYSTORE_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: ./gradlew :androidApp:bundleRelease
- name: Upload AAB
uses: actions/upload-artifact@v4
with:
name: android-aab
path: androidApp/build/outputs/bundle/release/*.aab
# Job 4: iOS构建
build-ios:
needs: test
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Gradle setup
uses: ./.github/actions/gradle-setup
- name: Build iOS Framework
run: ./gradlew :shared:linkReleaseFrameworkIosArm64
- name: Archive iOS App
run: |
xcodebuild archive \
-project iosApp/iosApp.xcodeproj \
-scheme iosApp \
-archivePath build/iosApp.xcarchive \
-destination "generic/platform=iOS"
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath build/iosApp.xcarchive \
-exportOptionsPlist iosApp/ExportOptions.plist \
-exportPath build/ios
- name: Upload to TestFlight
run: fastlane ios upload_testflight
# Job 5: Web构建与部署
build-web:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Wasm
run: ./gradlew :webApp:wasmJsBrowserDistribution
- name: Deploy to Static Host
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: webApp/build/dist/wasmJs/productionExecutable
6.4 多平台CI矩阵
| Job | Runner | 执行内容 |
|---|---|---|
test-shared | ubuntu-latest | commonTest + jvmTest(单元测试 + Compose UI测试) |
test-android | ubuntu-latest | androidHostTest(Roborazzi截图测试) |
test-ios | macos-latest | iosSimulatorArm64Test + Xcode UI Tests |
build-android | ubuntu-latest | androidApp:bundleRelease |
build-ios | macos-latest | Xcode archive + TestFlight |
build-web | ubuntu-latest | wasmJsBrowserDistribution + 静态托管部署 |
6.5 版本管理策略
CMP项目涉及多个平台的版本号,需要统一管理:
| 平台 | 版本号位置 | 说明 |
|---|---|---|
| Android | build.gradle.kts的versionName / versionCode | versionCode必须递增 |
| iOS | Config.xcconfig的MARKETING_VERSION / CURRENT_PROJECT_VERSION | 构建号必须递增 |
| Desktop | nativeDistributions.packageVersion | 语义化版本号 |
| Web | 无强制版本要求 | 可通过构建号标记 |
推荐通过scripts/ios_next_build_number.py等脚本统一计算和同步各平台的版本号。
6.6 签名与密钥管理
CI/CD中的签名密钥通过GitHub Secrets管理。Android签名需要以下Secrets:
| Secret名称 | 说明 |
|---|---|
KEYSTORE_BASE64 | Base64编码的keystore文件 |
KEYSTORE_PASSWORD | keystore密码 |
KEYSTORE_ALIAS | keystore别名 |
KEY_PASSWORD | 密钥密码 |
PLAYSTORE_CREDS | Google Play服务账号JSON |
iOS签名需要:
| Secret名称 | 说明 |
|---|---|
ASC_API_KEY | App Store Connect API Key(JSON) |
APPLE_ID | Apple ID(用于公证) |
APPLE_APP_PASSWORD | App专用密码 |
安全实践:keystore文件在CI中通过Base64解码临时生成,构建完成后删除;所有密码通过环境变量注入,绝不硬编码在构建脚本中。
Detekt的KMP配置陷阱:./gradlew detekt可能静默跳过KMP代码,返回成功但不检查任何内容。必须配置源集特定的任务:detektMetadataCommonMain、detektAndroidMain、detektIosArm64Main、detektDesktopMain、detektWasmJsMain。其中detektMetadataCommonMain检查的是共享代码,缺少它会导致CI绿灯但实际检查了零行代码。
配套习题(15题)
第一组:发布全景与Android部署(4题)
第1题 桌面端CMP应用发布的核心约束是( )
A. 只能在Linux上构建所有平台安装包 B. 不支持交叉编译,各平台需在对应操作系统上构建 C. 只能发布到GitHub Releases D. 需要目标系统安装JDK
答案:B
解析:桌面端不支持交叉编译——只能在macOS上构建.dmg,在Windows上构建.msi,在Linux上构建.deb。发布全平台桌面应用需要三种操作系统(或CI环境)。
第2题 Google Play要求上传的Android产物格式是( )
A. APK B. AAB C. IPA D. JAR
答案:B
解析:Google Play要求上传AAB(Android App Bundle),而非APK。AAB包含所有编译代码和资源,Play Store会根据设备配置生成优化的APK,减小下载体积。
第3题 Android签名密钥文件的正确管理方式是( )
A. 提交到版本控制,方便团队共享 B. 加入.gitignore,密码通过环境变量注入 C. 放在项目根目录并硬编码密码 D. 通过邮件发送给团队成员
答案:B
解析:签名密钥文件(.keystore/.jks)绝不提交到版本控制,应加入.gitignore。密码通过环境变量注入,在CI中通过GitHub Secrets管理。
第4题 用于本地验证AAB正确性的工具是( )
A. jpackage B. bundletool C. keytool D. fastlane
答案:B
解析:使用bundletool可以从AAB生成APK集合,并安装到连接的设备进行本地验证。keytool用于生成签名密钥,jpackage用于桌面端打包,fastlane用于自动化发布。
第二组:iOS部署(4题)
第5题 iOS应用构建的硬性限制是( )
A. 只能在Linux上构建 B. 必须在macOS上进行构建 C. 只能在Windows上构建 D. 不需要特定操作系统
答案:B
解析:iOS构建必须在macOS上进行,这是苹果生态的硬性限制。CMP代码被编译为Native二进制,需要Xcode进行签名和归档。
第6题 iOS发布流程中,归档后验证应用的正确步骤是( )
A. 直接上传到App Store B. 在Organizer中点击Validate App验证 C. 在模拟器中运行 D. 使用bundletool验证
答案:B
解析:在Xcode的Organizer中,归档后需要先点击Validate App验证归档,验证通过后再点击Distribute App上传到App Store Connect。
第7题 macOS App Store对KMP应用的特殊约束是( )
A. 无需任何额外处理 B. 每个二进制文件(包括JVM运行时和.dylib)都需要签名和Provisioning Profile C. 只需签名应用主体 D. 可以使用未签名的二进制文件
答案:B
解析:Apple要求每个二进制文件都被签名——包括应用主体、JVM运行时、Kotlin原生库、每个.dylib。每个都需要Provisioning Profile,一个未签名的文件就会导致拒绝。推荐跳过Mac App Store,通过签名并公证的DMG在GitHub Releases分发。
第8题 当KMP项目中GENERATE_INFOPLIST_FILE = true时,Fastlane版本注入失败的解决方案是( )
A. 手动修改Info.plist
B. 设置GENERATE_INFOPLIST_FILE = false或通过xcargs注入版本属性
C. 使用bundletool注入
D. 不需要处理
答案:B
解析:当GENERATE_INFOPLIST_FILE = true时,Xcode会动态生成Info.plist,导致Fastlane的版本注入操作无效。解决方案是将GENERATE_INFOPLIST_FILE设为false,或直接在Fastfile中通过xcargs注入MARKETING_VERSION和CURRENT_PROJECT_VERSION。
第三组:Desktop与Web部署(4题)
第9题 Compose Multiplatform Gradle插件生成桌面原生安装包基于什么工具?( )
A. Electron B. jpackage和jlink C. Flutter D. Docker
答案:B
解析:Compose Multiplatform Gradle插件基于jpackage和jlink生成自包含的原生安装包,无需目标系统安装JDK。打包需要JDK 17或更高版本。
第10题 macOS的.dmg分发需要进行的额外处理是( )
A. 无需额外处理 B. 代码签名和公证(Notarization) C. 转换为APK D. 上传到Google Play
答案:B
解析:macOS的.dmg分发需要进行代码签名和公证(Notarization),否则用户打开时会看到安全警告。
第11题 Web端CMP应用的构建任务是( )
A. jsBrowserDistribution B. wasmJsBrowserDistribution C. browserDistribute D. webBuild
答案:B
解析:Web端CMP应用通过wasmJsBrowserDistribution任务构建,产物位于build/dist/wasmJs/productionExecutable/。
第12题 从git tag部署到Cloudflare Pages时的已知陷阱是( )
A. 不支持tag部署
B. 需要指定branch: main,否则部署会到preview URL而非production
C. 需要额外安装插件
D. 只支持APK格式
答案:B
解析:从git tag部署时,Cloudflare不知道对应的分支,部署会到preview URL而非production。必须在Wrangler Action中指定branch: main。
第四组:CI/CD与版本管理(3题)
第13题 大多数CMP项目首选的CI/CD方案是( )
A. Bitrise B. GitHub Actions C. Jenkins D. Travis CI
答案:B
解析:对于大多数CMP项目,GitHub Actions是首选——它灵活、贴近仓库,且对许多团队而言成本效益更高。Bitrise适用于需要简化移动发布流程的团队。
第14题 关于Detekt在KMP项目中的行为,以下说法正确的是( )
A. ./gradlew detekt会自动检查所有KMP源集
B. ./gradlew detekt可能静默跳过KMP代码,需要配置源集特定任务
C. Detekt不支持KMP项目
D. 只需运行detektMetadataCommonMain即可
答案:B
解析:./gradlew detekt可能静默跳过KMP代码,返回成功但不检查任何内容。必须配置源集特定的任务:detektMetadataCommonMain、detektAndroidMain、detektIosArm64Main、detektDesktopMain、detektWasmJsMain。其中detektMetadataCommonMain检查共享代码。
第15题 CI环境中Gradle构建优化的关键配置不包括( )
A. 禁用Gradle daemon B. 启用并行执行 C. 启用构建缓存 D. 启用Gradle daemon以提高性能
答案:D
解析:在CI环境中,应禁用Gradle daemon(-Dorg.gradle.daemon=false),因为每个构建在独立的runner上执行,daemon无法复用。同时启用并行执行和构建缓存以提高效率。JetBrains官方推荐的配置为:GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4096M -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.caching=true"。
本课知识点总结
1. 四端分发的差异与约束
CMP应用发布到四个平台,各平台分发机制不同。Android通过APK/AAB分发,使用keystore签名。iOS通过IPA分发到App Store,必须在macOS上构建,需要Apple Developer账号、Xcode签名和公证。Desktop通过原生安装包分发,不支持交叉编译,各平台需在对应操作系统上构建。Web通过静态文件托管分发,需配置SPA重写规则和.wasm MIME类型。桌面端打包需要JDK 17+,iOS已在CMP 1.8.0达到Stable,Web目标为Beta。
2. Android签名与发布
使用keytool生成签名密钥,在build.gradle.kts中通过环境变量配置签名信息。签名密钥文件绝不提交版本控制。构建AAB使用./gradlew :androidApp:bundleRelease,使用bundletool本地验证。上传到Google Play通过Play Console或Fastlane的supply lane自动化。AAB是Google Play要求的唯一格式,Play Store根据设备生成优化APK。
3. iOS归档与TestFlight发布
iOS发布通过Xcode的Product → Archive → Validate App → Distribute App完成。Fastlane的pilot lane使用App Store Connect API Key自动化TestFlight上传。已知陷阱:GENERATE_INFOPLIST_FILE = true时Fastlane版本注入静默失败,需通过xcargs注入版本。macOS App Store要求每个二进制文件签名,推荐通过DMG+公证分发。版本号由MARKETING_VERSION和CURRENT_PROJECT_VERSION组成,构建号每次上传必须递增。
4. Desktop原生安装包打包
Compose Multiplatform Gradle插件基于jpackage和jlink生成自包含安装包。支持的格式:macOS的.dmg/.pkg,Windows的.msi/.exe,Linux的.deb/.rpm。jpackage不支持交叉编译,打包需要JDK 17+。JDK版本必须跨平台统一。macOS的.dmg需要代码签名和公证。jDeploy作为替代方案,可自动生成GitHub Actions工作流。
5. Web(Wasm)静态托管部署
通过wasmJsBrowserDistribution任务构建,产物位于build/dist/wasmJs/productionExecutable/。部署关键:SPA重写规则(所有路径重写到index.html)、.wasm文件的MIME类型配置为application/wasm、.wasm文件长期缓存、webApp.js每次检查更新。Cloudflare Pages从tag部署时需指定branch: main。
6. CI/CD流水线与版本管理
三阶段发布流程:Cut(手动创建prod分支)→ Deploy RC(推送到prod/*自动触发,打RC标签并上传)→ Tag Final(QA通过后手动打最终标签)。JetBrains官方推荐创建复合Action统一Java和Gradle设置。大多数CMP项目首选GitHub Actions。签名密钥通过GitHub Secrets管理,keystore文件通过Base64编码传输。CI环境中禁用Gradle daemon并启用并行执行和缓存。Detekt在KMP项目中需配置源集特定任务,否则静默跳过共享代码。多平台版本号通过脚本统一计算和同步。
下一课预告:第20课将进入综合实践模块,深度解析CMP跨平台经典示例——包括JetBrains官方示例(Imageviewer、Chat、Nav Cupcake等)的架构决策,社区标杆项目(kmp-clean-architecture、MVI架构示例)的设计模式,支付宝MYKMP的工业实践,以及从示例中提炼CMP工程决策方法论。