第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发布](#二、Android:签名配置与Google Play发布 "#%E4%BA%8Candroid%E7%AD%BE%E5%90%8D%E9%85%8D%E7%BD%AE%E4%B8%8Egoogle-play%E5%8F%91%E5%B8%83")
- [2.1 生成签名密钥](#2.1 生成签名密钥 "#21-%E7%94%9F%E6%88%90%E7%AD%BE%E5%90%8D%E5%AF%86%E9%92%A5")
- [2.2 配置签名](#2.2 配置签名 "#22-%E9%85%8D%E7%BD%AE%E7%AD%BE%E5%90%8D")
- [2.3 构建AAB](#2.3 构建AAB "#23-%E6%9E%84%E5%BB%BAaab")
- [2.4 本地验证AAB](#2.4 本地验证AAB "#24-%E6%9C%AC%E5%9C%B0%E9%AA%8C%E8%AF%81aab")
- [2.5 使用Fastlane自动化Google Play发布](#2.5 使用Fastlane自动化Google Play发布 "#25-%E4%BD%BF%E7%94%A8fastlane%E8%87%AA%E5%8A%A8%E5%8C%96google-play%E5%8F%91%E5%B8%83")
- 三、iOS:Xcode归档与TestFlight发布
- [3.1 构建前置条件](#3.1 构建前置条件 "#31-%E6%9E%84%E5%BB%BA%E5%89%8D%E7%BD%AE%E6%9D%A1%E4%BB%B6")
- [3.2 在Xcode中归档](#3.2 在Xcode中归档 "#32-%E5%9C%A8xcode%E4%B8%AD%E5%BD%92%E6%A1%A3")
- [3.3 使用Fastlane自动化iOS发布](#3.3 使用Fastlane自动化iOS发布 "#33-%E4%BD%BF%E7%94%A8fastlane%E8%87%AA%E5%8A%A8%E5%8C%96ios%E5%8F%91%E5%B8%83")
- [3.4 版本号管理](#3.4 版本号管理 "#34-%E7%89%88%E6%9C%AC%E5%8F%B7%E7%AE%A1%E7%90%86")
- 四、Desktop:jpackage原生安装包
- [4.1 Compose Multiplatform Gradle插件的nativeDistributions配置](#4.1 Compose Multiplatform Gradle插件的nativeDistributions配置 "#41-compose-multiplatform-gradle%E6%8F%92%E4%BB%B6%E7%9A%84nativedistributions%E9%85%8D%E7%BD%AE")
- [4.2 构建命令](#4.2 构建命令 "#42-%E6%9E%84%E5%BB%BA%E5%91%BD%E4%BB%A4")
- [4.3 jDeploy:免jpackage的替代方案](#4.3 jDeploy:免jpackage的替代方案 "#43-jdeploy%E5%85%8Djpackage%E7%9A%84%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88")
- [4.4 macOS签名与公证](#4.4 macOS签名与公证 "#44-macos%E7%AD%BE%E5%90%8D%E4%B8%8E%E5%85%AC%E8%AF%81")
- 五、Web(Wasm):静态托管部署
- [5.1 构建Wasm产物](#5.1 构建Wasm产物 "#51-%E6%9E%84%E5%BB%BAwasm%E4%BA%A7%E7%89%A9")
- [5.2 部署到静态托管](#5.2 部署到静态托管 "#52-%E9%83%A8%E7%BD%B2%E5%88%B0%E9%9D%99%E6%80%81%E6%89%98%E7%AE%A1")
- [5.3 部署到GitHub Pages](#5.3 部署到GitHub Pages "#53-%E9%83%A8%E7%BD%B2%E5%88%B0github-pages")
- [5.4 部署到Cloudflare Pages](#5.4 部署到Cloudflare Pages "#54-%E9%83%A8%E7%BD%B2%E5%88%B0cloudflare-pages")
- 六、CI/CD流水线设计
- [6.1 三种主流方案对比](#6.1 三种主流方案对比 "#61-%E4%B8%89%E7%A7%8D%E4%B8%BB%E6%B5%81%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94")
- [6.2 三阶段发布流程](#6.2 三阶段发布流程 "#62-%E4%B8%89%E9%98%B6%E6%AE%B5%E5%8F%91%E5%B8%83%E6%B5%81%E7%A8%8B")
- [6.3 完整的GitHub Actions工作流](#6.3 完整的GitHub Actions工作流 "#63-%E5%AE%8C%E6%95%B4%E7%9A%84github-actions%E5%B7%A5%E4%BD%9C%E6%B5%81")
- [6.4 多平台CI矩阵](#6.4 多平台CI矩阵 "#64-%E5%A4%9A%E5%B9%B3%E5%8F%B0ci%E7%9F%A9%E9%98%B5")
- [6.5 版本管理策略](#6.5 版本管理策略 "#65-%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86%E7%AD%96%E7%95%A5")
- [6.6 签名与密钥管理](#6.6 签名与密钥管理 "#66-%E7%AD%BE%E5%90%8D%E4%B8%8E%E5%AF%86%E9%92%A5%E7%AE%A1%E7%90%86")
- 配套习题(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作为试点。
下面是四端发布全景图:
二、Android:签名配置与Google Play发布
2.1 生成签名密钥
Android应用必须用密钥签名才能安装到设备或上传到商店。使用keytool生成:
bash
keytool -genkey -v -keystore my-release-key.keystore \
-alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
2.2 配置签名
在androidApp/build.gradle.kts中配置签名,通过环境变量读取敏感信息:
kotlin
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,减小下载体积。
bash
# 构建Release AAB
./gradlew :androidApp:bundleRelease
# 输出位置:androidApp/build/outputs/bundle/release/androidApp-release.aab
Google Play支持最大4GB的累计下载大小限制。如果应用启用了代码混淆,最佳实践是在发布时上传解混淆文件(mapping文件)。
2.4 本地验证AAB
上传前可使用bundletool在本地验证AAB的正确性:
bash
# 从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密钥:
ruby
# 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 发布流程:
三、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进行认证:
ruby
# 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注入版本属性:
ruby
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 发布流程:
四、Desktop:jpackage原生安装包
4.1 Compose Multiplatform Gradle插件的nativeDistributions配置
Compose Multiplatform Gradle插件基于jpackage和jlink生成自包含的原生安装包,无需目标系统安装JDK。打包需要JDK 17或更高版本。
kotlin
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 构建命令
bash
# 构建当前操作系统的原生安装包
./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),否则用户打开时会看到安全警告:
kotlin
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 打包流程:
五、Web(Wasm):静态托管部署
5.1 构建Wasm产物
Web端CMP应用通过wasmJsBrowserDistribution任务构建:
bash
./gradlew :webApp:wasmJsBrowserDistribution
构建产物位于webApp/build/dist/wasmJs/productionExecutable/,包含index.html、.js和.wasm文件。将该目录的内容复制到任意静态托管服务即可。
5.2 部署到静态托管
SPA重写规则 是必需的配置。CMP的Web应用使用前端路由,服务器需要将所有路径重写到index.html。官方文档明确要求"服务器必须为任何不匹配文件的路径提供index.html"。
在Vercel中,通过vercel.json配置:
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
yaml
# .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:
yaml
- 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 部署流程:
六、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)并触发各平台构建。
yaml
# .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
yaml
# .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工程决策方法论。