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 的编译边界。

源集可见平台典型内容
commonMainAndroid + iOS业务逻辑、数据模型、expect 声明
androidMain仅 Androidactual 实现、Android SDK 调用
iosMain仅 iOSactual 实现、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 调用共享代码。

首次配置 iOS 宿主时,建议先在 Xcode 中手动执行一次 Run Script,确认 framework 能正常生成,再接入业务代码,避免把构建问题和代码问题混在一起排查。

五、骨架验证

骨架搭好后,用一个最小示例验证三端是否打通:在 commonMain 中写一个 Greeting().greet() 返回字符串,Android 端用 TextView 显示,iOS 端用 SwiftUI 的 Text 显示。如果两端都能正确渲染出同一段文字,说明共享模块、多目标配置、源集划分、宿主接入全部生效。

KMP 互操作工程骨架结构 shared 共享模块 commonMain(业务逻辑 / expect) androidMain iosMain commonTest(共享测试) Gradle 多目标:androidTarget + iosX64/Arm64/SimulatorArm64 androidApp 宿主 implementation(project(":shared")) JVM 字节码 · Android SDK Compose / View 调用共享逻辑 iosApp 宿主 embedAndSignAppleFramework Shared.framework · SwiftUI import Shared 调用共享逻辑 编译为 JVM 库 编译为 iOS framework 验证:commonMain 返回字符串 → 两端 UI 渲染一致即骨架打通