2. 搭建KMP互操作工程骨架
上一节我们明确了 KMP 互操作的核心目标:让 Android 与 iOS 共享业务逻辑,同时保留各自平台的原生能力。这一节要把目标落地成可编译、可运行的工程骨架。骨架搭得好不好,直接决定后续互操作代码是"顺畅调用"还是"处处踩坑"。本节围绕四个关键动作展开:创建共享模块、建立 Android 与 iOS 宿主工程、配置 Gradle 多目标、划分源集并管理依赖。
一、工程整体结构
一个标准的 KMP 互操作工程通常由三部分组成:共享模块(shared)、Android 宿主(androidApp)、iOS 宿主(iosApp)。共享模块是 KMP 的核心,它通过 Gradle 多目标配置,把同一份 Kotlin 代码分别编译成 Android 可用的 JVM 字节码和 iOS 可用的原生框架(framework)。两个宿主工程则各自负责平台特有的 UI、生命周期和系统 API 调用,并通过约定好的接口调用共享模块。
推荐采用如下目录结构(以 Gradle 8.x + Kotlin 1.9.x 为例):
KmpInteropDemo/
├── settings.gradle.kts
├── build.gradle.kts
├── gradle/libs.versions.toml
├── shared/
│ ├── build.gradle.kts
│ └── src/
│ ├── commonMain/kotlin/
│ ├── androidMain/kotlin/
│ ├── iosMain/kotlin/
│ └── commonTest/kotlin/
├── androidApp/
│ ├── build.gradle.kts
│ └── src/main/
└── iosApp/
├── iosApp.xcodeproj
└── iosApp/
这种"一共享 + 双宿主"的结构,是 KMP 官方推荐的最小可用骨架,也是后续所有互操作能力(expect/actual、cinterop、平台 API 桥接)的承载基础。
二、创建共享模块
共享模块的 build.gradle.kts 是整个骨架的技术核心。它需要声明 Kotlin Multiplatform 插件、配置 Android 与 iOS 两个目标、指定 iOS framework 的输出方式。一个可用的最小配置如下:
plugins {
kotlin("multiplatform") version "1.9.22"
id("com.android.library") version "8.2.2"
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions.jvmTarget = "17"
}
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "Shared"
isStatic = true
}
}
sourceSets {
val commonMain by getting
val androidMain by getting
val iosMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
}
}
android {
namespace = "com.example.kmp.shared"
compileSdk = 34
defaultConfig { minSdk = 24 }
}
这里有几个关键点需要理解。androidTarget() 让共享模块能编译成 Android 库;iosX64/iosArm64/iosSimulatorArm64 分别对应 Intel 模拟器、真机 ARM64、Apple Silicon 模拟器三种 iOS 运行环境,缺一不可,否则在对应设备上会链接失败。isStatic = true 表示生成静态 framework,能减少 iOS 端的动态库加载开销,是互操作场景下的常见选择。
三、源集划分与依赖管理
源集(source set)是 KMP 组织代码的核心机制。它决定了"哪些代码在哪些平台可见"。理解源集,就理解了 KMP 的编译边界。
| 源集 | 可见平台 | 典型内容 |
|---|---|---|
| commonMain | Android + iOS | 业务逻辑、数据模型、expect 声明 |
| androidMain | 仅 Android | actual 实现、Android SDK 调用 |
| iosMain | 仅 iOS | actual 实现、Foundation/UIKit 调用 |
| commonTest | 所有平台测试 | 共享单元测试 |
依赖管理通过 libs.versions.toml 版本目录统一管理,避免多模块版本冲突。例如在共享模块中引入协程:
// gradle/libs.versions.toml
[versions]
coroutines = "1.7.3"
[libraries]
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
// shared/build.gradle.kts 中
sourceSets {
val commonMain by getting {
dependencies {
implementation(libs.coroutines.core)
}
}
}
注意:commonMain 中只能依赖支持多平台的库(如 kotlinx 系列)。如果误引入仅 Android 可用的库,Gradle 会在 iOS 编译阶段报错,这是新手最常见的坑之一。
四、宿主工程接入
Android 宿主只需在 androidApp/build.gradle.kts 中依赖共享模块:
dependencies {
implementation(project(":shared"))
}
iOS 宿主则需要在 Xcode 中配置一个 Run Script 构建阶段,调用 Gradle 生成 framework,并把 framework 路径加入 Framework Search Paths。典型脚本为:
cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode
这个任务会自动根据当前 Xcode 的架构(模拟器或真机)选择对应的 iOS 目标,并把 framework 嵌入到 App 包中。配置完成后,iOS 端即可通过 import Shared 调用共享代码。
五、骨架验证
骨架搭好后,用一个最小示例验证三端是否打通:在 commonMain 中写一个 Greeting().greet() 返回字符串,Android 端用 TextView 显示,iOS 端用 SwiftUI 的 Text 显示。如果两端都能正确渲染出同一段文字,说明共享模块、多目标配置、源集划分、宿主接入全部生效。