🤔 一个反常识的问题
如果我问你"2024 年之后跨端移动开发的主流方案是什么",十个人里有八个会说 Flutter。毕竟 Google 背书、生态成熟、UI 一致性强,这几乎已经是行业共识。
但我最近遇到一件事让我重新想了想这个问题:一位在大厂做 Android 的朋友,主导把团队的某个核心模块迁到了 KMP(Kotlin Multiplatform Mobile),不是因为 Flutter 不够好,而是因为他说了一句话------
"我不想用另一套语言描述我的业务逻辑。"
这句话戳到我了。Flutter 你写的是 Dart,KMP 你写的是 Kotlin------而 Android 工程师本来就在写 Kotlin。这不只是语言熟悉度的问题,更是一个关于"代码归属感"和"架构融合度"的深层选择。
今天这篇文章,我想从一个 Android 工程师的视角,聊聊 KMP 在真实项目中的落地体验,以及它和 Flutter 之间那些真正重要的差异------不是官方文档上的那种,而是干活时你会遇到的那种。
🧩 KMP 是什么:先搞清楚它在解决什么问题
很多人第一次接触 KMP 会有一个误解:以为它是 Flutter 的竞争对手,能做到"一套代码跑全端 UI"。其实不是,至少不完全是。
KMP 的核心定位是:共享业务逻辑,UI 各平台自己来。
它的架构分层很清晰:
| 层次 | Android | iOS | 共享? |
|---|---|---|---|
| UI 层 | Jetpack Compose / XML | SwiftUI / UIKit | ❌(各自实现) |
| ViewModel / Presenter | Kotlin ViewModel | Kotlin ViewModel(通过 KMP) | ✅ 可以共享 |
| 业务逻辑 / 领域层 | Kotlin 编写,完全共享 | ✅ 完全共享 | |
| 数据层(网络/本地存储) | Ktor + SQLDelight 跨平台 | ✅ 完全共享 |
换句话说,KMP 让你把那些真正费脑子的部分------业务规则、数据处理、状态管理------写一次,然后双端复用。UI 嘛,Android 继续用 Compose,iOS 继续用 SwiftUI,各自顺滑,互不干扰。
而 Compose Multiplatform 是 KMP 的 UI 扩展,让你可以选择把 Compose UI 也共享到 iOS 端。这是个可选项,不是强制的。
🔧 实战落地:一个真实的模块迁移案例
我们来看一个实际场景:把一个"用户积分计算模块"从 Android 原生迁移到 KMP 共享模块,同时对接 iOS 端。
第一步:创建 KMP 共享模块
项目结构大概长这样:
bash
shared/
├── commonMain/
│ └── kotlin/
│ └── com/example/points/
│ ├── PointsRepository.kt # 共享业务逻辑
│ ├── PointsCalculator.kt # 核心计算
│ └── model/
│ └── UserPoints.kt # 数据模型
├── androidMain/
│ └── kotlin/
│ └── com/example/points/
│ └── AndroidPointsDataSource.kt # Android 特有实现
└── iosMain/
└── kotlin/
└── com/example/points/
└── IosPointsDataSource.kt # iOS 特有实现
第二步:在 commonMain 写共享业务逻辑
kotlin
// commonMain - PointsCalculator.kt
class PointsCalculator {
fun calculate(
basePoints: Int,
multiplier: Double,
bonusEvents: List
): PointsResult {
val base = (basePoints * multiplier).toInt()
val bonus = bonusEvents.sumOf { it.points }
val total = base + bonus
return PointsResult(
basePoints = base,
bonusPoints = bonus,
totalPoints = total,
level = determineLevel(total)
)
}
private fun determineLevel(points: Int): UserLevel = when {
points >= 10000 -> UserLevel.PLATINUM
points >= 5000 -> UserLevel.GOLD
points >= 1000 -> UserLevel.SILVER
else -> UserLevel.BRONZE
}
}
data class PointsResult(
val basePoints: Int,
val bonusPoints: Int,
val totalPoints: Int,
val level: UserLevel
)
enum class UserLevel { BRONZE, SILVER, GOLD, PLATINUM }
第三步:处理平台差异------expect/actual 机制
KMP 最核心的跨平台机制就是 expect/actual。在 commonMain 里声明接口期望,各平台提供实现:
kotlin
// commonMain - Platform.kt
expect class PlatformContext
expect fun getPlatformName(): String
expect fun getCurrentTimeMillis(): Long
kotlin
// androidMain - Platform.android.kt
actual class PlatformContext(val context: Context)
actual fun getPlatformName(): String = "Android ${Build.VERSION.SDK_INT}"
actual fun getCurrentTimeMillis(): Long = System.currentTimeMillis()
kotlin
// iosMain - Platform.ios.kt
actual class PlatformContext
actual fun getPlatformName(): String = UIDevice.currentDevice.systemName() +
" " + UIDevice.currentDevice.systemVersion
actual fun getCurrentTimeMillis(): Long =
(NSDate().timeIntervalSince1970 * 1000).toLong()
第四步:网络请求用 Ktor
kotlin
// commonMain - PointsApiClient.kt
class PointsApiClient {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
install(HttpTimeout) {
requestTimeoutMillis = 10_000
}
}
suspend fun fetchUserPoints(userId: String): UserPoints {
return client.get("https://api.example.com/points/$userId").body()
}
suspend fun syncPoints(userId: String, points: PointsResult): Boolean {
val response = client.post("https://api.example.com/points/sync") {
contentType(ContentType.Application.Json)
setBody(SyncRequest(userId, points.totalPoints))
}
return response.status.isSuccess()
}
}
注意:这段代码在 Android 和 iOS 上完全一样,不需要任何修改。Ktor 底层会自动选择对应平台的 HTTP 引擎(Android 用 OkHttp,iOS 用 NSURLSession)。
第五步:iOS 端调用------Kotlin 编译成 Framework
KMP 会把共享模块编译成一个 .xcframework,iOS 侧直接 import 使用:
swift
// iOS Swift 代码
import shared // 导入 KMP 编译的 Framework
class PointsViewController: UIViewController {
private let calculator = PointsCalculator()
private let apiClient = PointsApiClient()
func loadPoints(userId: String) {
// 直接调用 Kotlin 编写的共享代码
Task {
let userPoints = try await apiClient.fetchUserPoints(userId: userId)
let result = calculator.calculate(
basePoints: Int32(userPoints.base),
multiplier: userPoints.multiplier,
bonusEvents: userPoints.events
)
await updateUI(result: result)
}
}
}
iOS 工程师看到这段代码的第一反应通常是:这跟调普通 Swift 库没什么区别。这正是 KMP 的设计目标------对 iOS 侧几乎透明。
⚔️ KMP vs Flutter:真正的核心差异在哪
聊了这么多 KMP 的实现,现在到了最关键的问题:我到底该选哪个?
先上对比表,然后逐条说我的看法:
| 维度 | KMP | Flutter |
|---|---|---|
| 语言 | Kotlin(Android 原生语言) | Dart(专为 Flutter 设计) |
| UI 共享 | 可选(Compose Multiplatform) | 强制(所有 UI 用 Flutter) |
| 原生体验 | 极高(UI 完全原生) | 中等(自绘引擎,非原生组件) |
| iOS 集成 | 编译为 Framework,无缝集成 | 需要 FlutterEngine,有额外开销 |
| 渐进式迁移 | 非常友好(模块级别替换) | 困难(通常需要整体重写) |
| 团队要求 | Android 工程师上手快,iOS 需要 Kotlin 学习 | 全端都需要学 Dart |
| 生态成熟度 | 快速成长,部分库仍在完善 | 非常成熟,pub.dev 库极丰富 |
| 调试工具链 | Android Studio,IDE 支持一流 | VS Code + DevTools,体验良好 |
| 热重载 | Android 侧有,iOS 侧弱 | 全端支持,体验极佳 |
| 公司背景 | JetBrains 主导 | Google 主导 |
光看表格可能还是模糊,我来说几个更有感触的点:
差异一:KMP 不强迫你放弃原生 UI
这是我认为 KMP 最被低估的优势。Flutter 的思路是"我来接管所有 UI",它自己画每一个像素,所以在某些平台上会有"不够原生"的观感------比如 iOS 上 Flutter 的 Material 组件默认不跟 iOS Human Interface Guidelines 走。
KMP 的思路恰恰相反:UI 是你的,逻辑是共享的。你的 iOS 用 SwiftUI,你的 Android 用 Compose,两边都是原生渲染,原生体验。这对于有严苛 UI 质量要求的 App(比如金融、医疗类)非常重要。
差异二:渐进式迁移是 KMP 的杀手锏
现实中很少有团队能从零起步,大多数面临的是"我有个跑了 3 年的 App,怎么引入跨端"。Flutter 基本上是个全或无的选择,混合接入 FlutterEngine 的成本相当高,性能也存在损耗。
KMP 天然支持渐进式:你可以先把网络层迁到 KMP 共享,其他不动;下个季度再把业务逻辑层迁过来;UI 层爱动不动。每一步都是可独立验证的小步骤,风险极低。
差异三:iOS 侧的开发者体验差距正在缩小
KMP 早期被诟病的一点是 iOS 侧体验差------协程暴露给 Swift 的方式比较别扭,编译时间长,错误提示不够友好。但这两年改善明显:
• SKIE(Swift Kotlin Interface Enhancer)让协程、Flow 等可以直接映射到 Swift 的 async/await 和 AsyncSequence
• 编译产物从 .framework 升级为 .xcframework,多架构支持更好
• Xcode 插件开始成熟,KMP 代码在 Xcode 里也能跳转、提示
当然,Flutter 的热重载体验依然是 KMP 短时间内很难超越的------写 UI 时秒级预览,开发效率高很多。
🎯 选型建议:什么团队该选什么
我的选型框架是这样的,可以按顺序问自己三个问题:
• 问题一:你的团队主力是 Android 工程师还是全栈/前端工程师?
如果主力是 Android,KMP 的学习成本几乎为零,Kotlin 就是你们的日常语言;如果主力是前端/全栈,Flutter+Dart 的体系更系统,学习曲线更平滑。
• 问题二:你的 App 对原生 UI 交互要求高不高?
金融 App、医疗 App、与系统深度集成的工具类 App,需要原生交互体验的,优先 KMP;游戏类、内容展示类、UI 高度定制化的,Flutter 的自绘引擎反而是优势。
• 问题三:你是新项目还是老项目改造?
新项目两者都可以,生态成熟度上 Flutter 略占优;老项目改造,KMP 的渐进式迁移能力是明显优势,Flutter 几乎必须重写。
💡 实际落地建议:对于大多数有 Android 背景的团队,我推荐先用 KMP 共享数据层和业务逻辑层(网络、存储、领域模型),UI 层暂时不动。这个改造成本极低,收益立竿见影------iOS 和 Android 的业务 bug 从此只需要修一次。
🚧 KMP 的局限:别被我说得太美好
当然,KMP 不是银弹,有几个真实的坑需要说:
• 编译速度:KMP 项目的增量编译相比纯 Android 项目慢,iOS 端的 framework 编译尤其耗时,CI/CD 需要做好缓存策略
• 多线程模型:iOS 侧的 Kotlin/Native 对多线程有一些限制(Frozen Objects 问题),虽然新版本有所改善,但如果你的共享代码涉及复杂并发逻辑,需要额外注意
• 三方库生态:不是所有 Android 常用库都有 KMP 版本,比如 Room 的 KMP 版本(Room 2.7+)还相对较新,SQLDelight 是目前更稳妥的选择
• iOS 工程师接受度:有些 iOS 工程师对"我的 App 里跑着 Kotlin 代码"这件事心理上有抵触。这不是技术问题,是团队文化问题,需要提前沟通
⚠️ 特别提醒:不要在刚开始就尝试用 Compose Multiplatform 共享 UI 到 iOS,这个特性虽然已经稳定,但生态和工具链还在成长中,坑比较多。先从共享业务逻辑层开始,是最稳妥的路径。
📝 最后说一句
Flutter 和 KMP 不是非此即彼的关系,它们解决的是不同层次的问题。Flutter 更擅长"从零开始构建跨平台 UI",KMP 更擅长"让已有的 Android 逻辑平滑复用到 iOS"。
如果你是 Android 工程师,我真心建议你花一个下午把 KMP 官方的 Getting Started 跑一遍。那种把一段 Kotlin 代码同时跑在 Android 模拟器和 iOS 模拟器上的感觉,还挺上头的。
不一定要在项目里立刻用,但心里有这张牌,下次选型的时候你会更有底气。
本文为原创技术文章,欢迎转载请注明出处。如有问题或建议,欢迎在评论区留言交流。