Kotlin Multiplatform

Kotlin Multiplatform,简称 KMP,用于在多个平台之间共享 Kotlin 代码。它的核心目标不是“一套代码写所有 UI”,而是把值得共享的业务逻辑、数据模型、网络协议、校验规则、数据库访问抽象等放到公共模块里,同时允许每个平台保留自己的原生能力和用户体验。

适合共享什么

通常适合共享:

  • DTO 和领域模型。
  • 表单校验、权限规则、价格计算等业务逻辑。
  • REST、GraphQL、RPC 等 API 客户端抽象。
  • 本地缓存、序列化、加密、日志等跨平台基础能力。
  • ViewModel 或 presentation logic,取决于团队架构。

通常不强求共享:

  • 深度平台相关 UI。
  • Android/iOS 生命周期细节。
  • 平台 SDK 强绑定能力。
  • 需要平台团队分别优化的体验。

Java 对比:传统 Java 跨平台更多依赖 JVM。KMP 的重点不是把 JVM 带到所有平台,而是把 Kotlin 编译到 JVM、Android、iOS、JS、Native、Wasm 等不同目标。

基本项目结构

KMP 项目通常通过 source set 组织代码:

shared/
  src/
    commonMain/
    commonTest/
    androidMain/
    iosMain/
    jsMain/
  • commonMain:所有目标共享的生产代码。
  • commonTest:共享测试。
  • androidMain:Android/JVM 相关代码。
  • iosMain:iOS 相关代码。
  • jsMain:JavaScript 相关代码。

不是每个项目都需要所有 source set。只做 Android+iOS 共享时,通常只需要 commonMainandroidMainiosMain

commonMain 的边界

commonMain 只能使用公共 Kotlin API 和声明为 multiplatform 的库。你不能在 commonMain 里直接使用:

  • java.io.File
  • Android Context
  • iOS NSUserDefaults
  • 浏览器 window

这些 API 都属于具体平台。共享代码想访问平台能力时,应该通过抽象或 expect/actual

expect/actual

expect/actual 用于在公共代码声明一个“平台会提供的能力”。

公共代码:

// commonMain
expect fun currentTimeMillis(): Long

JVM 实现:

// jvmMain
actual fun currentTimeMillis(): Long =
    System.currentTimeMillis()

使用原则:

  • expect 描述能力,不暴露平台细节。
  • actual 负责调用平台 API。
  • 不要把大量平台分支塞进 common 逻辑里。

依赖管理

依赖也按 source set 声明:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0")
        }
        androidMain.dependencies {
            implementation("androidx.core:core-ktx:...")
        }
    }
}

只有支持对应目标的库才能放进对应 source set。一个 JVM-only 库不能放进 commonMain

平台目标

常见目标包括:

  • Android/JVM。
  • iOS。
  • macOS、Linux、Windows 等 Native。
  • JavaScript。
  • WebAssembly。

目标越多,公共 API 设计越要保守。比如文件系统、线程模型、时间 API、加密库、网络栈在不同平台差异很大。

测试策略

共享逻辑应尽量放在 commonTest 中测试:

src/commonTest/kotlin/PriceCalculatorTest.kt

平台行为需要平台测试:

  • Android instrumented test。
  • iOS 测试。
  • JS 测试。
  • Native 测试。

不要只测 JVM 目标就宣称多平台逻辑安全。不同平台的时间、浮点、并发、文件和编码行为可能存在细节差异。

与 Java 项目的关系

KMP 不等于 Java 模块化,也不等于 Maven 多模块。

Java 多模块通常仍在 JVM 世界里复用代码。KMP 的 source set 会编译到不同平台产物,例如:

  • JVM bytecode。
  • Android artifact。
  • iOS framework。
  • JavaScript bundle/module。
  • Native binary/library。

如果团队主要是后端 Java/Kotlin,可能不需要 KMP;普通 JVM 多模块就足够。只有当业务确实需要跨 Android、iOS、Web、Native 等共享逻辑时,KMP 才值得引入。

常见误区

误区一:所有代码都应该共享

共享是有成本的。平台差异越大,强行共享越容易让公共层变成抽象垃圾桶。

误区二:KMP 会消灭平台工程师

KMP 更现实的价值是减少重复业务逻辑。平台 UI、性能调优、平台 SDK 集成仍需要平台经验。

误区三:commonMain 可以随意调用 Java 库

不可以。commonMain 不是 JVM。只有 JVM/Android source set 才能使用 JVM-only 库。

误区四:expect/actual 是跨平台 if-else

expect/actual 应该用于少量平台能力抽象。如果到处都是 expect/actual,说明公共模型可能设计得太贴近平台实现。

实践建议

  • 先共享稳定业务逻辑,不要一开始就共享 UI。
  • 让 common API 保持小而清晰。
  • 平台能力通过接口或 expect/actual 隔离。
  • 依赖库进入 commonMain 前确认它支持所有目标。
  • 每个目标都要有基本构建和测试验证。
  • Java/JVM 团队引入 KMP 前,先确认真的有非 JVM 目标需求。

参考

  • 官方 Kotlin Multiplatform:https://kotlinlang.org/docs/multiplatform.html
  • 官方入门:https://kotlinlang.org/docs/multiplatform/get-started.html