【Compose Multiplatform跨端声明式UI实用技术】第20课:CMP跨平台经典示例深度解析

前十九课系统覆盖了CMP从环境搭建到部署发布的完整技术栈。本课作为系列的收官之作,将以JetBrains官方示例和社区标杆项目为样本,逐层拆解CMP在真实项目中的架构决策、平台互操作策略与工程化实践。本课内容基于CMP 1.12.1与Kotlin 2.3.20。

目录

文末附有15道配套习题与本课知识点总结。

一、JetBrains官方示例全景导览

JetBrains在 compose-multiplatform 仓库的 examples/ 目录下维护了一组可运行的官方示例应用,覆盖图片查看、代码浏览、即时聊天、2D图形、导航、网络请求和Compose HTML等多个典型场景。

示例 描述 支持平台
Imageviewer 图片查看器应用(含相机与地图支持) Android、iOS、Desktop
Codeviewer 文件浏览器与代码查看器应用 Android、iOS、Desktop
Chat 简单的聊天应用 Android、iOS、Desktop
Graphics2D 2D游戏与图形示例 Android、iOS、Desktop
Nav Cupcake 演示Compose Navigation用法的多屏应用 Android、iOS、Desktop
Issues tracker 具有自适应UI与ktor-client的GitHub问题跟踪器 Android、Desktop
HTML based samples 使用Compose HTML Library编写的示例 浏览器(Web)

这些示例遵循高度一致的多平台工程结构:UI代码在 commonMain 中共享,同一套Compose声明式UI可编译到JVM(桌面)、Android、iOS(Kotlin/Native)以及Web/Wasm目标。Issues tracker仅面向Android与Desktop,而HTML系列示例完全面向浏览器场景,体现了不同技术路线的取舍。

官方示例的学习方法论:每个示例的README均提示参考官方KMP开发文档进行环境准备。建议按照"先跑通、再读懂、后迁移"的路径学习------先在本地运行官方示例,再逐模块阅读源码理解设计意图,最后将可复用的模式迁移到自己的项目中。

下面是官方示例的整体分类与学习路径:
#mermaid-svg-wsBq3K53IFEmgnir{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wsBq3K53IFEmgnir .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wsBq3K53IFEmgnir .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wsBq3K53IFEmgnir .error-icon{fill:#552222;}#mermaid-svg-wsBq3K53IFEmgnir .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wsBq3K53IFEmgnir .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wsBq3K53IFEmgnir .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wsBq3K53IFEmgnir .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wsBq3K53IFEmgnir .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wsBq3K53IFEmgnir .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wsBq3K53IFEmgnir .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wsBq3K53IFEmgnir .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wsBq3K53IFEmgnir .marker.cross{stroke:#333333;}#mermaid-svg-wsBq3K53IFEmgnir svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wsBq3K53IFEmgnir p{margin:0;}#mermaid-svg-wsBq3K53IFEmgnir .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wsBq3K53IFEmgnir .cluster-label text{fill:#333;}#mermaid-svg-wsBq3K53IFEmgnir .cluster-label span{color:#333;}#mermaid-svg-wsBq3K53IFEmgnir .cluster-label span p{background-color:transparent;}#mermaid-svg-wsBq3K53IFEmgnir .label text,#mermaid-svg-wsBq3K53IFEmgnir span{fill:#333;color:#333;}#mermaid-svg-wsBq3K53IFEmgnir .node rect,#mermaid-svg-wsBq3K53IFEmgnir .node circle,#mermaid-svg-wsBq3K53IFEmgnir .node ellipse,#mermaid-svg-wsBq3K53IFEmgnir .node polygon,#mermaid-svg-wsBq3K53IFEmgnir .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wsBq3K53IFEmgnir .rough-node .label text,#mermaid-svg-wsBq3K53IFEmgnir .node .label text,#mermaid-svg-wsBq3K53IFEmgnir .image-shape .label,#mermaid-svg-wsBq3K53IFEmgnir .icon-shape .label{text-anchor:middle;}#mermaid-svg-wsBq3K53IFEmgnir .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wsBq3K53IFEmgnir .rough-node .label,#mermaid-svg-wsBq3K53IFEmgnir .node .label,#mermaid-svg-wsBq3K53IFEmgnir .image-shape .label,#mermaid-svg-wsBq3K53IFEmgnir .icon-shape .label{text-align:center;}#mermaid-svg-wsBq3K53IFEmgnir .node.clickable{cursor:pointer;}#mermaid-svg-wsBq3K53IFEmgnir .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wsBq3K53IFEmgnir .arrowheadPath{fill:#333333;}#mermaid-svg-wsBq3K53IFEmgnir .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wsBq3K53IFEmgnir .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wsBq3K53IFEmgnir .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wsBq3K53IFEmgnir .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wsBq3K53IFEmgnir .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wsBq3K53IFEmgnir .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wsBq3K53IFEmgnir .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wsBq3K53IFEmgnir .cluster text{fill:#333;}#mermaid-svg-wsBq3K53IFEmgnir .cluster span{color:#333;}#mermaid-svg-wsBq3K53IFEmgnir div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wsBq3K53IFEmgnir .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wsBq3K53IFEmgnir rect.text{fill:none;stroke-width:0;}#mermaid-svg-wsBq3K53IFEmgnir .icon-shape,#mermaid-svg-wsBq3K53IFEmgnir .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wsBq3K53IFEmgnir .icon-shape p,#mermaid-svg-wsBq3K53IFEmgnir .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wsBq3K53IFEmgnir .icon-shape .label rect,#mermaid-svg-wsBq3K53IFEmgnir .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wsBq3K53IFEmgnir .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wsBq3K53IFEmgnir .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wsBq3K53IFEmgnir :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} JetBrains官方示例
全平台示例
特定平台示例
Imageviewer

相机+地图
Codeviewer

代码编辑器
Chat

CMP+SwiftUI
Graphics2D

2D渲染
Nav Cupcake

导航
Issues tracker

Android+Desktop
HTML samples

Web
学习路径:先跑通 → 再读懂 → 后迁移

二、深度拆解:Imageviewer的架构与平台互操作

Imageviewer是官方示例中最全面的一个------一个用于捕获、查看和存储图片并集成地图的应用,展示了CMP在所有平台上的完整能力。

核心架构决策 :UI在 commonMain 中共享,但相机、地图等硬件能力通过 expect/actual 机制下沉到各平台实现。Android端使用 AndroidView 嵌入CameraX和MapView,iOS端使用 UIKitView 封装AVFoundation与MapKit。

可迁移经验 :当CMP项目需要集成平台特有硬件能力时,应沿"平台能力依赖度"划定共享边界------数据模型、状态管理、导航逻辑放在 commonMain,硬件相关的UI和权限处理通过 expect/actual 下沉。

下面是 Imageviewer 的架构分层与平台互操作示意:
#mermaid-svg-Z8sjUUKf9jTMvMQ3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .error-icon{fill:#552222;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .marker.cross{stroke:#333333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 p{margin:0;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .cluster-label text{fill:#333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .cluster-label span{color:#333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .cluster-label span p{background-color:transparent;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .label text,#mermaid-svg-Z8sjUUKf9jTMvMQ3 span{fill:#333;color:#333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .node rect,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .node circle,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .node ellipse,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .node polygon,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .rough-node .label text,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .node .label text,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .image-shape .label,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .rough-node .label,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .node .label,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .image-shape .label,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .icon-shape .label{text-align:center;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .node.clickable{cursor:pointer;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .arrowheadPath{fill:#333333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .cluster text{fill:#333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .cluster span{color:#333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .icon-shape,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .icon-shape p,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .icon-shape .label rect,#mermaid-svg-Z8sjUUKf9jTMvMQ3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Z8sjUUKf9jTMvMQ3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} expect/actual 下沉
expect/actual 下沉
iOS 平台层
UIKitView
AVFoundation
MapKit
Android 平台层
AndroidView
CameraX
MapView
commonMain 共享层
数据模型
状态管理
导航逻辑

三、深度拆解:Chat的混合UI策略与Codeviewer的代码编辑器

3.1 Chat:CMP与SwiftUI互嵌

Chat示例演示了CMP最务实的落地路径之一:将CMP组件作为SwiftUI中的普通View嵌入 。核心机制是通过 ComposeUIViewController 在iOS端桥接Compose UI,实现SwiftUI状态与Compose状态之间的数据同步。

渐进式采用路径:在已有SwiftUI/UIKit项目中逐步引入CMP,而非全量重写。这一策略特别适合已有原生应用、希望逐步迁移的场景。

3.2 Codeviewer:代码编辑器UI与文件树导航

Codeviewer是一个专注于构建代码编辑器UI的示例,包含语法高亮和文件树导航。其核心架构价值在于:

  • 文件树导航 :使用 LazyColumn 构建层级文件树,展示如何在共享代码中处理树形数据结构和展开/折叠状态管理
  • 语法高亮 :通过 AnnotatedString 和 SpanStyle 在跨平台代码中实现文本着色
  • 文本布局 :使用 SelectionContainer 和等宽字体展示代码内容

可迁移经验:文件树导航是许多工具类应用的核心组件。Codeviewer展示了如何在CMP中处理递归数据结构、缩进层级和滚动位置恢复。

四、深度拆解:Nav Cupcake的导航架构

Nav Cupcake是理解CMP导航演进的绝佳样本------一个演示Compose Navigation用法的多屏应用,支持Android、iOS和Desktop。

Navigation 2的局限与Navigation 3的演进 :Nav Cupcake基于Navigation 2 API构建,使用 NavController 和 NavGraph 管理导航。Navigation 3彻底重设计了这一模型:返回栈从库内部状态变为开发者直接持有的 SnapshotStateList,导航操作简化为对列表的增删,自适应多窗格布局成为一等公民。

可迁移经验:导航架构的核心原则是"状态向下流动,事件向上流动"。无论使用Navigation 2还是3,保持单向数据流是导航可测试性和可预测性的基础。

下面是 Navigation 2 到 Navigation 3 的演进示意:
#mermaid-svg-OXXQ4FWsRx85BX1a{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OXXQ4FWsRx85BX1a .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OXXQ4FWsRx85BX1a .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OXXQ4FWsRx85BX1a .error-icon{fill:#552222;}#mermaid-svg-OXXQ4FWsRx85BX1a .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OXXQ4FWsRx85BX1a .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OXXQ4FWsRx85BX1a .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OXXQ4FWsRx85BX1a .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OXXQ4FWsRx85BX1a .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OXXQ4FWsRx85BX1a .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OXXQ4FWsRx85BX1a .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OXXQ4FWsRx85BX1a .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OXXQ4FWsRx85BX1a .marker.cross{stroke:#333333;}#mermaid-svg-OXXQ4FWsRx85BX1a svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OXXQ4FWsRx85BX1a p{margin:0;}#mermaid-svg-OXXQ4FWsRx85BX1a .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OXXQ4FWsRx85BX1a .cluster-label text{fill:#333;}#mermaid-svg-OXXQ4FWsRx85BX1a .cluster-label span{color:#333;}#mermaid-svg-OXXQ4FWsRx85BX1a .cluster-label span p{background-color:transparent;}#mermaid-svg-OXXQ4FWsRx85BX1a .label text,#mermaid-svg-OXXQ4FWsRx85BX1a span{fill:#333;color:#333;}#mermaid-svg-OXXQ4FWsRx85BX1a .node rect,#mermaid-svg-OXXQ4FWsRx85BX1a .node circle,#mermaid-svg-OXXQ4FWsRx85BX1a .node ellipse,#mermaid-svg-OXXQ4FWsRx85BX1a .node polygon,#mermaid-svg-OXXQ4FWsRx85BX1a .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OXXQ4FWsRx85BX1a .rough-node .label text,#mermaid-svg-OXXQ4FWsRx85BX1a .node .label text,#mermaid-svg-OXXQ4FWsRx85BX1a .image-shape .label,#mermaid-svg-OXXQ4FWsRx85BX1a .icon-shape .label{text-anchor:middle;}#mermaid-svg-OXXQ4FWsRx85BX1a .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OXXQ4FWsRx85BX1a .rough-node .label,#mermaid-svg-OXXQ4FWsRx85BX1a .node .label,#mermaid-svg-OXXQ4FWsRx85BX1a .image-shape .label,#mermaid-svg-OXXQ4FWsRx85BX1a .icon-shape .label{text-align:center;}#mermaid-svg-OXXQ4FWsRx85BX1a .node.clickable{cursor:pointer;}#mermaid-svg-OXXQ4FWsRx85BX1a .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OXXQ4FWsRx85BX1a .arrowheadPath{fill:#333333;}#mermaid-svg-OXXQ4FWsRx85BX1a .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OXXQ4FWsRx85BX1a .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OXXQ4FWsRx85BX1a .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OXXQ4FWsRx85BX1a .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OXXQ4FWsRx85BX1a .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OXXQ4FWsRx85BX1a .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OXXQ4FWsRx85BX1a .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OXXQ4FWsRx85BX1a .cluster text{fill:#333;}#mermaid-svg-OXXQ4FWsRx85BX1a .cluster span{color:#333;}#mermaid-svg-OXXQ4FWsRx85BX1a div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OXXQ4FWsRx85BX1a .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OXXQ4FWsRx85BX1a rect.text{fill:none;stroke-width:0;}#mermaid-svg-OXXQ4FWsRx85BX1a .icon-shape,#mermaid-svg-OXXQ4FWsRx85BX1a .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OXXQ4FWsRx85BX1a .icon-shape p,#mermaid-svg-OXXQ4FWsRx85BX1a .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OXXQ4FWsRx85BX1a .icon-shape .label rect,#mermaid-svg-OXXQ4FWsRx85BX1a .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OXXQ4FWsRx85BX1a .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OXXQ4FWsRx85BX1a .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OXXQ4FWsRx85BX1a :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 演进
Navigation 3
SnapshotStateList
开发者直接持有返回栈
导航操作=列表增删
自适应多窗格布局
Navigation 2
NavController
NavGraph
返回栈由库内部管理

五、深度拆解:Graphics2D的跨平台渲染

Graphics2D是一个运行在Android、iOS、桌面和浏览器中的2D图形示例,展示CMP在不同平台上的渲染能力。

技术要点 :Graphics2D使用Compose的 Canvas API直接绘制图形,绕过了常规的UI组件层。它展示了 DrawScope、Path、Brush 等绘图API在跨平台场景下的行为一致性。

跨平台差异:Android端使用Jetpack Compose的原生渲染管线,iOS/Desktop/Web端通过Skiko渲染。虽然绘图API在commonMain中完全一致,但iOS端的Metal着色器编译可能引入额外延迟(第17课讨论的着色器预热策略在此场景中尤为重要)。

六、深度拆解:Issues tracker的自适应UI与网络集成

Issues tracker是一个集成自适应UI与ktor-client的GitHub问题跟踪器,仅面向Android和Desktop。

核心架构价值:

  • 自适应布局 :使用 BoxWithConstraints 或Material 3 Adaptive库,根据窗口宽度动态切换单栏/双栏布局------手机竖屏显示列表,平板横屏同时显示列表和详情
  • 网络集成:使用Ktor Client获取GitHub API数据,展示Repository模式在共享代码中的实现
  • 仅面向Android和Desktop:这两个平台的窗口尺寸变化更频繁,自适应布局的价值更突出;移动端(iOS)的窗口尺寸相对固定

可迁移经验 :自适应布局的核心是"根据容器约束而非屏幕尺寸做决策"。BoxWithConstraints 在布局阶段暴露父容器的 maxWidth/minWidth,是实现响应式布局的核心工具(第5课已详细展开)。

七、社区标杆:kmp-clean-architecture的模块化实践

kmp-clean-architecture是一个模块化的KMP项目模板,面向Android、iOS、Desktop和Web,强调关注点分离、基于功能的模块化、共享的data与domain层、平台特定的UI宿主。

核心模块划分:

复制代码
kmp-clean-architecture/
├── composeApp/          # 平台UI宿主(AndroidApp、iOSApp、WebApp、DesktopApp)
├── features/            # 功能模块
│   ├── login/
│   ├── registration/
│   └── home/
├── data/                # 数据层
│   ├── core-utils/      # 通用工具
│   ├── core-net/        # 网络层
│   ├── core-analytic/   # 分析逻辑
│   └── data/            # Repository、DataSource、Mapper
└── shared/              # 多平台共享模块

构建产物 :Android产出 .aar,iOS产出 .xcframework,支持作为库集成到其他项目中。

可迁移经验 :模块化架构的核心价值在于------Gradle模块依赖关系本身就是一种架构约束。如果 domain 模块试图依赖 data 模块,构建会失败(第16课已详细讨论)。

八、社区标杆:kmp-mvi-compose-architecture-sample的MVI落地

这是一个生产级MVI架构示例,技术栈为KMP + Compose Multiplatform + Koin + Ktor + Coil。

架构分层:

层次 职责
Presentation Layer BaseViewModel、BaseScreen、Intent/State/Event处理
Data Layer Repository模式、API服务(Ktor)

MVI数据流:用户操作 → Intent → ViewModel → State更新 → UI重组。这一单向数据流确保了可预测的状态管理、高可扩展性和更好的可测试性。

可迁移经验 :该项目的 BaseViewModel 和 BaseScreen 提供了可复用的基础设施------新功能只需继承基类并定义自己的Intent/State即可。这种"基类模式"在中小型项目中可以显著减少样板代码。

下面是 MVI 单向数据流示意:
#mermaid-svg-Z4YfXUHecrh1J9Il{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Z4YfXUHecrh1J9Il .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Z4YfXUHecrh1J9Il .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Z4YfXUHecrh1J9Il .error-icon{fill:#552222;}#mermaid-svg-Z4YfXUHecrh1J9Il .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Z4YfXUHecrh1J9Il .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Z4YfXUHecrh1J9Il .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Z4YfXUHecrh1J9Il .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Z4YfXUHecrh1J9Il .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Z4YfXUHecrh1J9Il .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Z4YfXUHecrh1J9Il .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Z4YfXUHecrh1J9Il .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Z4YfXUHecrh1J9Il .marker.cross{stroke:#333333;}#mermaid-svg-Z4YfXUHecrh1J9Il svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Z4YfXUHecrh1J9Il p{margin:0;}#mermaid-svg-Z4YfXUHecrh1J9Il .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Z4YfXUHecrh1J9Il .cluster-label text{fill:#333;}#mermaid-svg-Z4YfXUHecrh1J9Il .cluster-label span{color:#333;}#mermaid-svg-Z4YfXUHecrh1J9Il .cluster-label span p{background-color:transparent;}#mermaid-svg-Z4YfXUHecrh1J9Il .label text,#mermaid-svg-Z4YfXUHecrh1J9Il span{fill:#333;color:#333;}#mermaid-svg-Z4YfXUHecrh1J9Il .node rect,#mermaid-svg-Z4YfXUHecrh1J9Il .node circle,#mermaid-svg-Z4YfXUHecrh1J9Il .node ellipse,#mermaid-svg-Z4YfXUHecrh1J9Il .node polygon,#mermaid-svg-Z4YfXUHecrh1J9Il .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Z4YfXUHecrh1J9Il .rough-node .label text,#mermaid-svg-Z4YfXUHecrh1J9Il .node .label text,#mermaid-svg-Z4YfXUHecrh1J9Il .image-shape .label,#mermaid-svg-Z4YfXUHecrh1J9Il .icon-shape .label{text-anchor:middle;}#mermaid-svg-Z4YfXUHecrh1J9Il .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Z4YfXUHecrh1J9Il .rough-node .label,#mermaid-svg-Z4YfXUHecrh1J9Il .node .label,#mermaid-svg-Z4YfXUHecrh1J9Il .image-shape .label,#mermaid-svg-Z4YfXUHecrh1J9Il .icon-shape .label{text-align:center;}#mermaid-svg-Z4YfXUHecrh1J9Il .node.clickable{cursor:pointer;}#mermaid-svg-Z4YfXUHecrh1J9Il .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Z4YfXUHecrh1J9Il .arrowheadPath{fill:#333333;}#mermaid-svg-Z4YfXUHecrh1J9Il .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Z4YfXUHecrh1J9Il .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Z4YfXUHecrh1J9Il .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Z4YfXUHecrh1J9Il .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Z4YfXUHecrh1J9Il .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Z4YfXUHecrh1J9Il .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Z4YfXUHecrh1J9Il .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Z4YfXUHecrh1J9Il .cluster text{fill:#333;}#mermaid-svg-Z4YfXUHecrh1J9Il .cluster span{color:#333;}#mermaid-svg-Z4YfXUHecrh1J9Il div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Z4YfXUHecrh1J9Il .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Z4YfXUHecrh1J9Il rect.text{fill:none;stroke-width:0;}#mermaid-svg-Z4YfXUHecrh1J9Il .icon-shape,#mermaid-svg-Z4YfXUHecrh1J9Il .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Z4YfXUHecrh1J9Il .icon-shape p,#mermaid-svg-Z4YfXUHecrh1J9Il .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Z4YfXUHecrh1J9Il .icon-shape .label rect,#mermaid-svg-Z4YfXUHecrh1J9Il .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Z4YfXUHecrh1J9Il .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Z4YfXUHecrh1J9Il .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Z4YfXUHecrh1J9Il :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 新用户操作
用户操作
Intent
ViewModel
State更新
UI重组

九、生产级实践:支付宝MYKMP的工业实践

支付宝已正式开源基于KMP与CMP的三端原生跨平台方案MYKMP,共开源17个仓库,涵盖Kotlin/Native、Skia/Skiko、Compose Core、基础库、Compose AntUI及三端示例工程。自2024年第四季度首个业务上线以来,MYKMP已落地支付宝多个核心场景,日均承载亿级线上流量。

9.1 三端共享架构

在HarmonyOS端,共享Kotlin代码编译为动态库接入现有工程,Compose UI经Skia绘制到XComponent,并可调用平台API、嵌入ArkUI原生组件。

9.2 编译目标与互操作

基于Kotlin 2.3,新增 ohos_arm64 target,开发者可通过 ohosArm64() 声明HarmonyOS目标,在默认的 ohosArm64Main 或自定义OHOS source set中实现平台代码,再将共享模块编译为供HarmonyOS宿主加载的 .so 动态库。

9.3 产物裁剪与性能优化

符号导出裁剪 :iOS端通过 @ObjCExport 和导出白名单控制Objective-C包装,HarmonyOS端只为带 @CName 或 @ObjCName 的函数生成C Export入口。构建优化 :ohos_arm64 链接使用 --gc-sections,非Debug构建裁剪调试符号;iOS target使用 -dead_strip。

内存调优 :调整小对象分配阈值和GC默认堆参数,增加目标堆平滑调整;在开启调试信息的iOS构建中,通过 tracePrefix 和signpost跟踪对象分配与释放。AddressSanitizer :在Kotlin/Native的LLVM优化流程中增加ASAN Pipeline,为 ohos_arm64 接入ASAN。

对普通CMP项目的启示:符号导出裁剪、Kotlin IR DCE和链接阶段裁剪是值得借鉴的通用优化手段。GC参数调优在小规模应用中通常不需要,但在内存敏感场景中值得关注。

9.4 国内KMP实践全景

美团餐饮系统通过KMP实现了共6个终端App的逻辑层复用,覆盖Android/iOS/Windows三个平台。携程从2021年初开始探索KMP实现跨端业务逻辑复用,攻克了工程集成、基础组件KMP化、iOS cinterop互操作、Kotlin/Native异步并发模型等多个疑难问题,并开源了SQLlin(KMP ORM框架)和mmkv-kotlin(MVVM的KMP封装)。哔哩哔哩在KMP on iOS的深度工程化方面输出多篇实践,包括模块化、并发编译与98%增量构建加速。

十、官方生产级示例:KMP RSS Reader

Kotlin官方维护的 kmp-production-sample 是一个开源跨平台RSS阅读器,设计目的是演示KMM如何在真实生产项目中使用。

架构亮点 :该示例展示了不仅共享data和domain层,还共享应用状态 。使用Redux模式管理应用状态,Store 类分发由用户或异步工作产生的action,生成新状态,并通过Kotlin的 StateFlow 促进状态更新的订阅。

原生UI的桥接 :UI层完全原生------iOS使用SwiftUI,Android使用Jetpack Compose,Desktop使用Compose Multiplatform,Web使用React.js。在iOS端,KMM库中的 Store 被包装为 ObservableObject,状态实现为 @Published 属性,通过 EnvironmentObject 注入到根视图。

可迁移经验:这是"共享业务逻辑 + 平台原生UI"策略的典型参考。当团队需要保持平台原生体验时,采用此模式;当团队追求更高的UI代码共享率时,参考第16课的全共享UI策略。

十一、示例横向对比与方法论提炼

维度 Imageviewer Chat Nav Cupcake kmp-clean-architecture MYKMP
UI共享策略 全共享 部分共享(CMP+SwiftUI) 全共享 全共享 全共享
平台互操作深度 高(相机/地图) 中(视图嵌入) 低 中 高(三端原生调用)
架构模式 MVVM MVVM Navigation 2 Clean Architecture 分层+平台适配
生态依赖 Accompanist 无 Navigation Ktor/Coil Ktor/SQLDelight/Koin
适用规模 中型 小型 小型 中大型 超大型

从示例中提炼的三条核心原则:

共享边界应沿"平台能力依赖度"划定 。Imageviewer的相机/地图通过 expect/actual 下沉,Chat的CMP组件作为SwiftUI的View嵌入,MYKMP的ArkUI组件通过interop定义接入。共享的粒度不是"全有或全无",而是根据平台能力依赖度动态调整。

混合UI是降低迁移风险的有效策略。Chat展示了CMP与SwiftUI互嵌的渐进式路径,KMP RSS Reader展示了共享状态+原生UI的模式。不必追求100%的UI共享率。

示例代码的价值不在直接复用,而在理解设计意图。当面对自己的项目时,能够判断"这个场景该参考哪个示例的哪种做法"------例如,需要深度平台集成时参考Imageviewer的互操作模式,需要跨平台状态共享时参考KMP RSS Reader的Redux Store模式。

下面是三条核心原则的决策参考图:
#mermaid-svg-DYupeRz6J2Rn2hhL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DYupeRz6J2Rn2hhL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DYupeRz6J2Rn2hhL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DYupeRz6J2Rn2hhL .error-icon{fill:#552222;}#mermaid-svg-DYupeRz6J2Rn2hhL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DYupeRz6J2Rn2hhL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DYupeRz6J2Rn2hhL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DYupeRz6J2Rn2hhL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DYupeRz6J2Rn2hhL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DYupeRz6J2Rn2hhL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DYupeRz6J2Rn2hhL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DYupeRz6J2Rn2hhL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DYupeRz6J2Rn2hhL .marker.cross{stroke:#333333;}#mermaid-svg-DYupeRz6J2Rn2hhL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DYupeRz6J2Rn2hhL p{margin:0;}#mermaid-svg-DYupeRz6J2Rn2hhL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DYupeRz6J2Rn2hhL .cluster-label text{fill:#333;}#mermaid-svg-DYupeRz6J2Rn2hhL .cluster-label span{color:#333;}#mermaid-svg-DYupeRz6J2Rn2hhL .cluster-label span p{background-color:transparent;}#mermaid-svg-DYupeRz6J2Rn2hhL .label text,#mermaid-svg-DYupeRz6J2Rn2hhL span{fill:#333;color:#333;}#mermaid-svg-DYupeRz6J2Rn2hhL .node rect,#mermaid-svg-DYupeRz6J2Rn2hhL .node circle,#mermaid-svg-DYupeRz6J2Rn2hhL .node ellipse,#mermaid-svg-DYupeRz6J2Rn2hhL .node polygon,#mermaid-svg-DYupeRz6J2Rn2hhL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DYupeRz6J2Rn2hhL .rough-node .label text,#mermaid-svg-DYupeRz6J2Rn2hhL .node .label text,#mermaid-svg-DYupeRz6J2Rn2hhL .image-shape .label,#mermaid-svg-DYupeRz6J2Rn2hhL .icon-shape .label{text-anchor:middle;}#mermaid-svg-DYupeRz6J2Rn2hhL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DYupeRz6J2Rn2hhL .rough-node .label,#mermaid-svg-DYupeRz6J2Rn2hhL .node .label,#mermaid-svg-DYupeRz6J2Rn2hhL .image-shape .label,#mermaid-svg-DYupeRz6J2Rn2hhL .icon-shape .label{text-align:center;}#mermaid-svg-DYupeRz6J2Rn2hhL .node.clickable{cursor:pointer;}#mermaid-svg-DYupeRz6J2Rn2hhL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DYupeRz6J2Rn2hhL .arrowheadPath{fill:#333333;}#mermaid-svg-DYupeRz6J2Rn2hhL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DYupeRz6J2Rn2hhL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DYupeRz6J2Rn2hhL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DYupeRz6J2Rn2hhL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DYupeRz6J2Rn2hhL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DYupeRz6J2Rn2hhL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DYupeRz6J2Rn2hhL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DYupeRz6J2Rn2hhL .cluster text{fill:#333;}#mermaid-svg-DYupeRz6J2Rn2hhL .cluster span{color:#333;}#mermaid-svg-DYupeRz6J2Rn2hhL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DYupeRz6J2Rn2hhL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DYupeRz6J2Rn2hhL rect.text{fill:none;stroke-width:0;}#mermaid-svg-DYupeRz6J2Rn2hhL .icon-shape,#mermaid-svg-DYupeRz6J2Rn2hhL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DYupeRz6J2Rn2hhL .icon-shape p,#mermaid-svg-DYupeRz6J2Rn2hhL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DYupeRz6J2Rn2hhL .icon-shape .label rect,#mermaid-svg-DYupeRz6J2Rn2hhL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DYupeRz6J2Rn2hhL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DYupeRz6J2Rn2hhL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DYupeRz6J2Rn2hhL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
面对自己的CMP项目
需要深度平台集成?
参考 Imageviewer

expect/actual 互操作模式
需要跨平台状态共享?
参考 KMP RSS Reader

Redux Store 模式
已有原生应用?
参考 Chat

CMP+SwiftUI 渐进式嵌入
参考 kmp-clean-architecture

模块化架构

配套习题(15题)

第一组:官方示例全景(4题)

第1题 JetBrains官方示例中,支持相机与地图集成的示例是( )

A. Codeviewer

B. Imageviewer

C. Nav Cupcake

D. Graphics2D

答案:B

解析:Imageviewer是官方示例中最全面的一个,包含相机与地图支持,展示了CMP在所有平台上的完整能力。

第2题 以下哪个官方示例仅面向Android和Desktop平台?( )

A. Chat

B. Nav Cupcake

C. Issues tracker

D. Codeviewer

答案:C

解析:Issues tracker仅面向Android和Desktop,其自适应布局在这两个窗口尺寸变化更频繁的平台上价值更突出。

第3题 Graphics2D示例的核心技术价值是( )

A. 网络请求

B. 使用Canvas API直接绘制图形,展示跨平台渲染能力

C. 数据库操作

D. 导航管理

答案:B

解析:Graphics2D使用Compose的Canvas API直接绘制图形,展示了DrawScope、Path、Brush等绘图API在跨平台场景下的行为一致性。

第4题 官方示例的推荐学习路径是( )

A. 直接复制代码

B. 先跑通、再读懂、后迁移

C. 只阅读源码不运行

D. 仅阅读文档

答案:B

解析:建议按照"先跑通、再读懂、后迁移"的路径学习------先在本地运行官方示例,再逐模块阅读源码理解设计意图,最后将可复用的模式迁移到自己的项目中。

第二组:架构与互操作(4题)

第5题 Imageviewer中,相机和地图等硬件能力的实现方式是( )

A. 在commonMain中直接调用

B. 通过expect/actual机制下沉到各平台实现

C. 使用第三方库统一实现

D. 仅支持Android平台

答案:B

解析:Imageviewer的UI在commonMain中共享,相机和地图等硬件能力通过expect/actual机制下沉到各平台实现,Android端使用AndroidView嵌入CameraX和MapView,iOS端使用UIKitView封装AVFoundation与MapKit。

第6题 Chat示例演示的混合UI策略的核心机制是( )

A. 完全重写SwiftUI界面

B. 通过ComposeUIViewController在iOS端桥接Compose UI

C. 使用WebView嵌入

D. 仅在Android上运行

答案:B

解析:Chat示例演示了将CMP组件作为SwiftUI中的普通View嵌入,核心机制是通过ComposeUIViewController在iOS端桥接Compose UI。

第7题 Nav Cupcake基于哪个导航版本构建?( )

A. Navigation 1

B. Navigation 2

C. Navigation 3

D. 无导航

答案:B

解析:Nav Cupcake基于Navigation 2 API构建,使用NavController和NavGraph管理导航。Navigation 3彻底重设计了这一模型。

第8题 Codeviewer示例的核心架构价值在于展示( )

A. 网络请求

B. 文件树导航和语法高亮的跨平台实现

C. 数据库操作

D. 动画效果

答案:B

解析:Codeviewer专注于构建代码编辑器UI,包含语法高亮(通过AnnotatedString和SpanStyle)和文件树导航(使用LazyColumn构建层级文件树)。

第三组:社区标杆与生产实践(4题)

第9题 kmp-clean-architecture的核心架构特征是( )

A. 单一模块架构

B. 基于功能的模块化与共享data/domain层

C. 仅支持Android平台

D. 不使用Clean Architecture

答案:B

解析:kmp-clean-architecture强调关注点分离、基于功能的模块化、共享的data与domain层、平台特定的UI宿主。

第10题 kmp-clean-architecture构建的产物格式是( )

A. APK和IPA

B. .aar(Android)和.xcframework(iOS)

C. JAR和WAR

D. 仅AAB

答案:B

解析:kmp-clean-architecture的构建产物为Android的.aar和iOS的.xcframework,支持作为库集成到其他项目中。

第11题 kmp-mvi-compose-architecture-sample使用的MVI数据流顺序是( )

A. State → Intent → ViewModel → UI

B. 用户操作 → Intent → ViewModel → State更新 → UI重组

C. ViewModel → State → Intent → UI

D. UI → State → ViewModel → Intent

答案:B

解析:MVI数据流为:用户操作 → Intent → ViewModel → State更新 → UI重组,确保单向数据流和可预测的状态管理。

第12题 支付宝MYKMP中,HarmonyOS端的Compose UI渲染方式是( )

A. 使用HarmonyOS原生渲染

B. 经Skia绘制到XComponent

C. 使用WebView渲染

D. 使用OpenGL渲染

答案:B

解析:在HarmonyOS端,共享Kotlin代码编译为动态库接入现有工程,Compose UI经Skia绘制到XComponent,并可调用平台API、嵌入ArkUI原生组件。

第四组:工程决策方法论(3题)

第13题 KMP RSS Reader(kmp-production-sample)使用的状态管理模式是( )

A. MVVM

B. MVI

C. Redux模式

D. MVC

答案:C

解析:KMP RSS Reader使用Redux模式管理应用状态,Store类分发由用户或异步工作产生的action,生成新状态,并通过Kotlin的StateFlow促进状态更新的订阅。

第14题 从官方示例中提炼的共享边界划定原则是( )

A. 所有代码都应共享

B. 共享边界应沿"平台能力依赖度"划定

C. 不共享任何代码

D. 仅共享UI代码

答案:B

解析:共享边界应沿"平台能力依赖度"划定。Imageviewer的相机/地图通过expect/actual下沉,Chat的CMP组件作为SwiftUI的View嵌入,共享的粒度根据平台能力依赖度动态调整。

第15题 关于示例代码的价值,以下说法正确的是( )

A. 示例代码可以直接复制到生产项目

B. 示例代码的价值在于理解设计意图,当面对自己的项目时能够判断参考哪个示例的哪种做法

C. 示例代码仅供学习语法

D. 示例代码不需要运行

答案:B

解析:示例代码的价值不在直接复用,而在理解设计意图。需要深度平台集成时参考Imageviewer的互操作模式,需要跨平台状态共享时参考KMP RSS Reader的Redux Store模式。

本课知识点总结

1. JetBrains官方示例全景

官方示例覆盖Imageviewer(相机+地图)、Codeviewer(代码编辑器UI)、Chat(CMP+SwiftUI互嵌)、Graphics2D(2D渲染)、Nav Cupcake(导航)、Issues tracker(自适应UI+网络)和HTML samples(Web)等场景。所有示例遵循"UI在commonMain中共享,平台能力通过expect/actual下沉"的核心模式。学习路径为"先跑通、再读懂、后迁移"。

2. 官方示例的架构决策

Imageviewer沿"平台能力依赖度"划定共享边界------数据模型和状态管理共享,相机/地图通过expect/actual下沉。Chat演示CMP组件作为SwiftUI的View嵌入,通过ComposeUIViewController桥接。Nav Cupcake基于Navigation 2,其演进到Navigation 3的过程体现了导航模型从"库拥有返回栈"到"开发者拥有返回栈"的转变。Graphics2D展示Canvas API的跨平台渲染一致性。Issues tracker展示自适应布局与Ktor网络集成。

3. 社区架构标杆

kmp-clean-architecture采用多模块Clean Architecture,ComposeApp(平台UI宿主)+ features(功能模块)+ data(数据层)+ shared(共享模块),构建产物为.aar和.xcframework。kmp-mvi-compose-architecture-sample实现MVI架构,提供BaseViewModel/BaseScreen基类,数据流为"用户操作→Intent→ViewModel→State更新→UI重组"。

4. 支付宝MYKMP工业实践

MYKMP基于Kotlin 2.3新增ohos_arm64 target,HarmonyOS端共享Kotlin代码编译为.so动态库,Compose UI经Skia绘制到XComponent。产物优化包括符号导出裁剪(@ObjCExport白名单、@CName入口)、Kotlin IR DCE、链接阶段裁剪(--gc-sections/-dead_strip)、GC参数调优和AddressSanitizer接入。已落地支付宝多个核心场景,日均承载亿级线上流量。

5. KMP RSS Reader的Redux模式

Kotlin官方kmp-production-sample使用Redux模式在共享模块中管理应用状态------Store类分发action生成新状态,通过StateFlow促进订阅。iOS端将Store包装为ObservableObject,状态实现为@Published属性,通过EnvironmentObject注入根视图。这是"共享业务逻辑+平台原生UI"策略的典型参考。

6. 示例横向对比与方法论提炼

三条核心原则:共享边界应沿"平台能力依赖度"划定 (Imageviewer的expect/actual、Chat的View嵌入、MYKMP的interop定义);混合UI是降低迁移风险的有效策略 (Chat的CMP+SwiftUI互嵌、KMP RSS Reader的共享状态+原生UI);示例代码的价值不在直接复用,而在理解设计意图------当面对自己的项目时,能够判断"这个场景该参考哪个示例的哪种做法"。


系列总结:至此,20课系列全部完成。从环境搭建到部署发布,从声明式UI基础到生产级架构,从平台抽象到性能优化,希望本系列能帮助你系统掌握Compose Multiplatform的实用技术。CMP正在快速演进------iOS已稳定、Hot Reload开箱即用、Navigation 3重塑导航模型、MCP服务器开启AI辅助开发新范式。建议持续关注JetBrains官方更新和社区动态,将本系列的知识框架与最新的技术演进结合起来。

相关推荐
传奇开心果编程2 小时前
【Rust入门练中学】第8课:错误处理
开发语言·rust·错误处理·编程语言理论·通用编程语言
贾伟康2 小时前
【化学实验室|19】HarmonyOS ArkTS 回归测试实战:覆盖启动、空数据、异常输入和重复点击
软件测试·移动开发·harmonyos·arkts·arkui
敲代码的瓦龙3 小时前
Jetpack?lifecycle!!!
android·kotlin
传奇开心果编程4 小时前
【Rust入门练中学】第9课:集合类型
开发语言·rust·编程语言理论·集合类型·通用编程语言
Kapaseker7 小时前
为什么程序员始终无法彻底解决内存泄漏?
android·kotlin
汤姆宝库7 小时前
推理工程深拆:调度、量化与投机解码
android·开发语言·kotlin
传奇开心果编程1 天前
【Compose Multiplatform跨端声明式UI实用技术】第17课:性能优化与基准测试
性能优化·移动开发·跨平台·编程语言理论·开发范式·编程框架·声明式ui
Eyfcom1 天前
〖深度〗鸿蒙 ArkTS 移动应用开发经验:从 TypeScript 心智模型到声明式运行时的六层认知重构
typescript·移动开发·harmonyos·arkts
传奇开心果1 天前
【Compose Multiplatform跨端声明式UI实用技术】第9课:跨平台 ViewModel 与状态架构
android·kotlin