29、Gradle与Kotlin多平台:Kotlin Multiplatform插件、源码集配置、平台目标配置
Kotlin Multiplatform(简称 KMP)是 JetBrains 推出的跨平台方案。它允许你用一套 Kotlin 代码,同时编译到 JVM、Android、iOS、JS、Native 等多个平台。说白了,就是「一次编写,到处运行」的 Kotlin 版本。
我最早接触 KMP 是在一个共享业务逻辑的项目里。当时团队既要写 Android 又要写 iOS,两边的数据模型和网络层几乎一模一样。我们试着用 KMP 把公共代码抽出来,效果出奇的好。嗯,这里要注意,KMP 不是要取代原生 UI,它主要负责业务逻辑层和数据层的复用。
Kotlin Multiplatform 插件
要在 Gradle 里启用 KMP,第一步就是应用插件。核心插件只有一个:org.jetbrains.kotlin.multiplatform。
plugins {
kotlin("multiplatform") version "1.9.22"
}
这个插件会帮你做几件事:
- 注册 KMP 相关的 DSL 扩展(比如
kotlin {}块) - 自动配置源码集(source set)的依赖关系
- 根据你声明的平台目标,生成对应的编译任务
我个人习惯在 settings.gradle.kts 里统一管理插件版本,这样多模块项目不会乱。
平台目标配置
平台目标(target)就是你要编译到哪些平台。每个目标对应一个编译产物,比如 JVM 的 jar、iOS 的 framework、JS 的 bundle。
配置方式很简单,在 kotlin {} 块里声明即可:
kotlin {
jvm() // JVM 目标
js(IR) { // JS 目标,使用 IR 编译器
browser()
nodejs()
}
iosX64() // iOS 模拟器(x86)
iosArm64() // iOS 真机(ARM)
iosSimulatorArm64() // iOS 模拟器(Apple Silicon)
linuxX64() // Linux 桌面
macosX64() // macOS 桌面
mingwX64() // Windows 桌面
}
每个目标还可以配置额外的参数。比如 JS 目标可以指定模块类型:
js(IR) {
moduleName = "my-lib"
binaries.executable()
browser {
webpackTask {
outputFileName = "my-lib.js"
}
}
}
我曾经在配置 iOS 目标时踩过一个坑:如果你同时声明了 iosX64 和 iosArm64,但忘了加 iosSimulatorArm64,那在 Apple Silicon 的 Mac 上跑模拟器就会报错。嗯,现在新版的 KMP 插件已经默认包含这三个了,但旧版本需要手动加。
源码集配置
源码集(source set)是 KMP 的核心概念。它把代码按「共享范围」分成几层:
- commonMain / commonTest:所有平台共享的代码
- jvmMain / jvmTest:仅 JVM 平台使用的代码
- jsMain / jsTest:仅 JS 平台使用的代码
- iosMain / iosTest:所有 iOS 变体共享的代码
- iosX64Main / iosX64Test:仅某个具体 iOS 变体使用的代码
默认情况下,KMP 插件会自动生成这些源码集。你只需要在 src/ 目录下创建对应的文件夹即可:
src/
├── commonMain/
│ └── kotlin/
│ └── com/example/
│ └── Platform.kt
├── jvmMain/
│ └── kotlin/
│ └── com/example/
│ └── Platform.jvm.kt
├── iosMain/
│ └── kotlin/
│ └── com/example/
│ └── Platform.ios.kt
└── jsMain/
└── kotlin/
└── com/example/
└── Platform.js.kt
你想想看,这种结构天然就支持「共享逻辑 + 平台适配」的模式。你在 commonMain 里写接口,在 jvmMain 和 iosMain 里分别提供实现。
依赖管理
KMP 的依赖配置也分平台:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
jvmMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.3")
}
iosMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
}
}
注意,kotlinx-coroutines-core 在 commonMain 和 iosMain 里都出现了。这是因为 iOS 目标没有独立的协程库,直接用 core 就行。而 JVM 有专门的 JDK 8 适配版本。
我记得有一次,团队里有人把 kotlinx-serialization-json 只加在了 jvmMain 里,结果 iOS 编译时找不到 JSON 解析器。排查了半天才发现是依赖范围配错了。所以我的建议是:能用 commonMain 的依赖,尽量放在 commonMain 里。
知识体系结构图
实际项目中的配置模板
下面是一个比较完整的 build.gradle.kts 示例,涵盖了常见的平台目标和源码集配置:
plugins {
kotlin("multiplatform") version "1.9.22"
id("com.android.library") version "8.1.0"
kotlin("plugin.serialization") version "1.9.22"
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
jvm()
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "shared"
isStatic = true
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
}
val androidMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
}
}
val jvmMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.3")
}
}
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
}
}
}
androidMain 里不要使用 JVM 专属的 API(比如 java.io)。Android 虽然也是 JVM 系,但 API 并不完全一致。我曾经因为用了 java.nio.file.Path 导致 Android 编译失败,后来改成了 java.io.File 才解决。
常见问题与避坑
- iOS 框架打包:如果你要生成 iOS framework,记得在目标配置里加上
binaries.framework。否则 Xcode 集成时会找不到产物。 - 源码集继承关系:
iosMain不会自动继承commonMain的依赖。你需要手动调用dependsOn(commonMain)。插件默认只帮你建了文件夹,但依赖链要自己配。 - Android 与 JVM 共存:如果同时声明了
androidTarget和jvm(),注意androidMain和jvmMain是独立的。不要指望 Android 能自动复用 JVM 的代码。 - Ktor 等三方库:很多跨平台库已经提供了平台适配。比如 Ktor 的
ktor-client-core放在 commonMain,ktor-client-okhttp放在 jvmMain,ktor-client-darwin放在 iosMain。照着官方文档配就行。
嗯,KMP 的配置其实不复杂,核心就是理解「平台目标」和「源码集」这两个概念。你只要记住:commonMain 写逻辑,平台源码集写适配,大部分场景都能搞定。