返回 跨端开发
跨端开发
14 分钟阅读

Compose Multiplatform 初体验:从 KMP 原理到多端跑通第一个 App

讲清 Compose Multiplatform 与 Kotlin Multiplatform、Jetpack Compose 的关系;梳理 shared / expect-actual 架构;手把手用 KMP Wizard 创建项目,在 Android、Desktop 与 iOS 上跑通共享 Composable。

为什么要关注 CMP? 如果你已经在用 Jetpack Compose 写 Android,却还要为 iOS 再维护一套 SwiftUI / UIKit,或者在 Desktop 上重写 UI——Compose Multiplatform(CMP) 提供了一条「同一套声明式 UI,编译到多个平台」的路径。它不是概念演示,JetBrains 与 Google 已在 Toolbox、KDoctor、Android Studio 部分新特性里用它落地。

这篇按 生态定位 → 架构原理 → 工程结构 → 上手实操 → 初体验结论 展开,目标是你读完后能 自己创建项目并在至少两个平台上跑起来


一、生态定位:KMP、Compose、CMP 分别是什么

┌─────────────────────────────────────────────────────────────────┐
│                    Kotlin Multiplatform (KMP)                    │
│         语言级跨端:共享业务逻辑,UI 各写各的(或部分共享)          │
│         expect / actual · 共享 ViewModel · 网络 / 数据库 / 算法   │
└───────────────────────────────┬─────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│              Compose Multiplatform (CMP)                         │
│         在 KMP 之上共享 UI:同一套 @Composable 编译到多端         │
│         Android · iOS · Desktop(JVM) · Web(Wasm)               │
└───────────────────────────────┬─────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│                   Jetpack Compose (Android)                      │
│         Android 专用声明式 UI 框架;CMP 与其 API 高度同源          │
└─────────────────────────────────────────────────────────────────┘
概念维护方共享什么典型场景
KMPJetBrains逻辑层(Repository、UseCase、序列化)双端共享网络与领域模型,UI 仍用 Compose + SwiftUI
CMPJetBrains + Google 协作逻辑 + UI 层 Composable工具类 App、内容展示、表单、内部管理系统
Jetpack ComposeGoogle仅 Android UI纯 Android 或 Android 为主的产品

关键认知: CMP ≠「把 Android Compose 原样拷贝到 iOS」。底层渲染走各平台原生管线(Android 用 Android 运行时,iOS 用 Skiko + UIKit 宿主,Desktop 用 Skiko + AWT/Swing 窗口),但 开发者面对的 API 表面 与 Jetpack Compose 高度一致。


二、原理层:CMP 如何做到「写一次,跑多端」

2.1 编译模型:Source Set 与 Target

KMP 工程按 Source Set 组织代码:

commonMain          ← 所有平台编译的共享代码(Composable、ViewModel、数据类)
    │
    ├── androidMain ← Android 专属(Activity、权限、Android Context)
    ├── iosMain     ← iOS 专属(UIViewController 桥接)
    ├── jvmMain     ← Desktop JVM 窗口入口
    └── wasmJsMain  ← Web(实验性)入口
Source Set作用
commonMain默认共享;放 @ComposableViewModel、纯 Kotlin 逻辑
*Main(平台)平台入口、无法抽象的系统 API
commonTest共享单测

Gradle 插件 org.jetbrains.kotlin.multiplatform 负责把 commonMain 编译成各 Target 字节码 / 框架产物。

2.2 expect / actual:平台差异的正式出口

无法在 commonMain 直接调用的 API(文件路径、蓝牙、Keychain),用 expect 声明、actual 实现

// commonMain
expect fun getPlatformName(): String
 
@Composable
expect fun PlatformIcon()
 
// androidMain
actual fun getPlatformName(): String = "Android"
 
@Composable
actual fun PlatformIcon() {
    Icon(Icons.Default.Android, contentDescription = null)
}
 
// iosMain
actual fun getPlatformName(): String = "iOS"
 
@Composable
actual fun PlatformIcon() {
    Icon(/* iOS 侧可用资源 */, contentDescription = null)
}

规则:

  • expectactual 签名必须一致(含 @Composable 注解)。
  • 每个 Target 必须有对应 actual,否则编译失败。
  • 能放 common 就别 expect——过度 expect 会让「跨端」退化成「五份实现」。

2.3 UI 运行时:Skiko 与原生宿主

平台UI 运行时窗口 / 宿主
AndroidCompose Android RuntimeComponentActivity.setContent { }
iOSCompose + Skiko 绘制UIViewController 嵌入 Swift ComposeView
DesktopCompose + Skikoapplication { Window { } }(Compose Desktop)
WebCompose + WasmCanvas / DOM 宿主(快速演进中)

Skiko(Skia + Kotlin)是 CMP 在非 Android 平台绘制 UI 的底层。你在 Composable 里写的 TextLazyColumn,最终由 Skiko 或 Android 运行时渲染到屏幕。

2.4 与 Jetpack Compose 的 API 关系

CMP 核心 UI 包为 org.jetbrains.compose.*,与 androidx.compose.* 高度相似

// commonMain — CMP 项目典型 import
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier

Material3、Foundation、Runtime 在 CMP 中有对应实现;部分 Android 专属 API(如 LocalContext.current 直接拿 Android Context)仅在 androidMain 可用

迁移心智: 会 Jetpack Compose,上手 CMP 的 UI 代码几乎零门槛;主要新增的是 工程结构、expect/actual、各平台 Run Configuration

2.5 架构建议:UI 共享 vs 逻辑共享

方案 A · 仅 KMP 共享逻辑(传统双端) common: Repository + ViewModel android: Compose UI ios: SwiftUI UI

方案 B · CMP 全共享 UI(工具 / 内容类 App) common: Repository + ViewModel + 全部 @Composable 各平台: 仅 Application / Activity / ViewController 壳

方案 C · 混合(多数商业 App 现实选择) common: 核心流程 UI + 逻辑 平台层: 支付、推送、深度系统集成单独 actual

方案优点风险
AiOS 原生体验最佳两套 UI 维护成本
B迭代最快、UI 完全一致iOS 平台感弱、包体与性能需验证
C平衡需清晰划分 common / 平台边界

三、实现层:环境准备与创建第一个项目

3.1 环境清单

目标平台必需推荐版本(2025–2026)
共用JDK 17+、Android Studio Ladybug+ / IntelliJ IDEAKotlin 2.0+
AndroidAndroid SDK、模拟器或真机AGP 8.x
Desktop无额外 SDK随 CMP 插件自带
iOSmacOS + Xcode + CocoaPodsXcode 15+;真机需 Apple 开发者账号
WebNode(部分模板)关注官方 Wasm 文档

插件:

  1. Kotlin Multiplatform — JetBrains 插件市场
  2. Compose Multiplatform — 与上者配合,提供项目模板与预览

也可直接用 Web 向导(无需先装 IDE):kmp.jetbrains.com

3.2 用 KMP Wizard 创建项目

推荐路径:File → New → Project → Kotlin Multiplatform → Compose Multiplatform Application

向导选项建议(初体验):

选项建议
Project nameCmpHello
Packagecom.example.cmphello
Targets✅ Android、✅ iOS、✅ Desktop;Web 可选
iOS frameworkRegular framework(Studio 直接 Run 更省心)
Code sharingShare UI(CMP 全 UI 模板)

生成后的典型结构:

CmpHello/
├── composeApp/                    # 主 KMP 模块
│   ├── src/
│   │   ├── commonMain/kotlin/
│   │   │   └── App.kt             # 共享 @Composable 入口
│   │   ├── androidMain/kotlin/
│   │   │   └── MainActivity.kt
│   │   ├── iosMain/kotlin/
│   │   │   └── MainViewController.kt
│   │   └── jvmMain/kotlin/
│   │       └── main.kt            # Desktop Window 入口
│   └── build.gradle.kts
├── iosApp/                        # Xcode 工程壳
│   ├── iosApp.xcodeproj
│   └── ContentView.swift          # 嵌入 Compose UIViewController
└── build.gradle.kts

3.3 核心入口代码走读

共享 UI(commonMain) — 业务与界面写在这里:

// composeApp/src/commonMain/kotlin/App.kt
@Composable
@Preview
fun App() {
    MaterialTheme {
        var count by remember { mutableStateOf(0) }
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
        ) {
            PlatformIcon()
            Spacer(Modifier.height(8.dp))
            Text("Running on ${getPlatformName()}")
            Spacer(Modifier.height(16.dp))
            Text("Count: $count", style = MaterialTheme.typography.headlineMedium)
            Spacer(Modifier.height(8.dp))
            Button(onClick = { count++ }) {
                Text("Increment")
            }
        }
    }
}

Android 入口 — 极薄:

// androidMain/MainActivity.kt
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { App() }
    }
}

Desktop 入口Window + application

// jvmMain/main.kt
fun main() = application {
    Window(
        onCloseRequest = ::exitApplication,
        title = "CmpHello",
    ) {
        App()
    }
}

iOS 入口 — Kotlin 提供 UIViewController,Swift 嵌入:

// iosMain/MainViewController.kt
fun MainViewController(): UIViewController =
    ComposeUIViewController { App() }
// iosApp/ContentView.swift(简化)
struct ContentView: View {
    var body: some View {
        ComposeView()
            .ignoresSafeArea(.all, edges: .bottom)
    }
}

三层壳各自 不超过十几行,其余全在 commonMain——这是 CMP 初体验里最直观的部分。


四、跑通三端:Run Configuration 与常见卡点

4.1 运行顺序建议

① composeApp [android]     ← 最快验证,与纯 Compose 几乎相同
        ↓
② composeApp [desktop]     ← 无需模拟器,启动 JVM 窗口
        ↓
③ composeApp [iosSimulator] ← 需 macOS + Xcode,首次编译较慢

在 Android Studio 顶栏 Run Configuration 中选择对应 Target,点击 Run。

4.2 各平台初体验差异

平台首次编译热重载 / 预览初体验印象
Android1–3 minCompose Preview(androidMain)与 Jetpack Compose 几乎无差
Desktop1–2 min重启较快窗口原生感好,适合内部工具
iOS 模拟器5–15 min(首次)无 Android 式 Preview能跑起来有「真的跨了」的体感
Web视模板而定变化快适合轻量展示,生产需评估

4.3 高频踩坑

现象原因处理
iOS 编译失败 pod installCocoaPods 未装或 Ruby 环境旧sudo gem install cocoapods;Xcode Command Line Tools
Unresolved reference: compose插件或 BOM 版本不匹配对齐 Kotlin、Compose Compiler、CMP 插件版本(看 兼容性表
Desktop 白屏Skiko 原生库下载失败检查网络 / Maven 镜像;./gradlew clean 重试
common 里用了 LocalContextAndroid 专属 API下沉到 androidMain 或 expect/actual
iOS 字体 / 间距与 Android 不一致平台默认 Typography 差异MaterialTheme 统一 typography;必要时平台 actual 微调

五、进阶一步:共享 ViewModel 与导航

初体验之后,通常会立刻遇到 状态放哪、页面怎么跳

5.1 共享 ViewModel(KMP + CMP 常见组合)

// commonMain
class CounterViewModel : ViewModel() {
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> = _count.asStateFlow()
 
    fun increment() { _count.update { it + 1 } }
}
 
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
    val count by viewModel.count.collectAsStateWithLifecycle()
    // UI ...
}

依赖 androidx.lifecycle:lifecycle-viewmodel-compose 的 KMP 版本,ViewModel 可放在 commonMain,Android / iOS / Desktop 共用同一套状态逻辑

5.2 导航

官方推荐 Compose Navigation 的 KMP 移植VoyagerDecompose 等跨端导航库。初体验阶段用「单屏 + 状态切换」即可;多页 App 再引入:

// 概念示例 — Voyager
class HomeScreen : Screen {
    @Composable
    override fun Content() {
        // ...
    }
}

导航选型属于 实践层 后续专题;初体验先确认 单屏 Composable 在三端行为一致 更重要。


六、与 Flutter / React Native 的快速对照

维度Compose MultiplatformFlutterReact Native
语言KotlinDartJS / TS
UI 范式声明式 Composable声明式 Widget声明式 Component
渲染原生管线 + Skiko自绘引擎 Skia桥接原生组件
与现有 Android 代码无缝(同 Compose)需重写需重写
iOS 工程Kotlin 框架嵌入 XcodeFlutter EngineRN Bridge
成熟生态快速增长,弱于 Flutter非常成熟成熟
适合谁Kotlin 团队、已有 Compose 资产从零跨端、UI 强一致Web 前端转移动端

若团队主力是 Kotlin + 已有 Jetpack Compose,CMP 的边际成本最低;若从零选型且要强 UI 一致与庞大插件生态,Flutter 仍是默认对比项。


七、初体验结论:现在适不适合上生产?

7.1 已经适合

  • 内部工具、Desktop 管理台、内容阅读类 App
  • Android 为主、iOS 要求「功能对齐」而非「像素级原生」
  • 已有 KMP 共享逻辑,希望 UI 也并入 common

7.2 仍需审慎

  • 重动画、复杂手势、深度 iOS Human Interface 适配
  • 对包体、冷启动极度敏感的消费级 iOS App
  • 大量依赖 Android 专属 Compose 库(Map、Media3 等无 common 版)

7.3 推荐学习路径

Level 1 · 本篇文章
  Wizard 建项 → Android + Desktop 跑通 → 改 commonMain 的 App.kt

Level 2 · 共享逻辑
  common ViewModel + Repository · expect/actual 封装平台 API

Level 3 · 真实业务
  网络(Ktor)· SQLDelight · 导航 · 资源(compose-resources)

Level 4 · 上线 iOS
  签名、TestFlight、性能与平台 actual 细化

八、小结

原理
  KMP 共享逻辑 → CMP 在之上共享 @Composable
  commonMain + expect/actual + 各平台薄壳
  非 Android 平台通过 Skiko 绘制

实现
  KMP Wizard 选 Compose Multiplatform Application
  UI 写 App.kt(commonMain)
  先 Run Android / Desktop,再跑 iOS 模拟器

初体验
  与 Jetpack Compose 写法几乎相同
  主要成本在工程配置与 iOS 工具链
  适合 Kotlin 团队做跨端 UI 的第一站

Compose Multiplatform 不是「又一个跨端框架」的简单重复,而是 把 Android 开发者已熟悉的 Compose 心智模型延伸到 Desktop 与 iOS。第一次在三端看到同一个 Button 计数器同步工作时,你对 KMP 的价值会有比文档更直观的认识。


参考

相关文章

Android
5 分钟
声明式UI布局
一种在构建MVVM业务逻辑阶段,就需要把所有情况定义好的UI布局,通过数据去推动页面的更新状态形式。(这是一种绝对的数据状态驱动UI展示逻辑,而不再是获取到数据之后,再去根据数据手动更新UI)
Android
4 分钟
如何在Android开发中实现MVVM的架构
MVVM与Android的实践简要指导
Java / Kotlin
9 分钟
Kotlin Coroutine
以图片处理串行流水线为例,对比 Callback、RxJava 与 Coroutines 三种异步写法