2. KMP工程环境搭建

Kotlin Multiplatform(KMP)工程的环境搭建,本质上是把“一份 Kotlin 源码要编译到多个平台”这件事,翻译成 Gradle 能理解的插件、源集(source set)与目标(target)声明。理解这三者的关系,比记住某一条命令更重要:插件负责提供能力,Gradle 配置负责组织构建,目标声明负责决定产物落到哪些平台。本节以一个可运行的共享模块为例,走通从零初始化到多平台编译通过的全过程。

一、环境前置条件与版本对齐

KMP 对工具链版本较敏感,建议先固定三件套:JDK、Gradle、Kotlin。示例采用 JDK 17、Gradle 8.7、Kotlin 2.0.20,Android 侧使用 AGP 8.5.2。这些是示例参数,实际项目应以官方兼容矩阵为准。需要特别注意的是,Kotlin 2.0 之后 kotlin("multiplatform") 插件已内置对 Compose Multiplatform 的兼容支持,但 Compose 编译器插件仍需单独声明。

初始化方式有两种:一是用 IntelliJ IDEA / Android Studio 的 KMP 向导生成骨架;二是手动创建目录与构建脚本。向导适合快速起步,但生成的结构往往包含大量示例代码,不利于理解。下面采用手动方式,从空目录开始,这样每一行配置都有明确目的。

二、工程目录与源集结构

KMP 的核心概念是源集:commonMain 存放所有平台共享的代码,androidMain、iosMain、desktopMain 等存放平台特有实现。源集之间通过 dependsOn 形成继承关系,commonMain 是公共父源集。目录结构如下:

shared/
├── build.gradle.kts
└── src/
    ├── commonMain/kotlin/       # 共享业务逻辑
    ├── commonTest/kotlin/       # 共享测试
    ├── androidMain/kotlin/      # Android 平台实现
    ├── iosMain/kotlin/          # iOS 平台实现
    └── desktopMain/kotlin/      # JVM 桌面实现

这个结构不是强制的,但遵循它可以让 Gradle 默认源集约定生效,减少显式配置。若自定义目录,需要在 kotlin { sourceSets { ... } } 中重写 kotlin.srcDir。

三、build.gradle.kts 关键配置

共享模块的构建脚本需要完成四件事:应用插件、声明目标、配置源集依赖、设置编译选项。以下为示例配置:

plugins {
    kotlin("multiplatform") version "2.0.20"
    id("com.android.library") version "8.5.2"
}

kotlin {
    androidTarget {
        compilations.all {
            kotlinOptions.jvmTarget = "17"
        }
    }
    jvm("desktop")
    iosX64(); iosArm64(); iosSimulatorArm64()

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
            }
        }
        val commonTest by getting {
            dependencies { implementation(kotlin("test")) }
        }
        val androidMain by getting
        val desktopMain by getting
        val iosMain by creating {
            dependsOn(commonMain)
        }
    }
}

这里有几个容易踩坑的点。第一,iosMain 在部分版本中不会自动创建,需要显式 creating 并 dependsOn(commonMain);第二,androidTarget 必须配合 com.android.library 插件,否则 Android 目标无法解析;第三,JVM 目标用 jvm("desktop") 命名后,源集名变为 desktopMain,而不是默认的 jvmMain。

四、多目标平台声明与产物关系

目标声明决定了编译产物。Android 目标产出 AAR,JVM 目标产出 JAR,iOS 目标产出 klib 与最终 framework。下图展示了插件、Gradle 配置、源集与目标之间的依赖与产物关系,帮助建立整体认知:

Kotlin Multiplatform 插件 提供 kotlin{} 与源集 DSL Android Library 插件 提供 androidTarget 支持 Gradle 构建系统 解析配置、调度任务 build.gradle.kts 配置层 kotlin { androidTarget(); jvm("desktop"); iosX64() ... } sourceSets { commonMain / androidMain / iosMain ... } commonMain 共享业务逻辑 androidMain Android 实现 iosMain iOS 实现 desktopMain JVM 桌面实现 AAR Android 产物 JAR JVM 产物 klib / framework iOS 产物 JAR 桌面产物

五、初始化后的验证与常见问题

配置完成后,执行 ./gradlew :shared:tasks 查看可用任务。若能看到 compileKotlinAndroid、compileKotlinDesktop、compileKotlinIosArm64 等任务,说明目标声明已被正确解析。接着执行 ./gradlew :shared:build,观察是否所有目标都编译通过。

常见问题有三类。其一,iOS 目标在非 macOS 环境下无法编译,Windows/Linux 上应只保留 Android 与 JVM 目标,或使用 if (HostManager.hostIsMac) 条件声明;其二,commonMain 中引用了平台 API(如 java.util.UUID),会导致其他平台编译失败,应改用 expect/actual 或 kotlin.uuid.Uuid;其三,AGP 与 Kotlin 版本不匹配会报 Unsupported Kotlin plugin version,需对照兼容表调整。

建议在 gradle.properties 中开启 kotlin.mpp.enableCInteropCommonization=true 与 kotlin.native.cacheKind=none(调试期),可减少 iOS 编译缓存带来的困惑。

六、可复用的初始化检查清单

为便于后续章节直接复用,下面给出一份初始化检查清单,覆盖从环境到验证的关键项:

检查项期望结果判断依据
JDK 版本17 或以上java -version 输出一致
Gradle Wrapper8.7gradle/wrapper/gradle-wrapper.properties
KMP 插件2.0.20plugins 块中版本号
目标声明android / desktop / ioskotlin { } 中 target 数量
源集继承iosMain dependsOn commonMainsourceSets 配置
编译验证BUILD SUCCESSFUL./gradlew :shared:build