别再说 Flutter 是唯一选择了——KMP 正在悄悄抢走它的地盘

🤔 一个反常识的问题

如果我问你"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 模拟器上的感觉,还挺上头的。

不一定要在项目里立刻用,但心里有这张牌,下次选型的时候你会更有底气。


本文为原创技术文章,欢迎转载请注明出处。如有问题或建议,欢迎在评论区留言交流。

相关推荐
三少爷的鞋2 小时前
2026 已过 1/3:事豫则立,不预则废——关于架构、协程与边界的思考
android
冬奇Lab2 小时前
Android 15 音频子系统(八):Audio HAL 与硬件接口——音频数据的最后一公里
android·音视频开发·源码阅读
黄林晴5 小时前
Compose Multiplatform 1.10 发布:里程碑式更新!
android
流星白龙5 小时前
【MySQL】19.MySQL用户管理
android·mysql·adb
匆忙拥挤repeat6 小时前
Android Compose 可组合项的生命周期、副作用API
android
hnlgzb7 小时前
目前编写安卓app的话有哪几种设计模式?
android·设计模式·kotlin·android jetpack·compose
studyForMokey8 小时前
【Android面试】Fragment生命周期专题
android·microsoft·面试
Android系统攻城狮9 小时前
Android tinyalsa深度解析之pcm_plugin_open调用流程与实战(一百七十四)
android·pcm·tinyalsa·音频进阶手册
用户622386252179 小时前
Android 列表控件实战:从 ListView 到 RecyclerView,仿今日头条 HeadLine 项目全解析
android