1. Compose Multiplatform 工程全景

Jetpack Compose 最初是 Android 平台上的声明式 UI 工具包,它用可组合函数(Composable)替代了 XML 布局与命令式 View 操作,把界面描述成“状态到 UI 的映射”。Compose Multiplatform 则把这套编程模型从 Android 扩展到桌面、iOS 和 Web,使同一套 UI 代码有机会在多个目标平台上运行。理解这一演进,是后续所有工程决策的起点:它决定了哪些代码可以共享、哪些必须平台化、哪些能力目前仍存在边界。

一、从 Jetpack Compose 到多平台:演进的三层结构

Compose 的跨平台能力并不是把 Android 的整套框架搬过去,而是分层实现的。最底层是 Compose Runtime,它只关心“可组合函数如何被记录、重组、调度”,与渲染平台无关;中间层是 Compose UI 与 Foundation,提供布局、绘制、手势、文本等基础能力;最上层是 Material 组件与平台适配层,负责把通用组件映射到各平台的原生交互习惯与渲染后端。

在 Android 上,Compose UI 最终通过 Android 的 Canvas 与 View 系统渲染;在桌面上,它使用 Skia 直接绘制到窗口;在 iOS 上,Compose 通过 Skia 渲染到 UIView 承载层;在 Web 上则编译为 Wasm 并借助 Skia/Web 渲染。也就是说,共享的是声明式 UI 模型与大部分组件逻辑,差异被收敛到渲染后端与平台适配层。这正是“一次编写、多端运行”能够成立的结构基础。

Compose Multiplatform 分层结构与目标平台 共享代码层(commonMain) Compose Runtime 重组 / 状态 / 调度 Compose UI / Foundation 布局 / 绘制 / 手势 / 文本 Material 组件 主题 / 组件 / 交互规范 平台适配层(expect / actual) 渲染后端 窗口 / 生命周期 输入 / 无障碍 平台 API 桥接 Android View / Canvas Desktop Skia / 窗口 iOS Skia / UIView Web (Wasm) Skia / Canvas 共享层代码在各平台复用;平台适配层通过 expect/actual 提供差异实现

二、核心优势:为什么值得把 UI 迁移到 Compose Multiplatform

第一是声明式模型带来的状态一致性。UI 由状态驱动,状态变化触发重组,开发者不再手动查找控件、修改属性。这在多平台场景下尤其重要:同一份状态逻辑在 Android、桌面、iOS 上产生一致的界面结果,减少了“各端表现不一致”的排查成本。

第二是共享粒度的灵活性。Compose Multiplatform 并不要求“全有或全无”。常见做法是共享业务逻辑、数据模型、状态管理,再共享大部分 UI;也可以只共享逻辑层,UI 仍用各平台原生实现。工程边界由团队按平台成熟度、交互差异和发布节奏自行划定。

第三是与 Kotlin 生态的协同。协程、Flow、序列化、Ktor 等库天然支持多平台,状态层与网络层可以复用,UI 层通过 collectAsState 等 API 消费数据流,形成从数据到界面的统一链路。

三、目标平台与工程边界

当前主流目标平台包括 Android、iOS、Desktop(JVM)与 Web(Wasm)。它们的成熟度并不相同:Android 与 Desktop 相对稳定,iOS 已可用于生产但需关注与原生混合的细节,Web 仍处于持续演进阶段。工程上需要明确三条边界。

  • 渲染边界:Compose 在各平台使用 Skia 或平台 Canvas 绘制,因此不直接复用原生控件。若必须嵌入原生 View 或 UIKit 组件,需要通过互操作 API 承载。
  • 能力边界:相机、蓝牙、推送、后台任务等平台能力,通常需要 expect/actual 或平台特定实现,不能假设 commonMain 中可直接调用。
  • 交互边界:桌面依赖鼠标与键盘,移动端依赖触摸与手势,iOS 与 Android 的返回导航、权限弹窗、系统栏行为也存在差异,需要在共享 UI 中做条件适配。
判断一段代码能否放入 commonMain,可以问三个问题:它是否依赖平台 API?它是否依赖平台渲染或控件?它的交互语义是否在所有目标平台一致?三个都“否”,才适合共享。

四、一个可参考的工程分层示例

下面是一个典型的多平台工程目录结构,用于说明共享与平台代码的边界。示例仅用于说明组织方式,具体包名与模块划分可按团队规范调整。

project/
├── composeApp/
│   ├── src/
│   │   ├── commonMain/kotlin/      # 共享 UI、状态、业务逻辑
│   │   │   ├── ui/                 # Composable 页面与组件
│   │   │   ├── state/              # ViewModel / 状态持有者
│   │   │   └── data/               # 仓库、模型、网络
│   │   ├── androidMain/kotlin/     # Android 入口、平台实现
│   │   ├── iosMain/kotlin/         # iOS 入口、平台实现
│   │   ├── desktopMain/kotlin/     # 桌面入口、窗口配置
│   │   └── wasmJsMain/kotlin/      # Web 入口
│   └── build.gradle.kts
└── gradle/libs.versions.toml       # 版本与依赖统一管理

在这个结构里,commonMain 承载可共享的界面与逻辑;各平台源集只保留入口、平台适配与必要的原生互操作。构建时,Gradle 会按目标平台编译对应的源集,最终产出 Android APK/AAB、iOS Framework、桌面安装包或 Web 资源。

五、学习路径与本节定位

后续章节会依次展开环境搭建、Compose 基础、布局与状态、导航、平台适配、性能与发布。本节的任务是建立一张“地图”:知道共享层与平台层各自负责什么,知道目标平台的成熟度差异,知道哪些能力必须平台化。带着这张地图进入后续实践,遇到“这段代码该放哪里”“为什么 iOS 上表现不同”这类问题时,就能快速定位到对应的层次,而不是在整条链路上盲目排查。

需要强调的是,Compose Multiplatform 的价值不在于“消灭平台差异”,而在于把差异集中到可控的适配层,让大部分 UI 与逻辑获得复用。工程边界划得越清楚,跨平台收益越稳定。