1. KMP原生互操作工程全景

Kotlin Multiplatform(KMP)的核心价值并不在于“用一套代码替换所有平台”,而在于共享可共享的部分,同时保留对原生平台的直接访问能力。这意味着一个 KMP 工程通常同时存在三层代码:commonMain 中的共享业务逻辑、各平台 source set 中的平台实现,以及通过互操作机制调用原生 API 的桥接层。理解这三层之间的边界与连接方式,是后续所有工程化工作的前提。

从工程视角看,KMP 的互操作定位可以概括为:Kotlin 作为跨平台宿主语言,原生平台作为能力提供方。共享层负责业务规则、数据模型、状态管理和网络协议等与平台无关的逻辑;平台层负责 UI、系统服务、硬件能力、生命周期和平台特有 SDK 的调用。两者之间不是替代关系,而是协作关系。互操作机制要解决的核心问题是:Kotlin 代码如何安全、可维护地调用原生代码,以及原生代码如何回调 Kotlin 逻辑。

原生平台边界在哪里

在 Android 侧,Kotlin 本身就是官方语言,KMP 的 Android target 可以直接使用 JVM 生态,互操作成本最低。真正的边界出现在 iOS、macOS、Linux、Windows 以及嵌入式等非 JVM 平台。以 iOS 为例,Kotlin/Native 编译产物是一个 framework 或 XCFramework,Swift/Objective-C 通过该 framework 调用共享逻辑;反过来,Kotlin 调用 iOS 的 Foundation、UIKit、CoreLocation 等系统框架时,需要依赖 Kotlin/Native 提供的平台库绑定。

因此,原生平台边界可以按“调用方向”和“语言边界”两个维度理解:

  • Kotlin → 原生:Kotlin 调用 C/Objective-C/Swift/Java 等原生 API,通常通过 cinterop 或平台库完成。
  • 原生 → Kotlin:Swift/Java 等调用共享模块暴露的类、接口和函数,通常通过生成的 framework 头文件或 JVM 字节码完成。
  • 回调与线程:跨语言回调涉及线程模型、内存管理和生命周期,是工程中最容易出问题的区域。

cinterop 与 expect/actual 的分工

KMP 提供两类互补的互操作机制。第一类是 cinterop,用于把 C 头文件、Objective-C 头文件或系统库绑定为 Kotlin 可调用的 API。它适合调用 C 库、平台底层框架和已有原生 SDK。cinterop 的输入是 .def 文件,其中声明头文件路径、库路径、编译器参数和要暴露的符号。构建时 Kotlin/Native 会生成对应的 Kotlin 存根,使 Kotlin 代码可以像调用普通 Kotlin 函数一样调用 C 函数。

第二类是 expect/actual,用于在 commonMain 中声明“期望”的 API,并在各平台 source set 中提供“实际”实现。它不直接绑定原生符号,而是把平台差异抽象成统一接口。典型用法是:commonMain 中声明 expect fun currentTimeMillis(): Long,Android 侧用 System.currentTimeMillis() 实现,iOS 侧用 NSDate().timeIntervalSince1970 实现。expect/actual 适合平台能力差异较小、接口稳定的场景;cinterop 适合需要直接访问原生库、结构体、指针或系统框架的场景。

选择原则:如果只是“不同平台用不同实现完成同一件事”,优先用 expect/actual;如果要“直接调用某个原生库或系统框架”,优先用 cinterop。两者可以组合使用,例如 expect 声明接口,actual 内部通过 cinterop 调用原生 API。

工程化落地的核心挑战

把互操作从“能跑通”推进到“可维护”,需要面对几类典型挑战。第一是构建复杂度:KMP 工程通常包含 Gradle 多模块、Kotlin/Native 编译、Xcode 集成、CocoaPods 或 SPM 依赖,构建链路比纯 Android 或纯 iOS 工程更长。第二是内存与生命周期:Kotlin/Native 的对象与 Objective-C/Swift 对象之间存在引用关系,跨语言持有对象时若处理不当,可能造成泄漏或悬空引用。第三是线程约束:Kotlin/Native 早期版本对对象跨线程访问有严格限制,新内存模型虽已放宽,但回调线程与 UI 线程的切换仍需显式处理。第四是API 稳定性:原生 SDK 升级、头文件变化、符号重命名都会影响 cinterop 绑定,需要把互操作层隔离在独立模块中,避免污染共享业务代码。

下面这张图把 KMP 互操作工程的全景结构、调用方向和关键机制放在同一张关系图中,便于建立整体认知。

KMP 原生互操作工程全景 共享层 · 桥接层 · 平台层 · 调用方向与关键机制 commonMain · 共享业务层 业务规则 / 数据模型 状态管理 / 网络协议 expect 声明 / 接口抽象 用例 / 领域服务 桥接层 · 互操作机制 expect / actual 平台差异抽象 · 统一接口 cinterop .def 绑定 · C/OC 头文件 Framework / XCFramework Kotlin/Native 编译产物 平台层 · 原生能力 Android / JVM Kotlin 直接调用 · JNI 可选 Java SDK · Android Framework iOS / macOS Swift / Objective-C 调用 Foundation · UIKit · 系统框架 Linux / Windows / 嵌入式 C 库 · 系统 API · 硬件接口 POSIX · Win32 · 交叉编译 声明与调用 actual 实现 绑定调用 framework 暴露 回调 回调 构建复杂度 · 多工具链 内存与生命周期管理 线程模型与回调切换 原生 SDK 升级与绑定维护

从这张图可以看到,KMP 互操作工程的关键不是“把 Kotlin 写到所有平台”,而是把共享层、桥接层、平台层的职责划清,并把调用方向和生命周期管理显式化。共享层只依赖抽象接口,桥接层负责把抽象接口落到具体平台,平台层提供真实能力。任何一层职责越界,都会让后续的构建、调试和升级变得困难。

一个最小可运行的互操作示例

下面给出一个最小示例,展示 expect/actual 与 cinterop 如何配合。假设共享层需要一个“获取当前时间戳”的能力,同时 iOS 侧需要调用一个 C 函数 get_uptime_ms() 来获取设备运行时长。

// commonMain
expect fun currentTimeMillis(): Long
expect fun deviceUptimeMillis(): Long

// androidMain
actual fun currentTimeMillis(): Long = System.currentTimeMillis()
actual fun deviceUptimeMillis(): Long = SystemClock.elapsedRealtime()

// iosMain
actual fun currentTimeMillis(): Long =
    (NSDate().timeIntervalSince1970 * 1000).toLong()

actual fun deviceUptimeMillis(): Long =
    get_uptime_ms().toLong()   // 来自 cinterop 绑定

对应的 cinterop 定义文件 nativeLib.def 可以写成:

headers = native_utils.h
package = com.example.nativeutils
compilerOpts = -I./src/nativeInterop/cinterop/include
linkerOpts = -L./src/nativeInterop/cinterop/libs -lnative_utils

构建后,Kotlin/Native 会生成 com.example.nativeutils 包下的 Kotlin 存根,get_uptime_ms() 即可在 iosMain 中直接调用。这里的关键工程约束是:cinterop 绑定只放在平台 source set,不进入 commonMain;共享层只看到 expect 声明,不感知具体平台符号。这样当原生库升级或头文件变化时,修改范围被限制在桥接层。

常见误区:把平台相关类型(如 NSDate、android.content.Context)直接暴露到 commonMain 的公开 API 中。这会导致共享层无法编译到其他平台,也会让调用方被迫依赖平台类型。正确做法是用 Kotlin 标准类型或自定义数据类做边界转换。

工程化落地的检查要点

在项目初期建立互操作规范,比后期重构成本低得多。可以从以下几个问题入手:共享层是否只依赖 Kotlin 标准库和自定义抽象?桥接层是否按平台隔离在独立 source set 或模块?cinterop 的 .def 文件是否纳入版本管理并附带头文件来源说明?跨语言回调是否明确了线程切换策略?原生 SDK 升级是否有回归测试覆盖?这些问题的答案,决定了互操作代码是“能跑”还是“可维护”。