Kotlin Multiplatform 是什么?一文详解
在当今软件开发的生态系统中,构建能够同时运行在多个平台(如 Android、iOS、Web、桌面和服务器)的应用是开发者面临的一个普遍挑战。传统的做法往往是针对每个平台使用其原生的开发语言和技术栈独立开发,这带来了代码冗余、逻辑不一致、开发成本高昂以及维护困难等问题。有没有一种更优雅、更高效的方式来解决这个问题呢?
Kotlin Multiplatform (KMP) 正是为了应对这一挑战而诞生的。它不是一个全新的框架或平台,而是 Kotlin 语言本身的一个特性和一种理念。通过 KMP,开发者可以编写一次业务逻辑代码,然后在不同的平台之间共享,同时保留使用各平台原生 UI 和原生特性的能力。
本文将深入探讨 Kotlin Multiplatform 的核心概念、工作原理、优势、挑战、适用场景以及其在现代软件开发中的地位。
第一部分:理解核心概念 – KMP 是什么?
要理解 Kotlin Multiplatform,首先要明确它不是什么。它不是像 React Native 或 Flutter 那样提供一个统一的 UI 框架,让你用同一套代码同时构建 UI 和业务逻辑的应用(尽管 Kotlin 的姐妹技术 Compose Multiplatform 可以帮助实现这一点)。KMP 的核心目标是共享非 UI 的代码,特别是应用的业务逻辑、数据模型、网络通信、数据存储、算法等核心部分。
简单来说,Kotlin Multiplatform (KMP) 允许你使用 Kotlin 语言编写一份代码库,这份代码库可以被编译成适用于不同平台的产物。
- 共享代码 (Shared Code / Common Code): 这是 KMP 的核心。你在这里编写与具体平台无关的业务逻辑。这部分代码可以定义数据模型、业务规则、使用多平台兼容的库(如 Kotlinx.Serialization 用于 JSON 解析,Ktor 用于网络请求,SQLDelight 或 Room KMP 用于数据库访问,Kotlin Coroutines 用于异步编程等)。
- 平台特定代码 (Platform-Specific Code): 尽管大部分核心逻辑在共享代码中,但有些功能是强依赖于特定平台的,例如访问设备的地理位置服务、调用特定的操作系统 API、使用平台原生的 UI 元素等。这些代码需要在每个平台的模块中独立编写,通常是用 Kotlin (for Android/JVM), Swift/Objective-C (for iOS), JavaScript (for Web), Kotlin/Native (for Desktop/iOS native calls) 等。
KMP 的强大之处在于,它提供了一种机制,使得共享代码可以“知道”平台特定代码的存在,并在需要时调用它们,反之亦然。这种机制的核心是 expect
和 actual
关键字。
Expect 和 Actual 机制详解
这是理解 KMP 如何工作的关键。
-
expect
(预期): 在共享代码模块中,你可以使用expect
关键字声明一个接口、类、函数或属性,表示“我期望在每个平台模块中都能找到这个声明的一个具体实现”。这个expect
声明只定义了事物的“签名”或“契约”,不包含具体的实现细节。-
示例: 假设你需要获取当前设备所在的操作系统名称。这是一个平台相关的操作。在共享模块中,你可以这样声明:
“`kotlin
// commonMain (共享模块)
expect class Platform() {
fun name(): String
}fun greet(): String {
return “Hello, ${Platform().name()}”
}
``
expect class Platform
这里的和
fun name(): String都是预期在每个平台模块中会有对应的实现。
greet()` 函数使用了这个预期声明。
-
-
actual
(实际): 在每个平台特定的模块中(如androidMain
,iosMain
,jvmMain
,jsMain
等),你需要使用actual
关键字提供expect
声明的具体实现。- 示例 (Android): 在 Android 模块 (
androidMain
) 中,你可以这样实现:
kotlin
// androidMain (Android 模块)
actual class Platform actual constructor() {
actual fun name(): String {
return "Android ${android.os.Build.VERSION.SDK_INT}" // 调用 Android SDK 的 API
}
} -
示例 (iOS): 在 iOS 模块 (
iosMain
) 中,你需要使用 Kotlin/Native 与 Objective-C/Swift 代码交互。
“`kotlin
// iosMain (iOS 模块)
import platform.UIKit.UIDevice // 导入 iOS UIKit 框架actual class Platform actual constructor() {
actual fun name(): String {
return UIDevice.currentDevice.systemName() + ” ” + UIDevice.currentDevice.systemVersion() // 调用 iOS 原生 API
}
}
* **示例 (JVM/Desktop):** 在 JVM 模块 (`jvmMain`) 中:
kotlin
// jvmMain (JVM/Desktop 模块)
actual class Platform actual constructor() {
actual fun name(): String {
return “JVM ${System.getProperty(“java.version”)}” // 调用 Java 标准库 API
}
}
“`
- 示例 (Android): 在 Android 模块 (
通过 expect
和 actual
机制,共享代码可以在不知道具体平台实现细节的情况下调用平台功能,而每个平台模块则负责提供其对应的具体实现。这种模式有效地将平台相关的代码与核心业务逻辑代码分离,保持了共享代码的纯净性和可移植性。
Kotlin 语言对多平台的支持
Kotlin 语言本身的设计就考虑到了多平台的需求。它具备优秀的互操作性:
- Kotlin/JVM: 与 Java 完全互操作,用于 Android 开发、后端开发或任何基于 JVM 的应用。
- Kotlin/JS: 可以编译成 JavaScript 代码,用于 Web 前端或 Node.js 后端。
- Kotlin/Native: 可以编译成不依赖虚拟机、直接在特定平台上运行的原生二进制代码,用于 iOS (与 Objective-C/Swift 互操作)、macOS、Linux、Windows 等。它还提供了与 C/Objective-C/Swift 代码的互操作能力。
KMP 利用这些 Kotlin 的编译目标,将同一份 Kotlin 源代码编译成适用于不同平台的产物,从而实现代码共享。
第二部分:为什么选择 Kotlin Multiplatform?优势分析
理解了 KMP 是什么以及它的核心机制后,我们来看看它能带来哪些显著的优势。
- 最大化代码共享(尤其是业务逻辑): 这是 KMP 最核心的优势。开发者可以将应用的绝大多数业务逻辑(如数据处理、验证、算法、状态管理、网络层、数据访问层等)编写在共享模块中。这意味着你不再需要在 Android 和 iOS 上分别用 Java/Kotlin 和 Swift 重写同一段逻辑,大大减少了重复劳动。
- 好处: 减少开发时间,降低开发成本,确保不同平台上的行为一致性。
- 保证跨平台的逻辑一致性: 当业务逻辑只在一处实现时,就自然而然地保证了 Android 和 iOS 等平台上的行为是完全一致的。这避免了由于不同开发者对同一需求理解不同或实现方式差异导致的 Bug 和不一致的用户体验。
- 好处: 提高产品质量和用户满意度。
- 利用 Kotlin 的现代化语言特性: Kotlin 是一门现代化的静态类型编程语言,提供了许多优秀的特性,如协程(Coroutines)用于简化异步编程、Flow 用于处理数据流、扩展函数、空安全等。在 KMP 中,这些特性都可以用于编写高效、可维护的共享代码。Kotlin 协程是实现多平台异步操作的利器,一套异步代码可以跑在 JVM、Native 和 JS 环境中。
- 好处: 提高开发效率,写出更健壮、更易读的代码。
- 保留原生的 UI/UX 体验: 与一些完全跨平台的 UI 框架不同,KMP 专注于共享业务逻辑,而 UI 部分仍然使用各平台原生的技术(Android 的 Jetpack Compose 或 Views/XML,iOS 的 SwiftUI 或 UIKit)。这意味着应用可以完全遵循各平台的设计规范和交互模式,提供最佳的用户体验和性能。
- 好处: 用户获得原汁原味的原生应用体验,开发者可以充分利用平台最新的 UI 技术和特性。
- 渐进式采用: KMP 可以非常灵活地引入到现有的项目中。你不需要一次性重写整个应用。可以从一个小的模块开始(例如,一个独立的功能模块、一个网络层或一个数据验证库),将其迁移到共享模块中,然后在现有的原生应用中使用它。随着对 KMP 的熟悉和信心增强,可以逐步迁移更多的逻辑。
- 好处: 降低引入新技术的风险,可以在不中断现有开发流程的情况下逐步享受 KMP 的优势。
- 访问原生 API: 通过
expect
和actual
机制以及 Kotlin/Native 的互操作性,共享代码可以方便地调用平台特定的 API。这使得 KMP 应用能够充分利用设备的所有功能,而不像一些框架那样受限于一个抽象层。- 好处: 功能不受限,可以实现任何原生应用能实现的功能。
- 降低开发和维护成本: 共享大部分核心代码意味着编写和测试的代码量大大减少。维护时,只需在一个地方修复业务逻辑 Bug 或添加新功能,这些改动会自动同步到所有使用该共享模块的平台。
- 好处: 长期来看,显著降低开发和维护的人力和时间成本。
- 强大的生态系统: Kotlin Multiplatform 的生态系统正在快速发展。许多流行的 Kotlin 库(如 Kotlinx.Serialization, Ktor, Kotlin Coroutines)都提供了多平台支持。还有越来越多的第三方库专为 KMP 设计,或者提供了 KMP 版本(如 SQLDelight, Room KMP, Realm, Koin, Kodein, MVIKotlin 等)。
- 好处: 开发者可以使用熟悉的、强大的工具和库来构建多平台应用。
- 服务器端共享: KMP 不仅限于移动端。你也可以将共享代码用于后端开发(Kotlin/JVM 或 Kotlin/Native),例如共享数据模型、验证逻辑等,实现真正的全栈 Kotlin 开发。这对于需要移动客户端和后端共享同一套业务契约或逻辑的场景非常有用。
- 好处: 进一步提升代码共享的范围和效率。
总而言之,Kotlin Multiplatform 提供了一种务实且高效的多平台开发策略:在保持原生用户体验和充分利用平台特性的前提下,最大限度地共享应用的业务逻辑。
第三部分:Kotlin Multiplatform 的架构和工作原理
为了更深入地理解 KMP,我们来看一下它的典型项目结构和编译流程。
项目结构
一个典型的 KMP 项目通常是一个 Gradle 多模块项目,包含以下主要模块:
shared
(或common
) 模块:- 这是存放共享代码的地方。
- 通常包含
commonMain
源集(source set),这是所有平台都会依赖的代码。 - 可能还包含其他公共源集,例如
commonTest
用于编写多平台共享的单元测试。 - 在
shared
模块内部,你还可以定义针对特定目标平台的源集,例如androidMain
,iosMain
,jvmMain
,jsMain
等。这些平台特定的源集用于存放actual
实现、访问平台 SDK 的代码,或者仅在某个平台使用的库的依赖。 iosMain
通常会进一步细分为iosArm64Main
(用于真机),iosX64Main
(用于模拟器),iosSimulatorArm64Main
(用于新款模拟器) 等,以支持不同 iOS 架构。
- 平台特定的应用模块:
androidApp
: 一个标准的 Android 项目模块,依赖于shared
模块。在这里编写 Android UI (Compose 或 Views) 和任何 Android 特定的逻辑。iosApp
: 一个标准的 Xcode 项目,通过 CocoaPods、Swift Package Manager (SPM) 或直接框架依赖的方式集成shared
模块编译生成的 Framework 或 Library。在这里编写 iOS UI (SwiftUI 或 UIKit) 和任何 iOS 特定的逻辑。- 可能还有
desktopApp
,webApp
等模块,分别依赖于shared
模块并在各自平台上运行。
源集 (Source Sets): KMP 项目中的关键概念是源集。一个模块可以包含多个源集,每个源集针对一个或多个目标平台。通过 Gradle 配置,可以建立源集之间的依赖关系。例如,androidMain
源集依赖于 commonMain
,并且可以访问 Android SDK。iosMain
依赖于 commonMain
,并可以访问 iOS SDK(通过 Kotlin/Native 的互操作层)。
编译过程
KMP 的编译过程是其实现多平台共享的核心:
- Kotlin 编译器: Kotlin 编译器是整个过程的核心。它能够理解 Kotlin 源代码,并根据指定的编译目标生成相应的产物。
- 源集处理: Gradle 构建系统根据项目配置识别各个源集及其依赖关系。
- 平台特定编译:
- 对于面向 JVM 的源集 (
commonMain
如果包含 JVM 相关代码,以及androidMain
,jvmMain
等),Kotlin 编译器生成 JVM 字节码 (.class
文件)。 - 对于面向 JS 的源集 (
commonMain
如果包含 JS 相关代码,以及jsMain
等),Kotlin 编译器生成 JavaScript 代码 (.js
文件)。可以生成不同的 JS 目标(如 IR 模式,Legacy 模式)。 - 对于面向 Native 的源集 (
commonMain
如果包含 Native 相关代码,以及iosMain
,macosMain
,linuxMain
,windowsMain
等),Kotlin/Native 编译器生成平台特定的原生二进制代码。例如,对于 iOS 目标,它通常生成一个 Framework (.framework
文件),这个 Framework 可以被 Swift 或 Objective-C 项目引用。
- 对于面向 JVM 的源集 (
- 产物集成:
- Android 项目直接将
shared
模块的 JVM 字节码作为依赖引入,就像引用任何其他 Gradle 模块一样。 - iOS 项目需要将 Kotlin/Native 编译生成的
.framework
文件嵌入到 Xcode 项目中。Gradle 提供了任务来自动化这个过程(例如,生成一个可供 CocoaPods 或 SPM 使用的二进制框架)。Xcode 项目然后将这个 Framework 作为其依赖,Swift/Objective-C 代码可以通过互操作性调用 Kotlin 代码。
- Android 项目直接将
这个编译过程使得同一份 Kotlin 源码能够在不同的平台上转化为可执行或可引用的代码,从而实现代码共享。
第四部分:挑战与注意事项
尽管 Kotlin Multiplatform 提供了诸多优势,但在实际应用中也需要面对一些挑战和注意事项:
- 学习曲线: 对于习惯于单一平台开发的团队来说,理解 KMP 的理念(共享与平台特定分离)、
expect
/actual
机制、Gradle 的多模块配置以及 Kotlin/Native 的互操作性(尤其是与 Swift/Objective-C 的交互)需要一定的学习时间。协调跨平台的开发团队(如 Android 开发者、iOS 开发者)共享同一个代码库也需要新的协作模式。 - 工具链成熟度: 尽管工具链正在快速发展和成熟,但在某些方面(尤其是在早期阶段)可能不如成熟的原生开发工具链那样完善或稳定。调试多平台代码,特别是 Kotlin/Native 部分,有时可能比原生调试更复杂。
- 构建时间: KMP 项目的构建过程,特别是包含多个 Native 目标时,可能会比单一平台项目更复杂,有时构建时间也可能相对较长。需要合理配置 Gradle 和利用缓存来优化。
- 第三方库支持: 虽然多平台库生态正在壮大,但并非所有 Java/Android 或 Swift/iOS 库都有对应的多平台版本。对于没有多平台支持但又必不可少的库,可能需要通过
expect
/actual
机制或 Kotlin/Native 的互操作性编写包装器或适配层。 - UI 共享(非 KMP 核心,但相关): KMP 本身不共享 UI。如果你需要一套代码同时构建 UI 和 逻辑,那么你需要结合 Compose Multiplatform。虽然 Compose Multiplatform 建立在 KMP 之上,但它引入了另一层复杂性。纯粹的 KMP 项目只共享逻辑。
- 平台特定功能的实现: 对于需要深度集成平台特定功能的场景(如复杂的地图集成、特定的传感器使用、操作系统级别的权限处理等),平台特定代码仍然是必需的,并且可能需要编写不少 Glue Code (连接共享代码和原生 API 的代码)。
- 团队协作和知识共享: 成功实施 KMP 需要 Android 和 iOS 团队之间的紧密协作和知识共享。他们需要共同维护共享代码库,理解彼此平台的约束和习惯。
认识到这些挑战并提前规划,可以帮助团队更顺利地引入和使用 Kotlin Multiplatform。
第五部分:适用场景和用例
Kotlin Multiplatform 特别适合以下场景和用例:
- 移动应用开发(Android + iOS): 这是 KMP 最常见和最有吸引力的用例。共享业务逻辑、网络层、数据存储、验证规则等,可以显著提高开发效率和产品一致性。许多公司已经成功地在其 Android 和 iOS 应用中使用了 KMP。
- SDK/库开发: 如果你需要开发一个可以同时被 Android 和 iOS 应用使用的 SDK 或库(例如,一个认证库、一个支付库、一个分析库),使用 KMP 是一个理想的选择。你可以编写一次核心库代码,然后将其编译成 Android Library (AAR) 和 iOS Framework,供原生应用方便地集成。
- Web 后端开发: 使用 Ktor 等多平台框架,可以用 Kotlin 编写 Web 后端服务。结合 KMP 共享模块,可以在后端和移动客户端之间共享数据模型、序列化/反序列化逻辑、验证规则等,实现真正的端到端 Kotlin 开发。
- 桌面应用开发: 结合 Jetpack Compose for Desktop 或其他 JVM/Native UI 框架,可以将 KMP 共享代码用于构建桌面应用,与移动端或后端共享逻辑。
- 全栈共享: 在一些场景下,可以构建一个包含共享业务逻辑、Android 应用、iOS 应用、Web 前端 (Kotlin/JS) 和后端 (Kotlin/JVM) 的 KMP 项目,最大化代码共享范围。
- 大型企业应用: 在需要为多个平台构建复杂应用的大型企业中,KMP 可以帮助减少重复劳动,确保核心业务逻辑的一致性和可维护性。
- 创业公司: 对于资源有限的创业公司,KMP 可以帮助他们更快地推出产品,同时保持代码质量和一致性。
KMP 的渐进式采用特性也意味着,即使现有项目已经非常庞大,也可以选择从某个新功能模块或某个核心库开始尝试使用 KMP,逐步评估其效果。
第六部分:生态系统和相关技术
Kotlin Multiplatform 的发展离不开其日益壮大的生态系统:
- Kotlin Coroutines & Flow: 用于多平台异步和响应式编程的标准库,极大地简化了复杂并发任务的处理。
- Kotlinx.Serialization: 多平台序列化库,支持 JSON、Protobuf 等格式,是在不同层之间传递数据(如网络通信)的关键。
- Ktor: Kotlin 编写的多平台网络框架,可以用于构建客户端应用(发送 HTTP 请求)和服务端应用(构建 RESTful API)。
- 数据库: SQLDelight(Square 开发的跨平台 SQL 数据库访问库)、Room KMP(Jetpack Room 的 KMP 版本,实验性)以及 Realm (MongoDB 的移动数据库,支持 KMP)。
- 状态管理/MVI: MVIKotlin 等库提供多平台的状态管理解决方案。
- 依赖注入: Koin, Kodein-DI 等库支持 KMP。
- Compose Multiplatform: 虽然核心 KMP 不共享 UI,但 Compose Multiplatform 允许使用 Jetpack Compose 编写一套声明式 UI 代码,然后部署到 Android、Desktop 和 Web (JS) 平台。未来计划支持 iOS。它是 KMP 的重要补充,可以在某些场景下实现更广泛的代码共享(包括 UI)。需要注意的是,Compose Multiplatform 和 KMP 是相互独立的但可以一起使用的技术。KMP 负责逻辑,Compose MP 负责 UI。
- KMM (Kotlin Multiplatform Mobile): KMM 曾是 JetBrains 推广 KMP 在移动端(Android + iOS)应用时使用的特定称谓。虽然现在 JetBrains 更多地使用更通用的 “Kotlin Multiplatform”,但 KMM 这个词汇仍然常用,特指 KMP 在移动开发领域的应用。
这个不断丰富的生态系统使得使用 KMP 构建实际应用变得越来越可行和高效。
第七部分:KMP 与其他跨平台方案的比较(简要)
理解 KMP 的定位,有助于将其与其他流行的跨平台方案区分开来:
- 原生开发 (Native Development): 分别使用 Java/Kotlin for Android 和 Swift/Objective-C for iOS。提供最佳的性能、用户体验和最新的平台特性访问。缺点是代码重复,开发和维护成本高。
- KMP vs. Native: KMP 试图在保持原生体验的同时,减少重复代码,降低成本。你可以认为 KMP 是对原生开发的一种增强,而非替代。它让你能继续使用原生 UI 技术。
- React Native: 使用 JavaScript/TypeScript 和 React 框架构建 UI 和逻辑。通过一个桥接层与原生组件交互。优点是 Web 前端开发者上手快,UI 和逻辑可以共享。缺点是性能可能受桥接层影响,原生模块访问有时复杂,可能不像原生 UI 那样流畅。
- KMP vs. React Native: KMP 主要共享逻辑,保留原生 UI;RN 主要共享 UI 和逻辑。RN 依赖于 JS 运行时和桥接层;KMP 编译成原生代码(或 JVM/JS)。KMP 更适合需要极致原生体验和性能的场景。
- Flutter: 使用 Dart 语言和自己的渲染引擎 (Skia) 绘制 UI。一套代码构建 UI 和逻辑。优点是渲染性能高(直接绘制像素),开发效率高,拥有丰富的 Widget 库。缺点是 UI 不使用原生组件(外观可能与原生有微小差异),应用体积相对较大,Dart 语言生态相对较小(但正在快速发展)。
- KMP vs. Flutter: 类似 RN,Flutter 共享 UI 和逻辑,KMP 共享逻辑保留原生 UI。Flutter 使用 Dart VM,KMP 编译到原生/JVM/JS。Flutter 更适合需要快速构建统一、美观 UI 的应用。KMP 更适合需要深度原生集成和极致原生体验的应用,或只需共享非 UI 逻辑的场景。
核心区别总结:
- KMP: 共享逻辑,原生 UI。 利用 Kotlin 语言特性,编译到不同平台原生代码/JVM/JS。保留平台原生体验。
- React Native / Flutter: 共享 UI 和逻辑。 RN 通过桥接与原生组件交互,Flutter 自绘 UI。目标是提供一致的跨平台 UI 和体验。
KMP 并不是要取代所有其他方案,而是提供了一种新的、灵活的选择,特别适合那些看重原生体验,但又希望减少业务逻辑重复开发的团队。
第八部分:Kotlin Multiplatform 的未来
Kotlin Multiplatform 正在积极发展中。JetBrains 持续投入资源,改进编译器、工具链、性能和生态系统。Compose Multiplatform 对 iOS 的支持是当前的重要方向,一旦成熟,将进一步提升 KMP 的吸引力,使其能够实现更广泛的代码共享(包括 UI)。
随着越来越多的公司和开发者采用 KMP,社区贡献的库和工具也在不断增加,形成一个正向循环。KMP 已经被 Dropbox, VMWare, Philips, 3Shape 等公司用于生产环境,证明了其可行性和价值。
可以预见,随着技术的成熟和生态的完善,Kotlin Multiplatform 将成为构建现代多平台应用越来越受欢迎的选择,尤其是在移动开发领域。
结论
Kotlin Multiplatform (KMP) 提供了一种革命性的方式来构建多平台应用,它允许开发者使用强大的 Kotlin 语言编写一次业务逻辑,然后在 Android、iOS、桌面、Web 和服务器等不同平台之间共享,同时保留使用各平台原生 UI 和原生特性的能力。
通过核心的 expect
/actual
机制和 Kotlin 语言的多平台编译能力,KMP 有效地解决了跨平台开发中的代码重复、逻辑不一致和维护成本高等痛点。它带来了显著的代码复用、开发效率提升、逻辑一致性保证以及灵活的渐进式采用能力。
尽管在工具链成熟度和团队协作方面仍可能面临一些挑战,但随着生态系统的不断完善和 JetBrains 的持续投入,Kotlin Multiplatform 已经成为一个构建高效、高质量、跨平台应用(特别是移动应用)的有力工具。
如果你正在寻找一种方案,既想最大化共享业务逻辑以降低成本和确保一致性,又不想牺牲原生应用的用户体验和对平台特性的完全访问,那么 Kotlin Multiplatform 绝对值得你深入了解和尝试。它代表着一种务实且具有前瞻性的多平台开发未来。