KMP 跨平台共享代码,现在能共享多少
Kotlin Multiplatform 跨平台共享代码,现在能共享多少
Kotlin 2.0.0 在 2024 年 5 月发布之后,JetBrains 给 KMP 贴上了 Stable 的标签。从 KMM 改名到 KMP,从 Beta 到正式版,从"只能共享业务逻辑"到 Compose Multiplatform 宣布支持 iOS,舆论场上的气氛一度让人产生一种错觉:似乎明天就能用一套 Kotlin 代码把 Android、iOS、桌面甚至 Web 全数吃下,然后理直气壮地跟老板汇报"我们砍掉了 50% 的开发人力"。
但标签这东西,贴在 IDE 插件上和贴在生产环境里的意义完全不同。真把 KMP 拉到真实项目里跑过完整交付周期的团队,心里大概都清楚一个事实:shared module 里的代码确实在增长,但增速和 PPT 里画的弧线完全不是一回事。那些宣传材料里动辄 70% 共享率的算法,通常只统计了 commonMain 里的行数,却自动忽略了 expect/actual 的胶水成本、iOS 桥接层的调试开销,以及 Compose Multiplatform 在 iOS 设备上偶尔露出的马脚。KMP 现在到底能共享多少?这个问题得拆开几层皮来看。
共享率的统计游戏:PPT 数字 vs 仓库真相
早期很多 KMP 布道文章喜欢算共享率。网络层用 Ktor、序列化用 kotlinx.serialization、数据库用 SQLDelight,这一套组合下来,PPT 上很容易算出"核心业务逻辑 70% 共享"的漂亮数字。但这个算法本身带有强烈的欺骗性。它通常只统计 commonMain source set 里的 Kotlin 文件行数,然后把两个平台各自独有的 UI 层排除在外。这种统计方式隐含了一个不太诚实的假设:平台差异代码不算成本。
expect/actual 机制是 KMP 的基石,也是共享率注水的重灾区。你在 common 里定义一个 expect class PlatformHttpClient,然后在 androidMain 里 actual 一个基于 OkHttp 的实现,在 iosMain 里 actual 一个基于 NSURLSession 的包装。表面上看,common 代码占了一大半,但实际上平台相关代码只是被体面地隐藏在了 androidMain 和 iosMain 两个目录里。当业务需要接入某个仅 Android 或仅 iOS 的第三方 SDK 时,比如 Android 的 Play Billing 或者 iOS 的 StoreKit,你不得不在 common 里定义接口,两边各自写适配层。这种胶水代码不写不行,写了又不产生直接业务价值,但共享率的统计公式里通常把它们算进了"平台自有代码",于是 common 的百分比就虚高了。
更隐蔽的成本在于 Gradle 构建本身。引入 Kotlin/Native 工具链后,Gradle sync 时间会以肉眼可见的速度上涨。纯 Android 项目里几秒钟能完成的配置阶段,在 KMP 项目里因为要多解析 iosArm64、iosSimulatorArm64、iosX64 等多个 target,经常拖到半分钟以上。CI 环境如果是 Linux 容器,还得额外配置 macOS runner 来编译 iOS 产物。这些基础设施开销不会出现在"共享率"的计算公式里,但它们是每天消耗团队精力的黑洞。
网络与存储:确实能共享,但省下的没有你想象的多
KMP 生态里最能打的基础设施库,无非就是 Ktor、SQLDelight 和 kotlinx.serialization。Ktor 2.x 对 Darwin 引擎的支持已经相当成熟,SQLDelight 2.0 也正式提供了