前十九课系统覆盖了CMP从环境搭建到部署发布的完整技术栈。本课作为系列的收官之作,将以JetBrains官方示例和社区标杆项目为样本,逐层拆解CMP在真实项目中的架构决策、平台互操作策略与工程化实践。本课内容基于CMP 1.12.1与Kotlin 2.3.20。
目录
- 一、JetBrains官方示例全景导览
- 二、深度拆解:Imageviewer的架构与平台互操作
- 三、深度拆解:Chat的混合UI策略与Codeviewer的代码编辑器
- [3.1 Chat:CMP与SwiftUI互嵌](#3.1 Chat:CMP与SwiftUI互嵌)
- [3.2 Codeviewer:代码编辑器UI与文件树导航](#3.2 Codeviewer:代码编辑器UI与文件树导航)
- [四、深度拆解:Nav Cupcake的导航架构](#四、深度拆解:Nav Cupcake的导航架构)
- 五、深度拆解:Graphics2D的跨平台渲染
- [六、深度拆解:Issues tracker的自适应UI与网络集成](#六、深度拆解:Issues tracker的自适应UI与网络集成)
- 七、社区标杆:kmp-clean-architecture的模块化实践
- 八、社区标杆:kmp-mvi-compose-architecture-sample的MVI落地
- 九、生产级实践:支付宝MYKMP的工业实践
- [9.1 三端共享架构](#9.1 三端共享架构)
- [9.2 编译目标与互操作](#9.2 编译目标与互操作)
- [9.3 产物裁剪与性能优化](#9.3 产物裁剪与性能优化)
- [9.4 国内KMP实践全景](#9.4 国内KMP实践全景)
- [十、官方生产级示例:KMP RSS Reader](#十、官方生产级示例:KMP RSS Reader)
- 十一、示例横向对比与方法论提炼
- 配套习题(15题)
- 本课知识点总结
文末附有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官方更新和社区动态,将本系列的知识框架与最新的技术演进结合起来。