Kotlin Multiplatform 这几年在移动端圈子里的热度一直没降过,但真正把它用明白的人其实不多。我大概从 2021 年开始在几个中型项目里落地 KMP,从最开始的“试着共享一套逻辑层”到后来整个业务模块下沉到 shared 里,踩了不少坑,也总结了一套自己的方法论。这篇东西不打算讲那些官方文档里已经写烂的基础概念,而是想从“这套东西到底怎么运转的”这个角度切入,再配合我实际项目里的工程配置和设计取舍,给准备上手 KMP 或者已经在用但总觉得别扭的同行一些参考。
不管你是 Android 出身想了解 iOS 侧怎么接,还是 iOS 出身对 KMP 的编译产物心存疑虑,或者是技术负责人正在做跨平台方案选型,这篇文章应该都能给你一些不一样的视角。我会尽量把原理讲得直白,把实践讲得具体,每一个配置和代码片段都来自真实项目,不是从 Demo 里抄出来的。
1. KMP 到底在解决什么问题
1.1 跨平台方案的演进和定位
跨平台开发这个事,业内走了好几条路。最早是 WebView 套壳,后来是 React Native 这种 JavaScript 桥接方案,再后来 Flutter 用自己的渲染引擎把 UI 层也接管了。这些方案有一个共同点:UI 是跨平台的,业务逻辑跟着 UI 框架走,语言的运行时也在各自框架的掌控之中。它们解决了“写一套界面到处跑”的问题,但代价是性能、包体积、原生能力调用链路,以及和系统版本迭代之间的时差。
Kotlin Multiplatform 的定位明显不一样。它不做 UI,不碰渲染。KMP 只负责把业务逻辑层共享出来,UI 还是各自原生实现。也就是说,你在 Android 上依然用 Compose 或者 View 体系画界面,在 iOS 上依然用 SwiftUI 或者 UIKit,但底下的状态管理、网络请求、数据持久化、业务规则这些通通可以下沉到共享模块里。
我个人的感觉是,经过了这几年大厂和中厂的实际验证,KMP 最靠谱的使用方式从来不是“全链路跨平台”,而是“逻辑层跨平台,表现层全原生”。这个定位使得它和 Flutter 这类方案并不是直接竞争关系,而是一个可以共存的互补方案。对于团队里已经有成熟原生工程师、又不想维护两套业务代码的项目来说,KMP 的性价比非常高。
1.2 从源码到二进制:KMP 的编译原理
很多人在接触 KMP 之前会有个疑问:Kotlin 代码怎么在 iOS 上跑的?答案不在 Kotlin 语言本身,而在编译器的目标平台支持。Kotlin 编译器背后是 Kotlin/Native 工具链,它通过 LLVM 将 Kotlin 源码编译成原生机器码,最终以静态库或者 framework 的形式集成进 iOS 工程。
具体到一次构建过程,KMP 会做这样几件事。Android 目标平台走的是 Kotlin/JVM 那套,编译成 .class 再打包进 APK/AAB;iOS 目标平台走的是 Kotlin/Native 那套,编译成 Klib 中间产物,再链接成最终的 framework。Kotlin/Native 之所以能跨平台输出不同架构的产物,靠的是 Kotlin/Native 自带的平台库(Platform Libraries),它把 Objective-C 运行时、Foundation、UIKit 这些 iOS 系统框架的基本接口都映射到了 Kotlin 层,让你在 commonMain 里可以统一调用 expect 声明,再由各平台的 actual 实现去对接系统 API。
还有一个容易被忽视的东西叫 Kotlin/Native 并发模型。KMP 的共享代码在 iOS 端运行时,内存管理走的是引用计数而不是 JVM 的 GC,这意味着你在 commonMain 里写并发代码时必须考虑到 iOS 端的限制。最初的版本是彻底的线程隔离,数据跨线程需要 freeze;后来引入了新内存管理器,默认开启,在并发模型上和 JVM 更接近了,但在一些边界场景下仍然不能完全照搬 Android 端写法。这块后面我会单独拎出来细说。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 工程结构怎么搭才对
2.1 source set 的层级关系
KMP 的工程结构初看会有点懵,因为它的源码目录不是 Android 里的单一 app/src/main 那种平铺,而是按 source set 来组织的。一个典型的 KMP 模块会包含 commonMain、androidMain、iosMain 这样几个 source set,如果你还需要支持 watchOS、macOS 之类的目标,还会有对应的独立目录。
层级关系是这样的:commonMain 是所有平台共享的代码,在它下面不能直接调用任何平台 API,只能调用 Kotlin 标准库和第三方库提供的跨平台接口;androidMain 里放 Android 专属实现,可以使用 Android SDK 和 Java/Kotlin JVM 生态的库;iosMain 里放 iOS 专属实现,可以直接调用 Foundation、UIKit,甚至可以通过 cinterop 对接 Objective-C 库。
实际工程里,source set 与 source set 之间还存在中间层。比如你如果开了 iosArm64、iosSimulatorArm64、iosX64 这三个目标,Kotlin 会帮你抽象出一个 iosMain 中间层,把三个架构共用的代码放到这里,而每个架构特有的配置放在各自的 source set 里。这一点很关键,因为大部分 iOS 平台代码其实不存在架构差异,你没必要在三个目录里各写一遍。
我从工程上给个建议:source set 结构的核心原则是“能上提的上提,能下沉的下沉”。上提是指尽量把逻辑代码写到 commonMain,下沉是指实在避免不了平台差异时,把差异封装到最小的 actual 实现里。如果 commonMain 和两端的 actual 代码比例能达到 8:2 左右,这个项目的共享收益率就已经非常舒服了。
2.2 Kotlin Multiplatform Wizard 创建项目
现在创建一个 KMP 工程,最推荐的方式是直接使用 JetBrains 官方的 Kotlin Multiplatform Wizard 向导,地址是 kmp.jetbrains.com。这个向导会生成一个包含 androidApp、iosApp 和 shared 三部分的标准工程。androidApp 是一个正常的 Android 应用模块,iosApp 是一个 Xcode 工程,而 shared 就是我们要写的 KMP 模块。
用向导生成的好处在于,它的模板已经处理好了 Android 和 iOS 两端的接入配置,包括 Gradle 脚本、Xcode 构建脚本、embedAndSign 逻辑等。你要是从零开始手写这些配置,大概率会被各种版本兼容问题折磨一遍。我个人建议是第一次上手用向导生成的模板跑通整个链路,然后再逐步改造适合自己的结构。
生成完成后,第一步不要急着改代码,先在 Android 端跑一次,再在 iOS 端跑一次,确认两端都能正常调起模板里默认的“Hello World”函数。这步走通说明基础环境没问题,后续的改动都是增量式的,排查起来也更容易。不少新手第一步就把工程结构大改一通,最后分不清是环境问题还是代码问题,这个节奏非常不推荐。
2.3 build.gradle.kts 里那些关键配置
KMP 模块的 Gradle
