Kotlin Multiplatform 入门指南 – wiki基地


Kotlin Multiplatform 入门指南:开启跨平台开发新篇章

在当今移动和 Web 开发领域,覆盖多个平台(如 Android、iOS、Web、桌面等)已成为常态。然而,为每个平台独立开发和维护代码库不仅成本高昂,效率低下,还容易导致功能和行为的不一致。为了解决这些痛点,JetBrains 推出了 Kotlin Multiplatform (KMP),一个强大的技术,允许开发者在不同平台之间共享 Kotlin 代码,特别是业务逻辑、数据层和通用工具,同时保留与原生平台 API 和 UI 的完全交互能力。

本文将作为一份详尽的入门指南,带你深入了解 Kotlin Multiplatform 的核心概念、优势、挑战,并手把手教你如何搭建环境、创建第一个 KMP 项目,最终掌握 KMP 开发的基础。

什么是 Kotlin Multiplatform (KMP)?

Kotlin Multiplatform (曾被称为 Kotlin Multiplatform Mobile 或 KMM,现已扩展) 是 Kotlin 语言的一项功能,它允许你将相同的 Kotlin 代码编译到多个目标平台。其核心思想不是“一次编写,到处运行”(Write Once, Run Anywhere),而是“一次编写逻辑,随处复用”(Write Logic Once, Reuse Everywhere),同时拥抱各个平台的原生特性。

KMP 的关键在于 代码共享。你可以将平台无关的业务逻辑、数据模型、网络请求、数据存储逻辑、算法等代码放在一个共享的 commonMain 模块中。然后,Kotlin 编译器会根据你的配置,将这部分共享代码编译成对应平台的格式:

  • JVM: 用于 Android 应用、服务器端应用、桌面应用 (Compose for Desktop)。
  • JavaScript: 用于 Web 前端应用 (React, Vue, Angular 或 Compose for Web)。
  • Native: 使用 Kotlin/Native 编译器,生成针对特定平台的原生二进制代码,如 iOS (arm64, x64 simulator), macOS, Windows, Linux。

这意味着你的核心逻辑只需要用 Kotlin 编写和测试一次,就可以在 Android、iOS、Web 等多个应用中使用,大大减少了重复工作和潜在的错误。

KMP 的核心概念

理解 KMP 的运作方式需要掌握几个关键概念:

  1. 共享模块 (Shared Module / Common Module): 这是 KMP 项目的核心。它包含平台无关的 Kotlin 代码。通常,这个模块的源代码位于 src/commonMain/kotlin 目录下。这里定义的类、函数、接口等都可以在所有目标平台中使用。
  2. 平台特定模块 (Platform-Specific Modules): 除了共享模块,KMP 项目还包含针对每个目标平台的特定模块(或源集 Source Sets)。例如:
    • src/androidMain/kotlin: 包含仅用于 Android 平台的 Kotlin 代码,可以访问 Android SDK。
    • src/iosMain/kotlin: 包含仅用于 iOS 平台的 Kotlin 代码,可以访问 iOS 框架 (Foundation, UIKit 等)。
    • src/jvmMain/kotlin: 用于 JVM 平台(非 Android)。
    • src/jsMain/kotlin: 用于 JavaScript 平台。
    • src/nativeMain/kotlin, src/macosX64Main/kotlin, etc.: 用于不同的 Kotlin/Native 目标。
  3. expect / actual 机制: 这是 KMP 实现平台特定功能的关键。当共享代码需要访问某个平台独有的 API 时(例如获取设备 ID、文件系统访问、日期格式化等),你可以在 commonMain 中使用 expect 关键字声明一个类、函数、属性或接口,表明“期望”各个平台提供这个功能的实现。

    • expect 声明 (in commonMain): 定义了一个契约,但不包含具体实现。
      “`kotlin
      // src/commonMain/kotlin/com/example/Platform.kt
      package com.example.kmpapp

      expect class Platform() {
      val name: String
      }

      expect fun getDeviceUUID(): String
      * **`actual` 实现 (in platform-specific source sets):** 在每个平台特定的源集(如 `androidMain`, `iosMain`)中,使用 `actual` 关键字提供 `expect` 声明的具体实现。kotlin
      // src/androidMain/kotlin/com/example/Platform.kt (Android)
      package com.example.kmpapp

      import android.os.Build
      import java.util.UUID
      import android.provider.Settings

      actual class Platform actual constructor() {
      actual val name: String = “Android ${Build.VERSION.SDK_INT}”
      }

      // A simplified example, requires context for real usage
      actual fun getDeviceUUID(): String {
      // In a real app, you’d need context, handle permissions etc.
      // This is illustrative. A common approach uses Settings.Secure.ANDROID_ID
      // For more robust UUID, other strategies exist.
      return “android-uuid-placeholder” // Placeholder
      }
      kotlin
      // src/iosMain/kotlin/com/example/Platform.kt (iOS)
      package com.example.kmpapp

      import platform.UIKit.UIDevice
      import platform.Foundation.NSUUID

      actual class Platform actual constructor() {
      actual val name: String = UIDevice.currentDevice.systemName() + ” ” + UIDevice.currentDevice.systemVersion
      }

      actual fun getDeviceUUID(): String {
      // Example using identifierForVendor, might change on app reinstall
      return UIDevice.currentDevice.identifierForVendor?.UUIDString ?: “ios-uuid-unavailable”
      }
      ``
      通过
      expect/actual,共享代码可以调用Platform().namegetDeviceUUID(),而 KMP 编译器会确保在编译到特定平台时,调用的是该平台对应的actual` 实现。

  4. Gradle 构建系统: KMP 项目严重依赖 Gradle 进行构建、依赖管理和多平台编译配置。项目根目录和共享模块下通常都有 build.gradle.kts 文件,用于定义目标平台、源集、依赖项和编译选项。

KMP 的主要优势

选择 KMP 能带来诸多好处:

  1. 显著的代码重用: 这是最核心的优势。业务逻辑、数据处理、网络层等只需编写一次,减少开发时间和成本。代码库更小,更易于维护。
  2. 平台一致性: 由于核心逻辑共享,不同平台应用的行为和业务规则能够保持高度一致,减少因平台差异导致的 Bug。
  3. 原生性能和体验: KMP 编译后的代码是对应平台原生的(JVM 字节码、JavaScript、原生二进制码)。它不像某些跨平台 UI 框架那样需要桥接或虚拟机,因此可以获得接近原生开发的性能。同时,UI 部分仍然可以使用平台原生技术(Android XML/Jetpack Compose, iOS UIKit/SwiftUI)构建,确保最佳的用户体验和平台特性利用。
  4. 利用 Kotlin 语言优势: 开发者可以享受 Kotlin 带来的现代语言特性,如空安全、协程(简化异步编程)、扩展函数、数据类、密封类等,这些特性在所有目标平台上都可用,提升开发效率和代码质量。
  5. 逐步迁移和集成: KMP 可以轻松集成到现有的 Android 和 iOS 项目中。你可以先将一小部分逻辑(如下载器、验证器)迁移到共享模块,逐步扩大共享范围,风险可控。
  6. 活跃的生态和社区: KMP 生态系统正在快速发展,涌现出许多支持多平台的库(如 Ktor 用于网络、SQLDelight 用于数据库、kotlinx.serialization 用于序列化、 kotlinx.coroutines 用于并发)。JetBrains 和社区也在持续投入,不断完善工具链和库支持。

KMP 的挑战与考量

尽管 KMP 非常有前景,但在采用前也需要了解一些潜在的挑战:

  1. 平台特定 API 的处理: 虽然 expect/actual 很强大,但频繁或复杂地使用它会增加共享代码的复杂性。需要仔细设计哪些逻辑应该共享,哪些应该保留在平台层。
  2. 库的支持: 虽然 KMP 库生态在增长,但可能仍然缺少某些特定平台原生库的直接 KMP 替代品。有时需要自己编写 expect/actual 封装,或者寻找社区提供的库。
  3. 构建时间和复杂性: KMP 项目的 Gradle 配置相对复杂,尤其是涉及多个 Native 目标时。编译时间,特别是首次编译或清理后编译 Kotlin/Native 代码,可能会比较长。
  4. 学习曲线: 对于团队来说,需要掌握 Kotlin 语言、KMP 概念、Gradle 多平台配置以及 Kotlin/Native 的工作原理(尤其是与 iOS 的互操作性)。iOS 开发者可能还需要适应 Kotlin 和 Gradle。
  5. 团队协作: 需要 Android 和 iOS (以及其他平台) 开发者紧密协作,共同维护共享代码。对共享模块的修改需要考虑所有目标平台的影响。
  6. 调试: 跨平台调试有时会比纯原生开发更具挑战性,尤其是在 expect/actual 边界或 Kotlin/Native 与原生平台交互的部分。

环境搭建

要开始 KMP 开发,你需要准备以下工具:

  1. IntelliJ IDEA 或 Android Studio: 这是主要的开发环境。推荐使用最新稳定版本。Android Studio 对于包含 Android 目标的 KMP 项目特别方便。
    • 确保安装了 Kotlin 插件(通常随 IDE 自动安装或更新)。
    • 安装 Kotlin Multiplatform Mobile 插件(即使你不只做移动端,这个插件也提供了很好的项目模板和工具集成)。
  2. JDK (Java Development Kit): KMP 依赖 JVM,需要安装 JDK (推荐版本 11 或更高)。
  3. Xcode: 如果你的目标平台包含 iOS 或 macOS,你需要安装最新版本的 Xcode(可从 Mac App Store 获取)及其命令行工具。Xcode 用于编译和运行 iOS/macOS 应用,并提供必要的 SDK 和模拟器。
  4. (可选) KDoctor: 这是一个命令行工具,可以帮助你检查 KMP 开发环境(Kotlin 插件、Android Studio、Xcode、CocoaPods 等)是否配置正确。在终端运行 brew install kdoctor (如果使用 Homebrew),然后运行 kdoctor 进行诊断。
  5. (可选) CocoaPods: 如果你的 iOS 项目使用 CocoaPods 管理依赖,Kotlin/Native 可以将共享模块打包成一个 Pod,方便集成。你需要安装 CocoaPods (sudo gem install cocoapods)。

创建你的第一个 KMP 项目

最简单的方式是使用 IntelliJ IDEA 或 Android Studio 提供的 KMP 项目向导:

  1. 启动 IDE: 打开 IntelliJ IDEA 或 Android Studio。
  2. 新建项目: 选择 “File” > “New” > “Project…”。
  3. 选择模板: 在左侧面板中,选择 “Kotlin Multiplatform”。在右侧,你会看到不同的模板:
    • Kotlin Multiplatform App: 一个包含 Android 和 iOS 客户端应用,以及共享模块的基本模板。这是入门的最佳选择。
    • Kotlin Multiplatform Library: 用于创建一个可被其他 KMP 项目或原生项目使用的共享库。
    • 其他可能还有 Web、Desktop 等模板,具体取决于你的 IDE 和插件版本。
    • 选择 Kotlin Multiplatform App
  4. 配置项目:
    • Project Name: 输入你的项目名称(例如 MyKmpApp)。
    • Package Name: 定义你的基础包名(例如 com.example.mykmpapp)。
    • Project Location: 选择项目存储路径。
    • Android App Name: Android 应用的显示名称。
    • iOS App Name: iOS 应用的显示名称。
    • Shared Module Name: 共享模块的名称(通常是 sharedcommon)。
    • iOS Framework Distribution: 选择如何将共享模块集成到 iOS 项目中。
      • Regular framework: 标准的动态或静态框架。
      • CocoaPods dependency: 将共享模块打包成一个 CocoaPod,通过 Podfile 集成。对于熟悉 CocoaPods 的 iOS 开发者来说,这通常是首选。
    • Add sample tests for your project: 勾选此项会自动生成一些测试示例。
  5. 完成创建: 点击 “Finish”。IDE 将会下载所需的依赖并设置项目结构。这可能需要一些时间。

理解项目结构

创建完成后,你会看到一个典型的 KMP 项目结构,主要包含:

  • 根目录:
    • settings.gradle.kts: 定义项目中包含哪些模块(如 :androidApp, :iosApp, :shared)。
    • build.gradle.kts: 根项目的构建脚本,通常用于配置全局插件版本和仓库。
    • gradle/wrapper: Gradle Wrapper 配置。
  • shared 模块 (或你指定的共享模块名):
    • build.gradle.kts: 关键配置文件。定义了共享模块的目标平台(androidTarget, iosX64, iosArm64, iosSimulatorArm64 等)、源集(commonMain, androidMain, iosMain 等)以及它们的依赖关系。
    • src/: 包含源代码。
      • commonMain/kotlin: 平台无关的共享 Kotlin 代码和资源。
      • androidMain/kotlin: Android 平台特定的 Kotlin 代码和 AndroidManifest.xml
      • iosMain/kotlin: iOS 平台特定的 Kotlin 代码。
      • commonTest/kotlin: 共享代码的单元测试。
      • androidTest/kotlin: Android 特定代码的测试。
      • iosTest/kotlin: iOS 特定代码的测试。
  • androidApp 模块: 一个标准的 Android 应用模块。
    • build.gradle.kts: Android 应用的构建脚本,它会依赖 shared 模块 (implementation(project(":shared")))。
    • src/main: Android 应用的源代码、资源、AndroidManifest.xml 等。
  • iosApp 目录: 一个标准的 Xcode 项目目录。
    • iosApp.xcodeprojiosApp.xcworkspace: Xcode 项目文件。
    • Podfile (如果选择了 CocoaPods): 定义 iOS 项目的依赖,包括指向本地 shared 模块的 Pod。
    • iosApp/: 包含 Swift/Objective-C 代码(如 ContentView.swift, AppDelegate.swift 等),用于构建 UI 和调用 shared 模块的功能。

编写共享代码

让我们在 shared 模块中添加一些简单的共享逻辑。

  1. 创建数据类:shared/src/commonMain/kotlin 下创建一个新的 Kotlin 文件,例如 Greeting.kt

    “`kotlin
    package com.example.mykmpapp.shared // Use your package name

    data class Message(val text: String, val timestamp: Long)

    class Greeting {
    private val platform: Platform = Platform() // Using the expect/actual Platform class

    fun greet(): String {
        val message = generateWelcomeMessage()
        return "${message.text}\nSent at: ${message.timestamp}\nRunning on: ${platform.name}!"
    }
    
    // Example of a private function within the shared module
    private fun generateWelcomeMessage(): Message {
        val currentTime = kotlinx.datetime.Clock.System.now().toEpochMilliseconds()
        return Message("Hello from KMP!", currentTime)
    }
    

    }

    // We need kotlinx-datetime library for Clock.System.now()
    // Add it to shared/build.gradle.kts in commonMain dependencies
    “`

  2. 添加依赖: 为了使用 kotlinx.datetime,需要将其添加到 shared/build.gradle.ktscommonMain 依赖中:

    kotlin
    // shared/build.gradle.kts
    kotlin {
    sourceSets {
    val commonMain by getting {
    dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") // Example version, check for latest
    // Add other common dependencies here
    }
    }
    // ... other source sets
    }
    // ... rest of the config
    }

    修改完 build.gradle.kts 文件后,IDE 会提示你同步 Gradle 项目 (Sync Now)。

  3. 实现 Platform (如果向导未完全生成): 确保 Platformexpect 声明在 commonMain 中,并且在 androidMainiosMain 中有对应的 actual 实现,如之前“核心概念”部分的示例所示。

在平台应用中使用共享代码

现在,我们可以在 Android 和 iOS 应用中调用 shared 模块的 Greeting 类。

Android (androidApp):

  1. 打开 androidApp/src/main/java/com/example/mykmpapp/android/MainActivity.kt (路径可能略有不同)。
  2. 修改 MainActivity 来使用 Greeting 类:

    “`kotlin
    package com.example.mykmpapp.android // Use your package name

    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.widget.TextView
    import com.example.mykmpapp.shared.Greeting // Import the shared class

    class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

        val tv: TextView = findViewById(R.id.text_view) // Assume you have a TextView with this ID in layout
        tv.text = Greeting().greet() // Call the shared code
    }
    

    }
    ``
    3. 确保你的
    activity_main.xml布局文件中有一个TextView,其 ID 为text_view
    4. 运行
    androidApp` 配置,应用启动后应该会显示来自共享代码的问候语,包含平台信息。

iOS (iosApp):

  1. 同步 Pods (如果使用 CocoaPods):iosApp 目录下打开终端,运行 pod installpod update。这会将 shared 模块编译成 iOS Framework 并链接到 Xcode 项目。
  2. 打开 Xcode 项目: 双击 iosApp/iosApp.xcworkspace (不是 .xcodeproj) 文件,在 Xcode 中打开项目。
  3. 修改 UI 代码 (例如 ContentView.swift):

    • 首先,你需要导入共享模块。在 Swift 中,共享模块的名称通常会被转换(例如,shared 模块可能导入为 Sharedshared,取决于你的配置)。
    • 修改 ContentView.swift 来调用 Greeting 类:

    “`swift
    import SwiftUI
    import shared // Import the shared module (name might vary based on config)

    struct ContentView: View {
    // Get the greeting text from the shared module
    let greetText = Greeting().greet() // Create an instance and call the method

    var body: some View {
        // Display the text in a SwiftUI Text view
        Text(greetText)
    }
    

    }

    struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
    ContentView()
    }
    }
    ``
    * **注意:** Kotlin 类在 Swift 中的使用方式:
    * 类名通常保持不变(
    Greeting)。
    * 方法名通常也保持不变(
    greet())。
    * 你需要创建一个类的实例
    Greeting()来调用它的方法。
    * Kotlin 的
    Int,String,Boolean,List,Map等基本类型和集合会被自动桥接到 Swift 的对应类型(Int32/Int64,String,Bool,Array,Dictionary`)。
    * 更复杂的类型或挂起函数(coroutines)需要特别处理(通常通过回调、Completion Handlers 或使用社区库如 KMP-NativeCoroutines)。
    4. 运行 iOS 应用: 在 Xcode 中选择一个模拟器或连接的设备,然后点击运行按钮 (▶)。应用启动后,应该在界面上看到来自共享模块的相同逻辑生成的问候语,但这次显示的是 iOS 平台信息。

构建和运行

  • 构建共享模块: 你可以在 IntelliJ IDEA/Android Studio 的 Gradle 面板中找到 shared 模块下的构建任务(如 build, assemble, 或特定平台的编译任务如 linkDebugFrameworkIos)。通常,当你运行平台应用时,Gradle 会自动构建其依赖的 shared 模块。
  • 运行 Android 应用: 在 IDE 中选择 androidApp 配置,然后点击运行或调试按钮。
  • 运行 iOS 应用: 在 Xcode 中打开 .xcworkspace 文件,选择目标设备/模拟器,然后点击运行。首次在 Xcode 中运行可能需要一些时间来完成 Kotlin/Native 的编译。

深入探索 KMP

这个入门指南只是冰山一角。要精通 KMP,你还需要探索:

  • 依赖管理: 如何为 commonMain 和平台特定源集添加和管理 KMP 库依赖。
  • 网络请求: 使用 Ktor 库在 commonMain 中实现跨平台的 HTTP 客户端。
  • 数据持久化: 使用 SQLDelight 生成类型安全的 Kotlin SQL 接口,或使用 Realm Kotlin SDK 等 KMP 数据库方案。
  • 序列化: 使用 kotlinx.serialization 在各平台间可靠地序列化和反序列化数据(如 JSON)。
  • 并发: 使用 Kotlin Coroutines (kotlinx.coroutines) 在共享代码中处理异步操作,并了解其在不同平台(尤其是 Native)上的行为。
  • 架构模式: 如何在 KMP 项目中应用 MVVM、MVI 等架构模式,将共享逻辑有效地组织起来。
  • 测试: 编写 commonTest (使用 kotlin.test) 以及平台特定的测试 (androidTest, iosTest)。
  • Compose Multiplatform: 如果你想进一步共享 UI 代码,可以探索 Jetpack Compose 的多平台版本,允许你用 Kotlin 编写 Android、iOS (Alpha/Experimental)、Desktop 和 Web 的 UI。
  • 与现有项目集成: 如何将 KMP 共享模块逐步引入到已有的原生 Android 和 iOS 代码库中。

结论

Kotlin Multiplatform (KMP) 提供了一种务实且强大的方法来应对跨平台开发的挑战。它允许开发者在保持原生性能和体验的同时,最大限度地重用 Kotlin 编写的核心逻辑代码。虽然它有学习曲线和一些挑战,但其带来的效率提升、代码一致性和开发体验优势使其成为现代应用开发中一个极具吸引力的选择。

通过本指南,你已经了解了 KMP 的基本概念、搭建了环境、创建并运行了第一个 KMP 应用。现在,是时候深入实践,探索 KMP 的更多功能和库,开始在你的项目中利用 Kotlin 构建跨平台的未来吧! KMP 的世界广阔,充满机遇,祝你学习愉快!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部