Android KMP(Kotlin Multiplatform)
由浅入深,从基本概念到源码原理,再到实战示例与生产级应用案例,系统梳理 Kotlin 跨平台开发之道
一、基础概念
1.1 什么是 Kotlin Multiplatform(KMP)?
Kotlin Multiplatform(又称 KMP 或 KMM)是 JetBrains 推出的跨平台代码共享方案,其核心理念是:共享业务逻辑,保留原生 UI。
1 | ┌─────────────────────────────────────────────────────────────────────────┐ |
与 Flutter、React Native 等「一套 UI 到处跑」的方案不同,KMP 让你:
- 用 Kotlin 写共享的业务逻辑、网络、数据层
- 用 Jetpack Compose 写 Android UI,用 SwiftUI 写 iOS UI
- 得到的是 原生体验,而非 WebView 或自绘引擎
1.2 为什么需要 KMP?
| 痛点 | KMP 的解决方式 |
|---|---|
| Android / iOS 重复实现业务逻辑 | 共享 ViewModel、UseCase、Repository 等 |
| 双端行为不一致(如计算逻辑) | 同一套 Kotlin 代码,编译到各平台 |
| 双端各自维护一套网络/序列化代码 | 共享 Ktor + kotlinx.serialization |
| 希望跨平台但不牺牲原生体验 | 保留原生 UI 框架 |
| 团队已有 Kotlin 基础 | 复用技能栈,降低学习成本 |
1.3 KMP 与主流跨平台方案对比
| 维度 | KMP | Flutter | React Native |
|---|---|---|---|
| UI 方案 | 原生 UI(Compose / SwiftUI) | 自绘引擎(Skia) | 原生组件 + Bridge |
| 共享范围 | 业务逻辑、网络、数据 | UI + 逻辑全部共享 | UI + 逻辑共享 |
| 性能 | 接近原生 | 接近原生 | 依赖 JS Bridge |
| 包体积 | 共享库较小 | 需携带 Flutter 引擎 | 需携带 RN 运行时 |
| 生态 | Kotlin 生态、官方支持 | Dart/Flutter 生态 | JS/React 生态 |
| 学习曲线 | Kotlin 开发者友好 | 需学 Dart | 需学 React/JS |
适用场景:KMP 更适合「业务逻辑复杂、希望 UI 保持原生」的应用,如金融、电商、工具类 App。
二、核心原理
2.1 编译模型
KMP 将 Kotlin 代码编译成各平台的原生产物,而不是通过虚拟机或解释器运行:
1 | ┌──────────────────┐ |
- Android:Kotlin → JVM 字节码 → DEX → APK
- iOS:Kotlin → Kotlin/Native(LLVM)→ 静态库/框架
- Web:Kotlin → JavaScript 或 WebAssembly
因此,共享代码在运行时就是本地代码,无额外解释层。
2.2 expect / actual 机制
当共享代码需要调用平台特有 API 时(如文件系统、UUID、日期格式化),KMP 使用 expect / actual 做编译期抽象:
在 commonMain 中声明 expect(契约):
1 | // commonMain/kotlin/platform/Platform.kt |
在各平台提供 actual 实现:
1 | // androidMain/kotlin/platform/Platform.android.kt |
1 | // iosMain/kotlin/platform/Platform.ios.kt |
原理要点:
- 编译时,编译器将
expect与对应平台的actual匹配 - 每个目标平台都必须有且仅有一个
actual实现 - 保证 common 代码只能依赖抽象,无法引用平台专属 API
2.3 Source Sets(源集)与层级结构
KMP 用 Source Set 组织代码,每个源集对应一组目标平台:
1 | ┌─────────────────────────────────────────────────────────────────┐ |
依赖方向:androidMain → commonMain,iosMain → commonMain,commonMain 不能依赖平台源集。
典型目录结构:
1 | shared/ |
三、源码与实现原理
3.1 expect / actual 的编译期处理
expect/actual 并非运行时多态,而是编译期替换:
- 在 common 编译时,
expect仅作为「占位声明」参与类型检查 - 在平台编译时,编译器用对应平台的
actual替换expect - 最终产物中只存在
actual实现,无额外抽象开销
这保证了共享代码在目标平台上等价于「直接调用平台 API」。
3.2 中间源集(Hierarchical Source Sets)
当多个平台共享同一套「非 common」逻辑时,可引入中间源集,避免重复:
1 | // 声明目标 |
Kotlin 插件会自动生成:
appleMain:所有 Apple 平台共享(iOS + macOS + watchOS + tvOS)iosMain:iOS 真机 + 模拟器共享
在 appleMain 中可以使用 Apple 专属 API(如 platform.Foundation.NSUUID),而无需在 iosArm64Main、iosSimulatorArm64Main 里各写一遍。
3.3 依赖解析规则
1 | commonMain 只能依赖: |
四、实战示例
4.1 项目搭建
根 build.gradle.kts:
1 | plugins { |
shared/build.gradle.kts(共享模块):
1 | plugins { |
4.2 网络层:Ktor + kotlinx.serialization
commonMain - 数据模型与 API:
1 | // commonMain/kotlin/data/model/User.kt |
1 | // commonMain/kotlin/data/remote/UserApi.kt |
commonMain - Ktor 客户端工厂:
1 | // commonMain/kotlin/di/NetworkModule.kt |
androidMain - OkHttp 引擎:
1 | // androidMain/kotlin/di/NetworkModule.android.kt |
iosMain - Darwin 引擎:
1 | // iosMain/kotlin/di/NetworkModule.ios.kt |
4.3 ViewModel 共享
1 | // commonMain/kotlin/presentation/UserViewModel.kt |
- Android:注入
viewModelScope,通过viewModel()获取 - iOS:注入
MainScope()或ViewModelScope,通过 KMP 的StateFlow订阅
两端共享同一套 ViewModel 逻辑,仅 CoroutineScope 由各平台注入。
4.4 expect/actual 实用示例:日期格式化
1 | // commonMain |
五、实际项目应用案例
5.1 Cash App(Block 旗下金融应用)
| 项目规模 | 50+ 移动工程师,约 3000 万月活 |
|---|---|
| 策略 | 业务共享、UI 原生 |
| 时间线 | 自 2018 年起试验 KMP,逐步通过 Feature Flag 落地 |
| 效果 | 移除有问题的 JavaScript 共享代码;双端维护单一业务逻辑库;服务器团队(Kotlin)可直接参与共享库开发 |
| 使用库 | SQLDelight、Wire、CrashKiOS |
启示:大型金融场景下,KMP 在「持久化」和「纯函数业务逻辑」上收益最大,且便于服务端参与共享代码。
5.2 Netflix
- 在移动工作室 App 中共享逻辑,减少重复开发
- 在影视制作的高节奏迭代下,提升交付效率与稳定性
5.3 McDonald’s
- 共享应用内支付等复杂业务逻辑
- 支撑每月 650 万+ 笔支付
- 验证 KMP 在电商/支付场景的可行性
5.4 Forbes
- 在 iOS 与 Android 间共享 80%+ 逻辑
- 双端可同步上线新功能,缩短发布周期
5.5 其他采用者
- Wrike、Bilibili、Feres 等使用 KMP + Compose Multiplatform
- Vouched、Karma 等采用 KMP 共享核心业务
六、最佳实践与注意事项
6.1 共享范围建议
| 适合共享 | 不建议共享 |
|---|---|
| 数据模型、DTO | 平台特有 UI 组件 |
| 网络请求、序列化 | 复杂平台动画、手势 |
| Repository、UseCase、ViewModel | 平台特定系统 API 封装 |
| 业务规则、校验逻辑 | 第三方 SDK 深度集成 |
| 本地存储(SQLDelight、DataStore) | 推送、支付等强平台相关逻辑 |
6.2 常见陷阱
- 在 commonMain 中引用平台 API:编译器会报错,应使用 expect/actual 抽象
- expect/actual 签名不一致:必须保持完全一致(包名、函数名、参数、返回值)
- 并发与线程:Kotlin/Native 对线程模型有约束,需注意
@ThreadLocal、freeze等 - 依赖版本:多平台库需保证各目标使用兼容版本
6.3 学习路线建议
- 搭建最小 KMP 工程,跑通 Android + iOS
- 用 expect/actual 封装 1~2 个平台 API
- 接入 Ktor + kotlinx.serialization 实现共享网络层
- 共享一个 ViewModel,在双端展示数据
- 按业务模块逐步迁移,避免一次性大改
七、小结
KMP 以 「共享逻辑、原生 UI」 为核心,通过 expect/actual 和 Source Sets 实现跨平台抽象,在不牺牲原生体验的前提下显著降低双端重复开发。从 Cash App、Netflix、McDonald’s 等实践来看,KMP 已可用于生产环境,特别适合业务逻辑复杂、对一致性与性能要求较高的应用。结合 Jetpack Compose 与 SwiftUI,KMP 正成为 Android 开发者拓展 iOS 能力的重要路径。