本课目标:掌握 Android、iOS、桌面、Web 四端的打包与发布流程,理解各平台分发的核心约束(iOS 签名、桌面端无法交叉编译、Web 静态托管),建立 CI/CD 自动化的基础认知,完成从开发到交付的最后一公里。

系列整体规划

课次主题核心内容难度
第1课从零开始技术概览、环境搭建、第一个应用、代码解读⭐
第2课Compose 基础语法@Composable、状态管理、重组机制、Modifier 体系⭐⭐
第3课布局与组件Column/Row/Box、LazyColumn、Material3 组件库⭐⭐
第4课导航与路由Navigation Compose、类型安全路由、深层链接⭐⭐⭐
第5课网络与数据层Ktor 客户端、序列化、Repository 模式⭐⭐⭐
第6课状态管理与架构ViewModel、单向数据流、依赖注入⭐⭐⭐⭐
第7课平台适配与互操作expect/actual、SwiftUI 互操作、平台特定 API⭐⭐⭐⭐
第8课资源管理与主题多平台资源、图片加载、深浅色主题⭐⭐⭐
第9课测试与调试Compose UI 测试、单元测试、性能分析⭐⭐⭐⭐
第10课发布与部署Android/iOS/桌面/Web 打包发布、CI/CD⭐⭐⭐⭐⭐

第10课 发布与部署

一、发布全景:四端分发的差异

CMP 应用发布到四个平台,每个平台的分发机制、约束条件和产物格式都不同。理解这些差异,是规划发布流程的前提。

Android 通过 APK 或 AAB 分发到 Google Play 或国内应用商店。签名用 keystore 文件,编译产物是标准 Android 格式,CMP 代码被编译为 JVM 字节码,与原生 Android 应用无区别。

iOS 通过 IPA 分发到 App Store。需要 Apple Developer 账号、Xcode 签名和公证(notarization)。编译 iOS 必须在 macOS 上进行,这是苹果生态的硬性限制。CMP 代码被编译为 Native 二进制。

桌面端 通过安装包分发(Windows 的 .msi/.exe,macOS 的 .dmg/.pkg,Linux 的 .deb/.rpm)。Compose Multiplatform Gradle 插件基于 jpackage 和 jlink 生成自包含的安装包,无需目标系统安装 JDK。

Web 通过静态文件托管分发。wasmJsBrowserDistribution 任务生成 HTML、JS、Wasm 文件,部署到任意静态托管服务(GitHub Pages、Vercel、Netlify)。

一个关键的跨平台约束:桌面端不支持交叉编译。你只能在 macOS 上构建 .dmg,在 Windows 上构建 .msi,在 Linux 上构建 .deb。这意味着发布全平台桌面应用需要三种操作系统(或 CI 环境)。

二、Android 打包与发布

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("my-release-key.keystore")
            storePassword = System.getenv("KEYSTORE_PASSWORD")
            keyAlias = "my-key-alias"
            keyPassword = System.getenv("KEY_PASSWORD")
        }
    }
    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
            isMinifyEnabled = true
        }
    }
}

最佳实践:密码通过环境变量传入,不写入版本控制。keystore 文件也应加入 .gitignore。

2.3 生成发布产物

# 生成 AAB(推荐用于 Google Play)
./gradlew :androidApp:bundleRelease

# 生成 APK(用于直接分发)
./gradlew :androidApp:assembleRelease

AAB 是 Google Play 的推荐格式,它让商店按设备配置生成最优的 APK,减小下载体积。

三、iOS 打包与发布

3.1 Xcode 项目配置

用 Xcode 打开 iosApp/iosApp.xcodeproj,在 Signing & Capabilities 中配置:

  • Bundle Identifier:唯一标识,上传后不可更改
  • Team:选择你的 Apple Developer 账号
  • Version 和 Build:遵循 [Major].[Minor].[Patch] 格式

3.2 创建 App Store Connect 记录

在 App Store Connect 中创建应用记录,Bundle ID 必须与 Xcode 项目中的完全一致。首次上传后 Bundle ID 无法更改,务必仔细核对。

3.3 归档与上传

在 Xcode 中选择 Product → Archive,完成后通过 Organizer 上传到 App Store Connect。上传前确认:

  • 应用图标已配置
  • 启动屏幕已配置
  • 加密合规声明已填写(如果使用了加密功能)

CMP 特有注意事项:iOS 14.0 是当前最低支持版本。CMP 1.11.0 起,并发渲染默认启用,渲染任务被卸载到专用线程,无需额外配置。如果使用原生文本输入(实验性),需要在代码中显式启用。

四、桌面端打包

4.1 Gradle 配置

在 build.gradle.kts 中配置桌面分发:

compose.desktop {
    application {
        mainClass = "MainKt"
        nativeDistributions {
            targetFormats(
                TargetFormat.Dmg,
                TargetFormat.Msi,
                TargetFormat.Deb,
            )
            packageName = "com.example.app"
            packageVersion = "1.0.0"
        }
    }
}

4.2 打包任务

# macOS
./gradlew packageDmg

# Windows
./gradlew packageMsi

# Linux
./gradlew packageDeb

产物位于 composeApp/build/compose/binaries/ 下。必须在对应操作系统上运行对应任务——交叉编译不受支持。

4.3 替代方案:jDeploy

jDeploy 是一个支持 KMP 的第三方工具,可以在任意操作系统上构建所有平台的安装包,并支持自动更新。它的工作方式是生成一个轻量的启动器,运行时下载 JVM 和应用 JAR。对于需要简化发布流程的项目,这是一个值得考虑的选项。

五、Web 端打包与部署

5.1 生成生产产物

./gradlew wasmJsBrowserDistribution

产物位于 webApp/build/dist/wasmJs/productionExecutable/ 下,包含 index.html、webApp.js 和 .wasm 文件。

5.2 部署到静态托管

Web 产物是纯静态文件,可以部署到任何静态托管服务:

GitHub Pages:将产物推送到 gh-pages 分支,或在 GitHub Actions 中自动部署。

Vercel / Netlify:连接 Git 仓库,自动构建和部署。

关键配置:SPA 路由回退。如果应用使用了导航(第4课),需要配置服务器将所有未匹配的路径返回 index.html。以 Vercel 为例,在 webApp/vercel.json 中配置:

{
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}

缓存策略:.wasm 文件名包含内容哈希,可以设置长期缓存。webApp.js 文件名固定,需要设置 no-cache 或短缓存。

六、CI/CD 自动化

6.1 GitHub Actions 策略

由于 iOS 和桌面端的平台约束,完整的 CI/CD 需要矩阵策略:

任务Runner产物
Android 构建ubuntu-latestAAB/APK
iOS 构建macos-latestIPA
Desktop macOSmacos-latestDMG
Desktop Windowswindows-latestMSI
Desktop Linuxubuntu-latestDEB
Web 构建ubuntu-latest静态文件

iOS 构建的关键配置:在 macOS runner 上安装 Xcode,配置签名证书(通过 GitHub Secrets),运行 xcodebuild archive。

6.2 简化的 CI 流程

如果不需要全平台发布,可以只自动化核心构建:

name: Build
on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: Build Android
        run: ./gradlew :androidApp:assembleRelease
      - name: Build Web
        run: ./gradlew wasmJsBrowserDistribution

一个实用建议:先在本地手动完成一次完整发布,记录每个平台的步骤和坑,再把流程翻译成 CI 配置。直接写 CI 而不了解本地流程,调试成本会高得多。

七、习题与参考答案

本课习题分为三类:概念理解(1-4 题)、代码实践(5-9 题)、综合设计(10-12 题)。

概念理解

习题 1:四端分发的核心差异

题目:Android、iOS、桌面、Web 四个平台的发布方式有什么本质不同?

参考答案:Android 用 keystore 签名,产物为 APK/AAB。iOS 需要 Apple Developer 账号和 Xcode 签名,必须在 macOS 上构建,产物为 IPA。桌面端基于 jpackage/jlink 生成自包含安装包,不支持交叉编译,必须在对应操作系统上构建。Web 产物是静态文件,部署到任意静态托管。

习题 2:为什么桌面端不支持交叉编译

题目:为什么不能在 macOS 上构建 Windows 的 .msi 安装包?

参考答案:jpackage 工具依赖目标操作系统的原生打包工具(如 Windows 的 WiX Toolset、Linux 的 dpkg/rpm)。这些工具只在对应操作系统上可用,因此构建必须在目标平台上进行。

习题 3:iOS Bundle ID 的重要性

题目:为什么 iOS 的 Bundle ID 在首次上传后不可更改?

参考答案:Bundle ID 是应用在 Apple 生态系统中的唯一标识,与 App Store Connect 记录、推送证书、应用内购买、iCloud 容器等绑定。更改会导致这些服务全部失效。

习题 4:Web 端的路由回退

题目:为什么部署 CMP Web 应用时通常需要配置路由回退到 index.html?

参考答案:CMP 的导航库(第4课)使用浏览器 History API 管理路由。当用户直接访问 example.com/profile 或刷新页面时,服务器会尝试查找 /profile 文件,但实际文件不存在。路由回退规则将所有未匹配请求返回 index.html,由前端路由处理。

代码实践

习题 5:配置 Android 签名

题目:在 androidApp/build.gradle.kts 中配置 release 签名,密码从环境变量读取。

参考答案:

android {
    signingConfigs {
        create("release") {
            storeFile = file("my-release-key.keystore")
            storePassword = System.getenv("KEYSTORE_PASSWORD")
            keyAlias = "my-key-alias"
            keyPassword = System.getenv("KEY_PASSWORD")
        }
    }
    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
            isMinifyEnabled = true
        }
    }
}
习题 6:配置桌面打包

题目:配置 build.gradle.kts,让桌面端支持 macOS DMG 和 Windows MSI 打包。

参考答案:

compose.desktop {
    application {
        mainClass = "MainKt"
        nativeDistributions {
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi)
            packageName = "com.example.myapp"
            packageVersion = "1.0.0"
        }
    }
}
习题 7:生成 Web 产物

题目:运行生成 Web 生产产物的 Gradle 任务,并说明产物位置。

参考答案:

./gradlew wasmJsBrowserDistribution

产物位于 webApp/build/dist/wasmJs/productionExecutable/。

习题 8:配置 Vercel 路由回退

题目:为 CMP Web 应用配置 Vercel,确保所有路由返回 index.html。

参考答案:

{
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}
习题 9:GitHub Actions 构建 Web 产物

题目:编写一个 GitHub Actions 工作流,在 push 时自动构建 Web 产物。

参考答案:

name: Build Web
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
      - run: ./gradlew wasmJsBrowserDistribution
      - uses: actions/upload-artifact@v4
        with:
          name: web-dist
          path: composeApp/build/dist/wasmJs/productionExecutable/

综合设计

习题 10:Android 发布检查清单

题目:列出 Android 应用发布到 Google Play 前必须完成的检查项。

参考答案:

  1. Release keystore 已生成并安全保存(不在版本控制中)
  2. build.gradle.kts 中签名配置使用环境变量
  3. 应用图标和启动屏幕已配置
  4. versionCode 和 versionName 已递增
  5. isMinifyEnabled = true 已启用(代码混淆和压缩)
  6. 隐私政策链接已准备(如果收集用户数据)
  7. 内容分级问卷已填写
习题 11:完整的 CI/CD 矩阵

题目:设计一个 GitHub Actions 工作流,同时构建 Android、iOS、Web 三个平台的产物。

参考答案:

name: Multi-Platform Build
on:
  push:
    tags: ['v*']

jobs:
  android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: 'temurin', java-version: '17' }
      - run: ./gradlew :androidApp:bundleRelease
      - uses: actions/upload-artifact@v4
        with: { name: android-aab, path: androidApp/build/outputs/bundle/release/ }

  ios:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: 'temurin', java-version: '17' }
      - name: Build iOS
        run: |
          cd iosApp
          xcodebuild -project iosApp.xcodeproj -scheme iosApp -sdk iphoneos archive
      # 需要配置签名证书

  web:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: 'temurin', java-version: '17' }
      - run: ./gradlew wasmJsBrowserDistribution
      - uses: actions/upload-artifact@v4
        with: { name: web-dist, path: composeApp/build/dist/wasmJs/productionExecutable/ }
习题 12:发布流程文档

题目:为一个 CMP 项目编写简明的发布流程文档,涵盖从代码冻结到各端上线的步骤。

参考答案:

Android:更新版本号 → 生成签名 AAB → 上传 Google Play Console → 填写发布说明 → 分阶段发布。

iOS:更新版本号和构建号 → Xcode Archive → 上传 App Store Connect → 填写审核信息 → 提交审核。

桌面端:在 macOS/Windows/Linux 上分别运行 packageDmg/packageMsi/packageDeb → 上传到发布页面或官网下载区。

Web:运行 wasmJsBrowserDistribution → 部署到静态托管 → 验证路由回退和缓存头。

八、本课小结

Android 发布:keystore 签名 + AAB 上传。密码用环境变量,keystore 不入版本控制。

iOS 发布:Xcode 签名 + App Store Connect。Bundle ID 上传后不可更改。必须在 macOS 上构建。CMP 1.11.0 起并发渲染默认启用,iOS 最低支持 14.0。

桌面端发布:Compose Gradle 插件基于 jpackage/jlink 生成自包含安装包。不支持交叉编译,必须在对应操作系统上构建。jDeploy 是跨平台构建的替代方案。

Web 发布:wasmJsBrowserDistribution 生成静态产物,部署到任意托管。需要配置 SPA 路由回退和缓存策略。

CI/CD:iOS 和桌面端的平台约束要求矩阵策略。先在本地手动完成一次完整发布,再翻译成 CI 配置。

九、系列总结

十课走完,从环境搭建到全平台发布,CMP 的完整能力图谱已经展开。

核心认知:CMP 的价值不在于“一套代码替代所有原生开发”,而在于让 Kotlin 开发者以渐进式的方式共享逻辑和 UI。Android 端零额外开销,iOS/桌面/Web 的差距在逐年缩小。

技术栈:@Composable + 状态管理 + Modifier 构成 UI 基础;Navigation、Ktor、ViewModel、Koin 构成应用架构;expect/actual 和原生互操作处理平台差异;composeResources 和主题系统管理视觉资产。

发布认知:每个平台都有自己的约束和流程。iOS 的 macOS 限制、桌面端的交叉编译限制、Web 的路由回退,都是必须接受的工程现实。

下一步建议:进阶讲解。

Logo

一站式 AI 云服务平台

更多推荐