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 的运作方式需要掌握几个关键概念:
- 共享模块 (Shared Module / Common Module): 这是 KMP 项目的核心。它包含平台无关的 Kotlin 代码。通常,这个模块的源代码位于
src/commonMain/kotlin
目录下。这里定义的类、函数、接口等都可以在所有目标平台中使用。 - 平台特定模块 (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 目标。
-
expect
/actual
机制: 这是 KMP 实现平台特定功能的关键。当共享代码需要访问某个平台独有的 API 时(例如获取设备 ID、文件系统访问、日期格式化等),你可以在commonMain
中使用expect
关键字声明一个类、函数、属性或接口,表明“期望”各个平台提供这个功能的实现。-
expect
声明 (incommonMain
): 定义了一个契约,但不包含具体实现。
“`kotlin
// src/commonMain/kotlin/com/example/Platform.kt
package com.example.kmpappexpect 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.kmpappimport android.os.Build
import java.util.UUID
import android.provider.Settingsactual 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.kmpappimport platform.UIKit.UIDevice
import platform.Foundation.NSUUIDactual 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().name或
getDeviceUUID(),而 KMP 编译器会确保在编译到特定平台时,调用的是该平台对应的
actual` 实现。
-
-
Gradle 构建系统: KMP 项目严重依赖 Gradle 进行构建、依赖管理和多平台编译配置。项目根目录和共享模块下通常都有
build.gradle.kts
文件,用于定义目标平台、源集、依赖项和编译选项。
KMP 的主要优势
选择 KMP 能带来诸多好处:
- 显著的代码重用: 这是最核心的优势。业务逻辑、数据处理、网络层等只需编写一次,减少开发时间和成本。代码库更小,更易于维护。
- 平台一致性: 由于核心逻辑共享,不同平台应用的行为和业务规则能够保持高度一致,减少因平台差异导致的 Bug。
- 原生性能和体验: KMP 编译后的代码是对应平台原生的(JVM 字节码、JavaScript、原生二进制码)。它不像某些跨平台 UI 框架那样需要桥接或虚拟机,因此可以获得接近原生开发的性能。同时,UI 部分仍然可以使用平台原生技术(Android XML/Jetpack Compose, iOS UIKit/SwiftUI)构建,确保最佳的用户体验和平台特性利用。
- 利用 Kotlin 语言优势: 开发者可以享受 Kotlin 带来的现代语言特性,如空安全、协程(简化异步编程)、扩展函数、数据类、密封类等,这些特性在所有目标平台上都可用,提升开发效率和代码质量。
- 逐步迁移和集成: KMP 可以轻松集成到现有的 Android 和 iOS 项目中。你可以先将一小部分逻辑(如下载器、验证器)迁移到共享模块,逐步扩大共享范围,风险可控。
- 活跃的生态和社区: KMP 生态系统正在快速发展,涌现出许多支持多平台的库(如 Ktor 用于网络、SQLDelight 用于数据库、kotlinx.serialization 用于序列化、 kotlinx.coroutines 用于并发)。JetBrains 和社区也在持续投入,不断完善工具链和库支持。
KMP 的挑战与考量
尽管 KMP 非常有前景,但在采用前也需要了解一些潜在的挑战:
- 平台特定 API 的处理: 虽然
expect
/actual
很强大,但频繁或复杂地使用它会增加共享代码的复杂性。需要仔细设计哪些逻辑应该共享,哪些应该保留在平台层。 - 库的支持: 虽然 KMP 库生态在增长,但可能仍然缺少某些特定平台原生库的直接 KMP 替代品。有时需要自己编写
expect
/actual
封装,或者寻找社区提供的库。 - 构建时间和复杂性: KMP 项目的 Gradle 配置相对复杂,尤其是涉及多个 Native 目标时。编译时间,特别是首次编译或清理后编译 Kotlin/Native 代码,可能会比较长。
- 学习曲线: 对于团队来说,需要掌握 Kotlin 语言、KMP 概念、Gradle 多平台配置以及 Kotlin/Native 的工作原理(尤其是与 iOS 的互操作性)。iOS 开发者可能还需要适应 Kotlin 和 Gradle。
- 团队协作: 需要 Android 和 iOS (以及其他平台) 开发者紧密协作,共同维护共享代码。对共享模块的修改需要考虑所有目标平台的影响。
- 调试: 跨平台调试有时会比纯原生开发更具挑战性,尤其是在
expect
/actual
边界或 Kotlin/Native 与原生平台交互的部分。
环境搭建
要开始 KMP 开发,你需要准备以下工具:
- IntelliJ IDEA 或 Android Studio: 这是主要的开发环境。推荐使用最新稳定版本。Android Studio 对于包含 Android 目标的 KMP 项目特别方便。
- 确保安装了 Kotlin 插件(通常随 IDE 自动安装或更新)。
- 安装 Kotlin Multiplatform Mobile 插件(即使你不只做移动端,这个插件也提供了很好的项目模板和工具集成)。
- JDK (Java Development Kit): KMP 依赖 JVM,需要安装 JDK (推荐版本 11 或更高)。
- Xcode: 如果你的目标平台包含 iOS 或 macOS,你需要安装最新版本的 Xcode(可从 Mac App Store 获取)及其命令行工具。Xcode 用于编译和运行 iOS/macOS 应用,并提供必要的 SDK 和模拟器。
- (可选) KDoctor: 这是一个命令行工具,可以帮助你检查 KMP 开发环境(Kotlin 插件、Android Studio、Xcode、CocoaPods 等)是否配置正确。在终端运行
brew install kdoctor
(如果使用 Homebrew),然后运行kdoctor
进行诊断。 - (可选) CocoaPods: 如果你的 iOS 项目使用 CocoaPods 管理依赖,Kotlin/Native 可以将共享模块打包成一个 Pod,方便集成。你需要安装 CocoaPods (
sudo gem install cocoapods
)。
创建你的第一个 KMP 项目
最简单的方式是使用 IntelliJ IDEA 或 Android Studio 提供的 KMP 项目向导:
- 启动 IDE: 打开 IntelliJ IDEA 或 Android Studio。
- 新建项目: 选择 “File” > “New” > “Project…”。
- 选择模板: 在左侧面板中,选择 “Kotlin Multiplatform”。在右侧,你会看到不同的模板:
- Kotlin Multiplatform App: 一个包含 Android 和 iOS 客户端应用,以及共享模块的基本模板。这是入门的最佳选择。
- Kotlin Multiplatform Library: 用于创建一个可被其他 KMP 项目或原生项目使用的共享库。
- 其他可能还有 Web、Desktop 等模板,具体取决于你的 IDE 和插件版本。
- 选择 Kotlin Multiplatform App。
- 配置项目:
- Project Name: 输入你的项目名称(例如
MyKmpApp
)。 - Package Name: 定义你的基础包名(例如
com.example.mykmpapp
)。 - Project Location: 选择项目存储路径。
- Android App Name: Android 应用的显示名称。
- iOS App Name: iOS 应用的显示名称。
- Shared Module Name: 共享模块的名称(通常是
shared
或common
)。 - iOS Framework Distribution: 选择如何将共享模块集成到 iOS 项目中。
- Regular framework: 标准的动态或静态框架。
- CocoaPods dependency: 将共享模块打包成一个 CocoaPod,通过 Podfile 集成。对于熟悉 CocoaPods 的 iOS 开发者来说,这通常是首选。
- Add sample tests for your project: 勾选此项会自动生成一些测试示例。
- Project Name: 输入你的项目名称(例如
- 完成创建: 点击 “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.xcodeproj
或iosApp.xcworkspace
: Xcode 项目文件。Podfile
(如果选择了 CocoaPods): 定义 iOS 项目的依赖,包括指向本地shared
模块的 Pod。iosApp/
: 包含 Swift/Objective-C 代码(如ContentView.swift
,AppDelegate.swift
等),用于构建 UI 和调用shared
模块的功能。
编写共享代码
让我们在 shared
模块中添加一些简单的共享逻辑。
-
创建数据类: 在
shared/src/commonMain/kotlin
下创建一个新的 Kotlin 文件,例如Greeting.kt
。“`kotlin
package com.example.mykmpapp.shared // Use your package namedata class Message(val text: String, val timestamp: Long)
class Greeting {
private val platform: Platform = Platform() // Using the expect/actual Platform classfun 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
“` -
添加依赖: 为了使用
kotlinx.datetime
,需要将其添加到shared/build.gradle.kts
的commonMain
依赖中: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)。 -
实现
Platform
(如果向导未完全生成): 确保Platform
的expect
声明在commonMain
中,并且在androidMain
和iosMain
中有对应的actual
实现,如之前“核心概念”部分的示例所示。
在平台应用中使用共享代码
现在,我们可以在 Android 和 iOS 应用中调用 shared
模块的 Greeting
类。
Android (androidApp
):
- 打开
androidApp/src/main/java/com/example/mykmpapp/android/MainActivity.kt
(路径可能略有不同)。 -
修改
MainActivity
来使用Greeting
类:“`kotlin
package com.example.mykmpapp.android // Use your package nameimport androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import com.example.mykmpapp.shared.Greeting // Import the shared classclass 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 }
}
``
activity_main.xml
3. 确保你的布局文件中有一个
TextView,其 ID 为
text_view。
androidApp` 配置,应用启动后应该会显示来自共享代码的问候语,包含平台信息。
4. 运行
iOS (iosApp
):
- 同步 Pods (如果使用 CocoaPods): 在
iosApp
目录下打开终端,运行pod install
或pod update
。这会将shared
模块编译成 iOS Framework 并链接到 Xcode 项目。 - 打开 Xcode 项目: 双击
iosApp/iosApp.xcworkspace
(不是.xcodeproj
) 文件,在 Xcode 中打开项目。 -
修改 UI 代码 (例如
ContentView.swift
):- 首先,你需要导入共享模块。在 Swift 中,共享模块的名称通常会被转换(例如,
shared
模块可能导入为Shared
或shared
,取决于你的配置)。 - 修改
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 methodvar body: some View { // Display the text in a SwiftUI Text view Text(greetText) }
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
``
Greeting
* **注意:** Kotlin 类在 Swift 中的使用方式:
* 类名通常保持不变()。
greet()
* 方法名通常也保持不变()。
Greeting()
* 你需要创建一个类的实例来调用它的方法。
Int
* Kotlin 的,
String,
Boolean,
List,
Map等基本类型和集合会被自动桥接到 Swift 的对应类型(
Int32/
Int64,
String,
Bool,
Array,
Dictionary`)。
* 更复杂的类型或挂起函数(coroutines)需要特别处理(通常通过回调、Completion Handlers 或使用社区库如 KMP-NativeCoroutines)。
4. 运行 iOS 应用: 在 Xcode 中选择一个模拟器或连接的设备,然后点击运行按钮 (▶)。应用启动后,应该在界面上看到来自共享模块的相同逻辑生成的问候语,但这次显示的是 iOS 平台信息。 - 首先,你需要导入共享模块。在 Swift 中,共享模块的名称通常会被转换(例如,
构建和运行
- 构建共享模块: 你可以在 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 的世界广阔,充满机遇,祝你学习愉快!