Kotlin Multiplatform 稳定版发布,生态还差什么

Kotlin Multiplatform 稳定版发布,生态还差什么

Kotlin Multiplatform 稳定版发布,生态还差什么


Kotlin Multiplatform 稳定版发布,生态还差什么


KotlinConf 2023 的 Keynote 上,JetBrains 官宣 Kotlin Multiplatform(KMP)进入 Stable 状态。台下掌声热烈,直播弹幕里一片「终于」。但回到 IDE 里真正去升级 kotlin("multiplatform") 插件的人,很多在第二天就遇到了 Gradle sync 报错——Android Gradle Plugin 8.2 不兼容 Kotlin 1.9.20 的某个内部 API,或者 iOS target 的 linkDebugFrameworkIosSimulatorArm64 任务莫名其妙地 OOM。这种反差构成了 KMP 生态最真实的用户体验:名义上毕业了,实际上 syllabus 里还有大半课程没修完。


"Stable" 的保质期似乎只限于 commonMain


JetBrains 对 Stable 的定义是「可以安全地用于生产环境共享业务逻辑」。这句话的定语很关键。它实际上只保证了你在 commonMain 里写的 Kotlin 代码,在 JVM、Native、JS 三个后端上的语言语义一致性。一旦你开始写 expect 声明,或者试图调用平台特定 API,那个「Stable」的标签就迅速褪色。


expect/actual 机制从 1.9.20 开始被标记为 Stable,但语法稳定不等于好用。在 KMP 项目里,你经常要为了一件在 Android 上三行代码搞定的事,在 iOS target 上写二十行 actual 实现,再配上一段 iosMain 里的 platform-specific 胶水。比如读取一个本地配置文件,Android 侧用 Context.assets 很直接,iOS 侧你要桥接 NSBundle,而 Desktop 侧可能得用 java.io.File 或者自己封一套 Okio。这些不是边缘场景,是每一个试图共享非业务逻辑代码的开发者都会踩的地雷。JetBrains 的解决方案是建议你把所有平台差异都「下沉」到 commonMain 的接口背后,但这本质上是在要求开发者自己建立一个薄弱的抽象层——而这个抽象层的维护成本,往往抵消了代码共享带来的收益。


更隐蔽的问题在于 Kotlin/Native 的遗留惯性。虽然 Kotlin 1.7.20 就已经把新内存模型设为默认,解决了老版本里 freezeAtomicReference 那些让人抓狂的并发限制,但社区里大量的第三方 Native 库,尤其是 iOS 生态里通过 CocoaPods 导入的 Obj-C 库,仍然在新老内存模型的边界上行为诡异。你按照官方文档写了一段线程安全的代码,在单元测试里跑得飞起,打包成 iOS Framework 后莫名其妙地冻结对象,这种 debug 经历在 KMP 社区论坛和 Slack 里至今仍然高频出现。JetBrains 说语言特性 Stable 了,但没说跟 iOS 原生生态的互操作不会再让你熬夜。


Gradle 构建脚本仍是最大的单一故障点


如果你统计 KMP 开发者在 Stack Overflow 上提问的标签,Gradle 相关的问题数量一定超过语言本身。Kotlin Multiplatform Gradle Plugin 是 KMP 体验的核心枢纽,也是最大的单点故障源。Kotlin 2.0.0 在 2024 年 5 月发布时,强制要求 Gradle 版本至少为 8.5。这个数字对很多 Android 团队来说并不轻松:他们的 CI 流水线可能还停留在 7.6,因为某个老旧的 Gradle Enterprise 插件或者内网镜像配置没有升级。于是为了试用 K2 编译器,你必须先解决 Gradle 的升级迁移,而 Gradle 升级往往意味着整个构建脚本的重新验证。


KMP 的构建复杂度是指数级的。一个同时支持 Android、iOS、Desktop 和 JS 的 KMP 项目,其 build.gradle.kts 里往往充斥着 kotlin {} 块、android {} 块、cocoapods {} 块,以及各种 sourceSets 的精妙嵌套。JetBrains 和 Google 分别维护两套并不完全一致的 DSL 惯例,当你试图把 Android 的 buildTypes 与 KMP 的 targets 对齐时,那种无力感很难用「技术债务」轻描淡写地带过。更糟的是 configuration cache。KMP plugin 对 configuration cache 的支持在 Kotlin 1.9.x 时代一直磕磕绊绊,开发者打开项目时看着 Gradle sync 跑满两分钟是常态。Kotlin 2.0 的 K2 编译器确实显著提升了编译速度,但很多开发者发现省下的时间全赔进了 Gradle 的 configuration phase 里。


CocoaPods 集成是另一个重灾区。kotlin("native.cocoapods") 插件的理念听起来很美好:直接在 Kotlin 脚本里声明 iOS 依赖,自动生成 podspec。现实是,每次 Xcode 升级——比如从 15.0 到 15.3——都可能破坏掉 Kotlin/Native 与 CocoaPods 的集成路径。你执行 ./gradlew podInstall,得到的可能是 Framework 'Foo' not found 这种极其 Native 的错误,而排查过程需要你同时理解 Xcode 的 build settings、Gradle 的 task graph,以及 Kotlin/Native 的 cinterop 工具链。一个号称跨平台共享代码的框架,在 iOS 侧对 Apple 原生工具链的依赖反而比 Swift Package Manager 还要深,这种讽刺感只有踩过坑的人才懂。


第三方库的生态真空比想象中严重


KMP 的 Stable 发布有一个隐含承诺:「生态已经成熟到可以吸引主流库迁移。」但真实情况是,除了 JetBrains 自家维护的 kotlinx 系列(coroutines、serialization、datetime),大部分 Android 开发者习以为常的基础设施在 KMP 上要么不存在,要么处于二等公民状态。


OkHttp 没有官方 KMP 版本。虽然可以换用 Ktor client,但 Ktor 的 engine 配置在不同平台差异巨大:Android 上默认是 OkHttp,iOS 上是 NSURLSession,Desktop 上可能落到 CIO 或者 Java HTTP client。一个统一的 HTTP 客户端接口背后,是四套完全不同的行为细节和 bug 模式。Retrofit 更是遥遥无期,Jake Wharton 曾经表示过对 KMP 的兴趣,但 Square 官方没有资源投入,社区里的替代方案如 Ktorfit 或者 decompose 里的网络层,又远未达到 Retrofit 在 Android 领域的统治级成熟度。


本地存储同样尴尬。SQLDelight 2.0 确实做了出色的 KMP 支持,它的 SQL 代码生成和跨平台驱动抽象做得相当扎实。但当你真的想在 iOS 上跑起来时,你会发现还得自己处理 SQLite 驱动的链接问题,而且 SQLDelight 的 Gradle plugin 与 KMP plugin 的版本兼容性矩阵并不比 Android Gradle Plugin 宽松多少。Realm 在 2021 年高调发布了 Kotlin Multiplatform SDK,但后续迭代速度和社区信心都成问题,MongoDB 对 Realm 的战略调整让很多 KMP 开发者不敢把核心数据层押在上面。


kotlinx-datetime 是官方提供的日期时间库,但功能相当基础。没有内建的时区数据库,没有复杂的日历计算,没有格式化器的统一抽象。大多数项目最后要么引入 OkIO 的 Clock 概念自己封装,要么在 iOS 侧 fallback 到 NSCalendar,Android 侧继续用 java.time。这种「common 层用一个折中子集,各平台再补漏洞」的开发模式,与 KMP 宣传的统一代码库愿景相去甚远。当一个平台声称自己 Stable,但连日期处理这种基础需求都得让开发者自己拼积木时,它的生态完成度显然被高估了。


Compose Multiplatform 的跨平台承诺有多重


很多人把 KMP 的 Stable 和 Compose Multiplatform(CMP)混为一谈。JetBrains 自己也在宣传时有意无意地模糊这条边界——毕竟纯 KMP 共享逻辑的故事不够性感,「一套 Compose UI 跑全平台」才是能打动决策者的 pitch。但 CMP 的现状,恰恰是 KMP 生态里最不 Stable 的部分。


Compose Multiplatform for iOS 在 2023 年底进入 Beta,2024 年随着 Compose 1.6.x

Intent 的 FLAG_ACTIVITY_CLEAR_TOP 与 singleTask,任务栈清理的坑 2026-08-19
Scrcpy 的投屏控制,开发者调试神器 2026-08-19

评论区