KMP实战教程:从零开始构建你的第一个跨平台应用
在移动开发的浪潮中,“一次编写,处处运行”的梦想始终是开发者追逐的圣杯。从早期的 Hybrid 技术到后来的 React Native、Flutter,每一种方案都在尝试以自己的方式解决跨平台开发的效率与体验难题。今天,我们将深入探讨一个由 JetBrains 官方支持、正迅速崛起的强大选手——Kotlin Multiplatform (KMP),并手把手带你构建第一个真正意义上的跨平台应用。
本文是一篇详尽的实战教程,无论你是经验丰富的安卓开发者,还是对跨平台充满好奇的 iOS 开发者或后端工程师,都能在这里找到清晰的路径,开启你的 KMP 之旅。
第一章:破冰 KMP —— 它是什么,又不是什么?
在动手之前,我们必须清晰地理解 KMP 的核心哲学。
1.1 KMP 的核心思想:共享逻辑,原生 UI
与 Flutter 或 React Native 试图统一 UI 层不同,KMP 采取了一种更为务实和灵活的策略:它专注于共享非 UI 的业务逻辑,而将 UI 的实现完全交还给各个平台原生处理。
这意味着:
* 共享层 (Shared Logic): 你可以使用纯 Kotlin 编写数据模型、网络请求、数据库操作、业务算法、状态管理等核心逻辑。这部分代码被编译成适用于不同平台的模块(例如安卓的 AAR 库,iOS 的 Framework)。
* 平台层 (Platform UI):
* 在 Android 端,你可以使用你最熟悉的 Jetpack Compose 或传统的 XML 布局来构建界面,无缝调用共享层的 Kotlin 代码。
* 在 iOS 端,你可以使用 SwiftUI 或 UIKit,同样轻松地调用由 KMP 编译出的 Swift/Objective-C 兼容的框架。
这种模式带来了无与伦比的优势:
1. 极致的原生性能与体验: UI 是 100% 原生的,用户可以享受到最流畅、最符合平台习惯的操作体验。
2. 代码复用最大化: 核心业务逻辑只需编写和测试一次,大大减少了开发和维护成本。
3. 无缝访问平台 API: 当你需要使用特定平台的功能(如相机、GPS、HealthKit)时,KMP 提供了强大的 expect
/actual
机制,让你可以在共享层定义一个“期望”接口,然后在各个平台层给出“实际”实现。
4. 渐进式集成: 你可以将 KMP 集成到任何现有的原生项目中,逐步替换共享模块,而无需重写整个应用。
1.2 KMP vs KMM
你可能听说过 KMM (Kotlin Multiplatform Mobile)。实际上,KMP 是 KMM 的超集。KMM 特指用于 Android 和 iOS 移动开发的场景,而 KMP 的目标更为宏大,它还支持桌面端 (Desktop)、Web (Wasm) 等。从 Kotlin 1.9.20 开始,官方已将 KMM 的概念统一为 KMP,以体现其更广阔的应用范围。本文我们将聚焦于最主流的 Android 和 iOS 平台。
第二章:厉兵秣马 —— 搭建你的 KMP 开发环境
工欲善其事,必先利其器。一个顺畅的开发环境是成功的一半。
必备工具清单:
- Android Studio: 建议使用最新稳定版(例如 Hedgehog 或更高版本)。它集成了创建和管理 KMP 项目所需的一切工具。
- Xcode: iOS 开发的唯一选择。你需要它来编写 SwiftUI/UIKit 代码、运行 iOS 模拟器以及将应用部署到真实设备。
- Kotlin Multiplatform Mobile 插件: 在 Android Studio 的
Settings/Preferences > Plugins
中搜索并安装。这是 KMP 项目向导和IDE支持的核心。 - Java Development Kit (JDK): 通常 Android Studio 会自带合适的 JDK 版本,无需单独安装。
- CocoaPods: iOS 的依赖管理工具。如果你的 Mac 上没有安装,可以通过终端运行
sudo gem install cocoapods
来安装。
环境检查完毕后,我们就可以开始创建项目了。
第三章:开天辟地 —— 创建你的第一个 KMP 项目
启动 Android Studio,让我们通过官方模板来创建项目。
- 点击
File > New > New Project...
。 - 在左侧模板列表中,选择 Kotlin Multiplatform。
- 在右侧选择 Kotlin Multiplatform App 模板,然后点击
Next
。 - 配置项目:
- Name: 为你的应用取一个名字,例如
KMPDemo
。 - Package name: 设置你的包名,例如
com.example.kmpdemo
。 - Save location: 选择项目存放路径。
- iOS framework distribution: 这里选择 CocoaPods dependency manager。这是目前最稳定和推荐的方式,它会自动帮你管理 Xcode 项目对共享模块的依赖。
- Name: 为你的应用取一个名字,例如
- 点击
Finish
。Android Studio 会开始下载依赖、配置 Gradle。这个过程可能需要几分钟,请耐心等待。
项目结构导览
项目创建成功后,你会看到一个全新的、与传统 Android 项目略有不同的结构。我们重点关注以下部分:
iosApp
: 存放所有与 iOS 应用相关的代码,包括 Xcode 项目文件 (iosApp.xcodeproj
)、SwiftUI/UIKit 视图、资源文件等。androidApp
: 存放所有与 Android 应用相关的代码,包括MainActivity
、Compose 视图、AndroidManifest.xml
等。shared
: 这 KMP 项目的核心! 所有的共享代码都在这里。src/commonMain/kotlin
: 通用代码区。这里是编写平台无关的纯 Kotlin 代码的地方。我们 90% 的共享逻辑将在这里实现。src/androidMain/kotlin
: Android 专属实现区。用于实现commonMain
中定义的actual
声明,或编写仅 Android 需要的辅助代码。src/iosMain/kotlin
: iOS 专属实现区。用于实现commonMain
中定义的actual
声明,或编写仅 iOS 需要的辅助代码。
现在,你可以尝试分别运行 Android 和 iOS 应用。
* 运行 Android: 在顶部的配置下拉菜单中选择 androidApp
,然后点击绿色的运行按钮。
* 运行 iOS: 选择 iosApp
,然后点击运行。Android Studio 会自动启动 Xcode,并在 iOS 模拟器中运行应用。
如果一切顺利,你会看到两个平台上都显示着 “Hello, [Platform]!” 的简单应用。恭喜你,你的 KMP 开发环境已经畅通无阻!
第四章:牛刀小试 —— 构建一个随机语录生成器
理论讲了这么多,是时候写真正的共享代码了。我们的目标是创建一个简单的应用:点击按钮,显示一条随机的励志语录。
4.1 在 commonMain
中定义共享逻辑
我们的核心逻辑是“提供随机语录”,这个逻辑与平台无关。
-
创建数据模型:
在shared/src/commonMain/kotlin
下,右键你的包名目录,选择New > Kotlin Class/File
,创建一个名为Quote.kt
的文件。“`kotlin
// file: shared/src/commonMain/kotlin/com/example/kmpdemo/Quote.kt
package com.example.kmpdemodata class Quote(
val text: String,
val author: String
)
“` -
创建数据仓库 (Repository):
同样在commonMain
下,创建一个QuoteRepository.kt
文件。这个类将负责存储和提供语录。“`kotlin
// file: shared/src/commonMain/kotlin/com/example/kmpdemo/QuoteRepository.kt
package com.example.kmpdemoclass QuoteRepository {
private val quotes = listOf(
Quote(“The only way to do great work is to love what you do.”, “Steve Jobs”),
Quote(“Innovation distinguishes between a leader and a follower.”, “Steve Jobs”),
Quote(“Stay hungry, stay foolish.”, “Steve Jobs”),
Quote(“The future belongs to those who believe in the beauty of their dreams.”, “Eleanor Roosevelt”),
Quote(“Strive not to be a success, but rather to be of value.”, “Albert Einstein”)
)fun getRandomQuote(): Quote { return quotes.random() }
}
“`
至此,我们的核心业务逻辑已经完成,并且是 100% 共享的!
4.2 连接 Android UI (Jetpack Compose)
现在,我们让 Android 应用使用这个 QuoteRepository
。
- 打开
androidApp/src/main/java/com/example/kmpdemo/android/MainActivity.kt
。 -
修改
MainActivity
和GreetingView
,让它们能够展示和更新语录。“`kotlin
// file: androidApp/src/main/java/com/example/kmpdemo/android/MainActivity.kt
package com.example.kmpdemo.androidimport android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.
import androidx.compose.runtime.
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.kmpdemo.QuoteRepository // 导入共享模块的类class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
QuoteScreen()
}
}
}
}
}@Composable
fun QuoteScreen() {
// 1. 创建 Repository 实例
val repository = remember { QuoteRepository() }// 2. 使用 remember 和 mutableStateOf 来管理状态 var currentQuote by remember { mutableStateOf(repository.getRandomQuote()) } Column( modifier = Modifier .fillMaxSize() .padding(16.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "\"${currentQuote.text}\"", fontSize = 24.sp, textAlign = TextAlign.Center ) Text( text = "- ${currentQuote.author}", fontSize = 18.sp, modifier = Modifier.padding(top = 8.dp) ) Button( onClick = { // 3. 点击按钮时,从 repository 获取新语录并更新状态 currentQuote = repository.getRandomQuote() }, modifier = Modifier.padding(top = 24.dp) ) { Text("Get Another Quote") } }
}
“` -
再次运行
androidApp
。你会看到一个显示随机语录的界面,点击按钮即可刷新。
4.3 连接 iOS UI (SwiftUI)
这是最激动人心的部分:在完全不同的平台和语言上,复用我们刚才编写的 Kotlin 逻辑。
- 打开 Xcode 项目: 在 Android Studio 的项目视图中,右键
iosApp
文件夹,选择Open in Xcode
。 - 找到 ContentView.swift: 在 Xcode 的文件导航器中,找到
iosApp > ContentView.swift
并打开它。 -
编写 SwiftUI 代码: 修改
ContentView
以实现相同的功能。“`swift
// file: iosApp/ContentView.swift
import SwiftUI
import shared // 1. 导入由 KMP 生成的共享框架struct ContentView: View {
// 2. 创建 Repository 实例
private let repository = QuoteRepository()// 3. 使用 @State 来管理语录状态 @State private var currentQuote: Quote // 初始化器,用于设置初始语录 init() { _currentQuote = State(initialValue: repository.getRandomQuote()) } var body: some View { VStack { Spacer() Text("\"\(currentQuote.text)\"") .font(.title) .multilineTextAlignment(.center) .padding() Text("- \(currentQuote.author)") .font(.headline) .padding(.top, 8) Spacer() Button(action: { // 4. 点击按钮时,调用共享模块的方法更新状态 self.currentQuote = self.repository.getRandomQuote() }) { Text("Get Another Quote") .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) } .padding(.bottom, 50) } .padding() }
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
“`
4. 运行 iOS 应用: 在 Xcode 中,选择一个模拟器并点击运行按钮。你将看到一个功能与 Android 版完全相同的 iOS 应用!
我们刚刚完成了 KMP 开发中最核心的流程:编写一次业务逻辑,在 Android 和 iOS 两个平台上通过各自原生的 UI 框架进行调用和展示。
第五章:精雕细琢 —— 使用 expect/actual 处理平台差异
KMP 的强大之处不止于共享通用代码,更在于它能优雅地处理平台差异。expect
/actual
机制是实现这一点的关键。
假设我们想在语录下方显示当前的平台信息(例如 “Running on Android 33” 或 “Running on iOS 16.2″)。这个功能显然是平台相关的。
5.1 在 commonMain
中定义“期望”
在 shared/src/commonMain/kotlin
下,创建一个 Platform.kt
文件,并定义一个 expect
函数。expect
就像一个接口,它声明了一个函数(或类、属性)的存在,但没有具体实现。
“`kotlin
// file: shared/src/commonMain/kotlin/com/example/kmpdemo/Platform.kt
package com.example.kmpdemo
expect fun getPlatformName(): String
``
expect
此时,IDE 会提示你函数缺少
actual` 实现。
5.2 在 androidMain
中提供“实际”实现
在 shared/src/androidMain/kotlin
目录下,同样创建 Platform.kt
文件,并使用 actual
关键字提供 Android 平台的具体实现。
“`kotlin
// file: shared/src/androidMain/kotlin/com/example/kmpdemo/Platform.kt
package com.example.kmpdemo
import android.os.Build
actual fun getPlatformName(): String {
return “Android ${Build.VERSION.RELEASE}”
}
“`
5.3 在 iosMain
中提供“实际”实现
同样地,在 shared/src/iosMain/kotlin
目录下创建 Platform.kt
文件,提供 iOS 的实现。
“`kotlin
// file: shared/src/iosMain/kotlin/com/example/kmpdemo/Platform.kt
package com.example.kmpdemo
import platform.UIKit.UIDevice
actual fun getPlatformName(): String {
return “${UIDevice.current.systemName} ${UIDevice.current.systemVersion}”
}
“`
5.4 在共享代码和原生 UI 中使用
现在,我们可以在任何地方(包括 commonMain
)调用 getPlatformName()
,编译器会自动在构建不同平台时链接到正确的 actual
实现。
修改 Android UI (MainActivity.kt
):
kotlin
// In QuoteScreen Composable
// ...
Button(...)
Text(
text = getPlatformName(), // 直接调用
modifier = Modifier.padding(top = 16.dp),
color = Color.Gray
)
修改 iOS UI (ContentView.swift
):
“`swift
// In ContentView body
// …
Button(…) { … }
Text(getPlatformName()) // 在 Swift 中,KMP 会自动生成全局函数
.padding(.top, 16)
.foregroundColor(.gray)
“`
现在再次运行两个应用,你会看到它们各自显示了正确的平台信息。expect/actual
机制的优雅和强大尽显无疑。
第六章:展望未来 —— KMP 生态与下一步
我们刚刚完成的只是 KMP 世界的冰山一角。当你准备构建更复杂的应用时,KMP 成熟的生态系统将为你提供强大支持:
- 网络请求: 使用 Ktor,一个由 JetBrains 开发的纯 Kotlin、跨平台的异步网络框架。
- 数据库: 使用 SQLDelight,它可以根据你的 SQL 语句在编译时生成类型安全的 Kotlin API,支持多平台。
- 依赖注入: Koin 或 Kodein 等流行的 Kotlin DI 框架都已完美支持 KMP。
- 异步编程: Kotlin Coroutines (协程) 是 KMP 中处理异步任务的一等公民,它在所有平台上提供了一致的、结构化的并发模型。
- Compose Multiplatform: 这是 KMP 的下一个进化方向。除了共享业务逻辑,我们还可以使用 Jetpack Compose 的声明式语法来编写跨平台的 UI,目前已稳定支持 Android、Desktop 和 Web (Wasm),iOS 版本的支持也已进入 Alpha 阶段,未来可期。
结语
通过这个从零到一的实战教程,我们不仅构建了一个功能完整的跨平台应用,更重要的是,我们掌握了 Kotlin Multiplatform 的核心思想和开发流程。KMP 不是又一个试图用 Web 技术包裹原生外壳的框架,而是一个尊重平台特性、追求极致性能和开发效率的“集大成者”。它将 Kotlin 语言的现代化、安全性与原生开发的灵活性、深度完美结合。
现在,你已经站在了 KMP 开发的起点上。继续探索,将你自己的想法付诸实践,你会发现,在代码共享与原生体验之间,KMP 已经为你铺就了一条康庄大道。