Kotlin Multiplatform (KMP) 入门指南


Kotlin Multiplatform (KMP) 入门指南:开启跨平台开发新纪元

在当今移动和 Web 开发领域,覆盖多个平台(如 Android, iOS, Web, Desktop 等)已成为许多应用程序的标配。然而,为每个平台独立开发和维护代码库不仅成本高昂,而且容易导致功能不一致和开发效率低下。为了解决这一痛点,JetBrains 推出了 Kotlin Multiplatform (KMP),一个旨在通过共享代码逻辑来简化跨平台开发流程的强大技术。本指南将详细介绍 KMP 的核心概念、优势、工作原理、关键组件以及如何开始您的第一个 KMP 项目,助您踏上高效跨平台开发之旅。

一、 什么是 Kotlin Multiplatform (KMP)?

Kotlin Multiplatform (KMP) 是 Kotlin 语言的一个特性,允许开发者使用 Kotlin 编写可在多个平台(包括 JVM、Android、iOS、watchOS、tvOS、macOS、Windows、Linux 和 WebAssembly)上运行的共享代码。其核心思想并非“一次编写,到处运行”(Write Once, Run Anywhere),而是“一次编写核心逻辑,随处适配原生”(Write Core Logic Once, Adapt Native Elsewhere)。

KMP 允许您将应用程序中与平台无关的通用逻辑(如业务规则、数据处理、网络请求、数据存储抽象等)提取到一个共享的 commonMain 模块中。然后,针对每个目标平台(如 androidMain, iosMain, jvmMain 等),您可以编写平台特定的代码,实现共享模块中定义的预期功能(expect 声明),或者直接调用平台独有的 API。

这种方法的最大优势在于,它尊重并利用每个平台的原生能力和 UI 范式,同时最大限度地提高了核心业务逻辑的代码复用率。开发者无需学习全新的 UI 框架(除非选择 Compose Multiplatform),仍然可以使用熟悉的 Android XML/Jetpack Compose 或 iOS UIKit/SwiftUI 来构建用户界面。

二、 为什么选择 KMP? KMP 的核心优势

采用 KMP 技术能为开发团队和项目带来诸多显著好处:

  1. 大幅提升代码复用率:这是 KMP 最直观的优势。将业务逻辑、数据模型、网络层、数据持久化逻辑等共享,可以显著减少重复编码工作,降低维护成本。通常可以共享 50% 到 80% 的代码,具体取决于应用复杂度和架构设计。
  2. 确保业务逻辑一致性:由于核心逻辑在所有平台共享同一份代码,可以从根本上保证不同平台版本在功能行为上的一致性,避免因平台差异导致的功能偏差或 Bug。
  3. 提高开发效率和速度:共享代码意味着更少的总代码量,开发者可以将精力更集中于核心功能的实现和优化,以及平台特有的 UI/UX 打磨,从而加快产品迭代速度。
  4. 灵活的技术栈选择:KMP 并不强制统一 UI 层。团队可以继续使用各自平台最擅长、最成熟的 UI 技术(Android 的 Jetpack Compose/XML, iOS 的 SwiftUI/UIKit),仅共享非 UI 部分的逻辑。这降低了学习曲线,也便于现有项目的逐步迁移。
  5. 利用 Kotlin 的现代语言特性:Kotlin 是一种现代、简洁、安全且富有表现力的编程语言。其空安全、协程(Coroutines)、扩展函数、数据类等特性,在 KMP 项目中同样适用,有助于编写高质量、易维护的代码。
  6. 无缝的原生互操作性:KMP 设计了强大的 expect/actual 机制,允许共享代码声明预期的功能或类型,然后由平台特定代码提供实际实现。这使得共享代码可以方便地调用平台 API,反之亦然,实现了与原生生态的深度集成。
  7. 逐步采用和迁移:您不必一蹴而就地将整个应用改造成 KMP 架构。可以从共享一小部分逻辑(如数据模型或网络请求)开始,逐步扩大共享范围,对现有项目风险可控。
  8. 活跃的社区和 JetBrains 的支持:KMP 由开发 Kotlin 语言本身的 JetBrains 公司积极推动和维护,拥有日益壮大的开发者社区和不断丰富的生态库。其稳定性(自 Kotlin 1.9.20 起 Beta,部分组件 Stable)也得到了显著提升。

三、 KMP 的核心架构与工作原理

理解 KMP 的工作方式,关键在于掌握其模块化结构和 expect/actual 机制。

1. 模块化结构

一个典型的 KMP 项目通常包含以下几种类型的模块(Source Sets):

  • commonMain:这是 KMP 项目的核心,包含所有平台共享的 Kotlin 代码。这里的代码不依赖任何特定平台的 API,是纯粹的 Kotlin 代码,可以使用 Kotlin 标准库(kotlin-stdlib-common)以及支持 KMP 的通用库(如 kotlinx.coroutines, kotlinx.serialization, Ktor, SQLDelight 等)。
  • 平台特定模块(androidMain, iosMain, jvmMain, jsMain, nativeMain 等):每个目标平台都有一个对应的模块。这些模块包含:
    • 针对该平台对 commonMainexpect 声明的 actual 实现。
    • 调用该平台原生 API 的代码。
    • 平台特有的逻辑或配置。
    • androidMain 可以访问 Android SDK 和 JVM 库。
    • iosMain 可以访问 iOS/macOS/watchOS/tvOS 的 Frameworks (Foundation, UIKit/AppKit 等)。
    • jvmMain 可以访问 Java 类库。
    • jsMain 可以编译成 JavaScript,访问 Web API。
    • nativeMain 是针对所有 Kotlin/Native 平台的通用层,其下还可以细分如 iosMain, linuxX64Main 等。

Gradle 构建系统会负责根据配置,将 commonMain 的代码编译到每个目标平台,并与相应的平台特定代码结合,最终生成各平台的产物(如 Android 的 AAR/APK, iOS 的 Framework, JVM 的 JAR, JavaScript 文件等)。

2. expect/actual 机制

这是 KMP 实现平台差异化处理的关键机制,类似于接口(expect)和实现(actual)的概念,但更加灵活,可以用于类、接口、函数、属性、类型别名等。

  • expect 声明(在 commonMain 中)
    在共享代码中,当你需要一个功能,但其实现依赖于具体平台时(例如获取设备唯一 ID、文件系统访问、显示一个原生弹窗等),你可以使用 expect 关键字声明一个预期。这相当于定义了一个“契约”或“占位符”。

    ```kotlin
    // In commonMain source set
    expect class PlatformSpecificUtility {
    fun getPlatformName(): String
    fun generateUUID(): String
    }

    expect fun showNativeAlert(title: String, message: String)
    ```

  • actual 实现(在平台特定模块中)
    在每个目标平台的特定模块(如 androidMain, iosMain)中,你需要使用 actual 关键字为 commonMain 中声明的 expect 提供具体的实现。编译器会确保每个 expect 声明在所有目标平台都有对应的 actual 实现。

    ```kotlin
    // In androidMain source set
    import java.util.UUID
    import android.os.Build

    actual class PlatformSpecificUtility {
    actual fun getPlatformName(): String = "Android ${Build.VERSION.SDK_INT}"
    actual fun generateUUID(): String = UUID.randomUUID().toString()
    }

    actual fun showNativeAlert(title: String, message: String) {
    // Implementation using Android AlertDialog
    // Note: UI calls are generally better handled differently, this is illustrative
    }
    ```

    ```kotlin
    // In iosMain source set
    import platform.UIKit.UIDevice
    import platform.Foundation.NSUUID

    actual class PlatformSpecificUtility {
    actual fun getPlatformName(): String = "${UIDevice.currentDevice.systemName} ${UIDevice.currentDevice.systemVersion}"
    actual fun generateUUID(): String = NSUUID().UUIDString()
    }

    actual fun showNativeAlert(title: String, message: String) {
    // Implementation using iOS UIAlertController
    }
    ```

通过 expect/actual,共享代码可以透明地使用平台相关的功能,而无需了解其底层实现细节,保持了 commonMain 的平台无关性。

四、 KMP 项目中适合共享的内容

并非所有代码都适合放入 commonMain。通常,以下类型的逻辑是 KMP 共享的理想候选者:

  • 业务逻辑:核心业务规则、计算、状态管理等。
  • 数据层
    • 数据模型(Data Models):定义应用程序中使用的数据结构(通常使用 Kotlin data classes)。
    • 网络请求(Networking):使用 Ktor 或 Retrofit(配合 KMP 适配)等库进行 API 调用和数据获取。
    • 数据序列化/反序列化(Serialization):使用 kotlinx.serialization 库处理 JSON、ProtoBuf 等格式。
    • 数据存储(Persistence):使用 SQLDelight 生成类型安全的 SQL 操作代码,或使用 Multiplatform Settings 进行简单的键值存储。数据库、文件存储的具体实现可能需要 expect/actual
    • 数据仓库(Repositories):封装数据获取和缓存逻辑的抽象层。
  • ViewModel / Presenter / Controller:如果采用 MVVM、MVP 或 MVI 等架构,这些负责连接 UI 和业务逻辑的组件通常可以大部分或完全共享,尤其是状态管理和业务操作部分。它们处理用户输入,调用业务逻辑,并准备好供 UI 显示的数据。
  • 工具类(Utilities):与平台无关的通用工具函数,如日期处理、字符串操作、加密、格式化等。
  • 依赖注入(Dependency Injection):可以使用 Koin、Kodein-DI 等支持 KMP 的 DI 框架来管理共享模块和平台模块中的依赖关系。

不适合(或较难)共享的内容

  • 用户界面(UI):这是最具平台特定性的部分。Android 使用 Jetpack Compose 或 XML,iOS 使用 SwiftUI 或 UIKit。虽然 Compose Multiplatform 正在崛起,旨在用 Kotlin 编写声明式 UI 并共享到 Android, iOS (Alpha), Desktop, 和 Web,但目前(截至写作时)在 iOS 上的支持仍处于 Alpha 阶段,且它本身是一个需要学习的新 UI 框架。传统 KMP 项目通常仍采用原生 UI。
  • 直接操作平台硬件/传感器:如相机、GPS、蓝牙等,这些通常需要通过 expect/actual 调用原生 API。
  • 特定平台的 SDK 集成:如推送通知、支付、地图等,需要平台特定的实现。

五、 开始您的第一个 KMP 项目

现在,让我们看看如何着手创建一个 KMP 项目。

1. 开发环境准备

  • IDE: 最新版本的 IntelliJ IDEA (Community 或 Ultimate) 或 Android Studio (推荐使用最新的稳定版或 Canary/Beta 版以获得更好的 KMP 支持)。
  • Kotlin Plugin: 确保 IDE 中的 Kotlin 插件已更新到最新版本。
  • KMP Mobile Plugin (可选但推荐): Android Studio Hedgehog 及以后版本内置了 KMP Mobile 相关支持。对于 IntelliJ IDEA 或旧版 Android Studio,可以安装 "Kotlin Multiplatform Mobile" 插件,它提供了项目模板和便于 iOS 开发的集成功能。
  • Xcode: 如果您计划开发 iOS 平台,需要安装最新版本的 Xcode 和相应的命令行工具。
  • JDK: 需要安装 Java Development Kit。
  • (可选) KDoctor: 一个命令行工具,可以帮助检查您的开发环境是否满足 KMP 开发的所有要求(特别是 iOS 相关配置)。可以通过 Homebrew (macOS) 安装:brew install kdoctor,然后运行 kdoctor

2. 创建项目

最简单的方式是使用官方提供的项目模板:

  • 使用 KMP Mobile 向导 (Android Studio / IntelliJ IDEA with KMP Plugin):

    1. 打开 IDE,选择 "File" > "New" > "New Project..."。
    2. 在左侧面板选择 "Kotlin Multiplatform"。
    3. 选择 "Kotlin Multiplatform App" 模板。
    4. 配置项目名称、包名、存储位置等。
    5. 选择您想要支持的平台(例如,勾选 Android 和 iOS)。
    6. 可以选择是否包含示例代码(如共享网络请求、原生 UI 显示共享数据等)。
    7. 点击 "Finish"。IDE 会自动生成一个包含 shared 模块(存放 commonMain, androidMain, iosMain 等)、androidApp 模块和 iosApp Xcode 项目的基本结构。
  • 使用 JetBrains KMP 网页向导: 访问 https://kmp.jetbrains.com/,这是一个在线的项目生成器,可以选择更多平台和配置,然后下载生成的项目 zip 文件。

3. 项目结构概览

生成的项目通常包含:

  • shared 模块: 这是 KMP 核心模块。
    • src/commonMain/kotlin: 共享的 Kotlin 代码。
    • src/androidMain/kotlin: Android 平台的 actual 实现和特定代码。
    • src/iosMain/kotlin: iOS 平台的 actual 实现和特定代码。
    • src/commonTest/kotlin: 共享代码的单元测试。
    • src/androidTest/kotlin: Android 特定代码的测试。
    • src/iosTest/kotlin: iOS 特定代码的测试。
    • build.gradle.kts: shared 模块的 Gradle 构建脚本,配置 KMP 插件、目标平台、依赖项等。
  • androidApp 模块: 一个标准的 Android 应用程序模块。
    • src/main/javasrc/main/kotlin: Android UI 和应用逻辑。
    • build.gradle.kts: androidApp 的 Gradle 构建脚本,依赖于 shared 模块。
  • iosApp 目录: 一个标准的 Xcode 项目。
    • 包含 Swift/Objective-C 代码(UI, AppDelegate 等)。
    • 通过 CocoaPods 或直接链接的方式依赖于 shared 模块编译出的 iOS Framework。
    • 需要使用 Xcode 来构建和运行 iOS 应用。

4. 运行项目

  • Android: 在 Android Studio 中,选择 androidApp 配置,然后像运行普通 Android 应用一样点击 "Run"。
  • iOS:
    1. 首次构建可能需要在终端 shared 模块目录下运行 ./gradlew podInstall (如果使用 CocoaPods 集成)。
    2. 在 Android Studio 中,可以直接选择 iosApp 运行配置(如果 KMP Mobile 插件安装正确且配置了 Xcode 项目路径),或者直接用 Xcode 打开 iosApp 目录下的 .xcodeproj.xcworkspace 文件,选择一个模拟器或真机,然后点击 "Run"。

六、 常用 KMP 库生态

为了方便开发,KMP 社区和 JetBrains 提供了许多支持多平台的库:

  • Kotlinx:
    • kotlinx.coroutines: 用于异步编程和并发。
    • kotlinx.serialization: 用于 JSON、ProtoBuf 等数据格式的序列化/反序列化。
    • kotlinx.datetime: 用于处理日期和时间。
  • Ktor: JetBrains 开发的客户端(和服务器端)网络请求库,天然支持 KMP。
  • SQLDelight: Square 开发的库,从 SQL 语句生成类型安全的 Kotlin API,支持多平台数据库访问(底层使用平台特定的 SQLite 驱动)。
  • Multiplatform Settings: 一个简单的键值存储库,用于跨平台存储偏好设置。
  • Koin / Kodein-DI: 流行的依赖注入框架,均提供 KMP 支持。
  • Napier: 一个简单的多平台日志库。
  • Moko MVVM: 一个专注于在 KMP 中共享 ViewModel 的库。

选择库时,请务必确认其明确支持 KMP 以及您需要的目标平台。

七、 KMP 开发中的挑战与考量

虽然 KMP 带来了巨大潜力,但在实践中也可能遇到一些挑战:

  • 学习曲线: 团队成员需要熟悉 Kotlin 语言。如果目标包含 iOS,那么至少部分成员需要了解 Swift/Objective-C 和 Xcode。Gradle 构建脚本的配置也可能比原生项目更复杂。
  • 构建时间: KMP 项目,特别是包含 Kotlin/Native 编译(针对 iOS 等)时,构建时间可能相对较长。持续优化构建配置和利用缓存是必要的。
  • 生态系统成熟度: 虽然 KMP 生态在快速发展,但相比于 Android 或 iOS 的原生生态,某些特定领域的库可能还不够丰富或成熟。有时需要自己编写 expect/actual 来封装原生库。
  • 调试: 跨语言调试(例如从 Swift 代码调试到共享的 Kotlin 代码)可能不如单一平台流畅,但工具链在不断改进。
  • 平台专业知识仍需: KMP 共享的是逻辑,UI 和许多平台特性仍需原生开发知识。团队需要具备或培养跨平台(至少是目标平台)的技能。
  • 与现有项目集成: 将 KMP 引入大型现有项目需要仔细规划,可能需要重构部分代码以适应共享模块的结构。

八、 KMP 的未来:Compose Multiplatform

一个令人兴奋的方向是 Compose Multiplatform。它基于 Google 的现代 Android UI 工具包 Jetpack Compose,旨在将声明式 UI 的开发体验带到更多平台。使用 Compose Multiplatform,理论上可以用 Kotlin 编写一次 UI 代码,并在 Android, iOS (Alpha), Desktop (Windows, macOS, Linux), 和 Web (Wasm - Experimental) 上运行。

如果 Compose Multiplatform 成熟并被广泛采用,它将进一步提升 KMP 的代码共享比例,甚至可能实现 UI 层的共享。但这仍是一个相对较新的技术(尤其是在 iOS 和 Web 平台上),需要关注其发展和稳定性。

九、 总结与展望

Kotlin Multiplatform (KMP) 为跨平台开发提供了一种务实且强大的解决方案。它允许开发者在保持原生 UI/UX 的同时,最大限度地共享核心业务逻辑,从而显著提高开发效率、保证功能一致性并降低维护成本。通过其灵活的模块化结构和 expect/actual 机制,KMP 实现了共享代码与原生平台能力的无缝集成。

虽然存在一定的学习曲线和生态挑战,但随着 JetBrains 的持续投入、社区的壮大以及 Compose Multiplatform 等技术的演进,KMP 正变得越来越成熟和有吸引力。对于希望优化跨平台开发流程、拥抱 Kotlin 语言优势的团队来说,现在正是深入了解和尝试 Kotlin Multiplatform 的绝佳时机。开始你的 KMP 之旅,探索代码共享带来的无限可能吧!


THE END