Kotlin Multiplatform 允许你使用 Kotlin 编写代码,并将其在多个平台(如 Android、iOS、Web 等)上运行。它不是一个像 Flutter 那样的UI框架,而更侧重于在不同平台间共享代码逻辑,同时保留原生UI和性能的优势。我将从核心概念讲起,并带你通过一个实战项目来理解它的工作方式。
什么是 Android KMP?
简单来说,Android KMP 通常指的是使用 Kotlin Multiplatform Mobile 进行 Android 与 iOS 的跨平台开发。它的核心思想是:在共享模块中编写一次业务逻辑(如网络请求、数据存储、状态管理等),然后在 Android 和 iOS 平台上分别编写原生用户界面,调用这些共享逻辑。
这种模式的优势很明显:
- 代码复用率高:可以节省30%-50%的开发时间,尤其是在业务逻辑复杂的场景下。
- 原生性能与体验:UI 是原生的(Jetpack Compose 和 SwiftUI),没有性能损耗,也能直接调用平台特定功能。
- 灵活性高:你可以选择从共享一个模块开始,逐步将整个应用迁移到 KMP,无需一次性重写所有代码。
- 逻辑一致性:共享逻辑确保了 Android 和 iOS 上的功能和行为完全一致,减少了因双端实现不同而产生的 Bug。
KMP 实战:构建一个跨平台的用户资料页面
为了让你有更直观的感受,我们通过一个具体的案例来拆解 KMP 的开发流程。这个案例将构建一个用户资料页面,包含获取数据、共享UI组件和平台特定实现。
案例概述
我们将创建一个简单的跨平台应用,它能够:
- 共享业务逻辑:从一个假想的 API 获取用户资料。
- 共享 UI 组件:使用 Compose Multiplatform 创建一个在 Android 和 iOS 上都能显示的资料卡片。
- 处理平台差异:使用
expect/actual机制,创建一个在 Android 上是 Material Design 按钮、在 iOS 上是原生UIButton的平台特定按钮。
1. 项目结构
一个典型的 KMP 项目主要包含三个部分:
shared:这是核心模块,包含了我们所有的 Kotlin 共享代码(业务逻辑、数据模型、共享UI)。androidApp:Android 应用模块,它依赖shared模块,并使用 Jetpack Compose 构建UI。iosApp:iOS 应用模块,它依赖shared模块(作为一个 framework),并使用 SwiftUI 构建UI。
2. 编写共享业务逻辑 (shared)
在 shared 模块的 commonMain 代码集中,我们使用 Ktor 进行网络请求,用 kotlinx.coroutines 处理异步。
// --- 文件: shared/src/commonMain/kotlin/com/example/app/DataModels.kt ---
// 共享的数据模型
data class User(
val id: String,
val name: String,
val email: String,
val profileImageUrl: String
)
// --- 文件: shared/src/commonMain/kotlin/com/example/app/Repository.kt ---
// 共享的业务逻辑层 - 仓库
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
interface UserRepository {
suspend fun getUserProfile(): Flow<User>
}
class UserRepositoryImpl(private val api: Api) : UserRepository {
override suspend fun getUserProfile(): Flow<User> {
return flow { emit(api.fetchUser()) }
}
}
// --- 文件: shared/src/commonMain/kotlin/com/example/app/Api.kt ---
// 共享的网络层接口
import io.ktor.client.HttpClient
import io.ktor.client.request.get
interface Api {
suspend fun fetchUser(): User
}
class ApiImpl(private val client: HttpClient) : Api {
override suspend fun fetchUser(): User {
// 这里简化了,实际会用Ktor发起请求并解析JSON
return client.get("https://api.example.com/user")
}
}
这部分代码(数据类、仓库、API接口)在 Android 和 iOS 上是完全一致且共享的。
3. 处理平台差异 (expect/actual)
有时我们需要调用平台特有的API(如获取设备ID、显示原生弹窗)。KMP 提供了 expect/actual 关键字来处理这种差异。
我们在 commonMain 中声明一个 "期待" (expect) 的函数或类。
// 文件: shared/src/commonMain/kotlin/com/example/app/Platform.kt
expect fun getPlatformName(): String
然后,在 androidMain 中提供 "实际" (actual) 的 Android 实现。
// 文件: shared/src/androidMain/kotlin/com/example/app/Platform.android.kt
actual fun getPlatformName(): String {
return "Android ${android.os.Build.VERSION.SDK_INT}"
}
在 iosMain 中提供 "实际" (actual) 的 iOS 实现。
// 文件: shared/src/iosMain/kotlin/com/example/app/Platform.ios.kt
import platform.UIKit.UIDevice
actual fun getPlatformName(): String {
return UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
这样,共享代码就可以调用 getPlatformName(),而实际执行的是对应平台的代码。
4. 构建共享UI (shared 中的 Compose)
使用 Compose Multiplatform,我们可以在 shared 模块中创建 UI 组件,这些组件能直接在 Android 和 iOS 上运行。
// 文件: shared/src/commonMain/kotlin/com/example/app/ProfileScreen.kt
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage // 注意:图片加载库可能需要平台特定配置
@Composable
fun ProfileScreen(user: User) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// 头像
AsyncImage(
model = user.profileImageUrl,
contentDescription = "Profile Image",
modifier = Modifier.size(64.dp)
)
Spacer(modifier = Modifier.height(8.dp))
// 用户名
Text(text = user.name, style = MaterialTheme.typography.h5)
// 邮箱
Text(text = user.email, style = MaterialTheme.typography.body1)
Spacer(modifier = Modifier.height(16.dp))
// 平台特定按钮,将在下一步定义
PlatformButton(label = "More Info") {
// 处理点击
}
}
}
5. 在原生项目中集成
Android (androidApp):
Android 项目可以直接使用 Jetpack Compose 调用我们刚才创建的 ProfileScreen,就像调用普通的 Composable 函数一样。
// 文件: androidApp/src/main/java/com/example/app/MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
// 直接使用共享的 Composable
ProfileScreen(user = sampleUser)
}
}
}
}
iOS (iosApp):
在 iOS 中,我们需要使用 ComposeUIViewController 将 Compose 代码包装成一个 UIViewController,然后嵌入到 SwiftUI 中。
// 文件: iosApp/iosApp/ContentView.swift
import SwiftUI
import shared // 导入共享模块
struct ContentView: View {
let user: User // 假设从共享模块获取
var body: some View {
VStack {
Text("Native SwiftUI Header")
.font(.headline)
// 将 Compose UI 嵌入 SwiftUI
ComposeProfileScreen(user: user)
.frame(height: 300)
List {
Text("Native SwiftUI Item 1")
Text("Native SwiftUI Item 2")
}
}
}
}
// 用于包装 Compose UI 的辅助结构体
struct ComposeProfileScreen: UIViewControllerRepresentable {
let user: User
func makeUIViewController(context: Context) -> UIViewController {
// 这个函数由 KMP 插件生成,用于创建包含 Compose UI 的 ViewController
return App_ProfileScreenKt.ProfileScreenViewController(user: user)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
可以看到,KMP 允许你将共享的 Compose UI 和原生的 SwiftUI 组件混合在同一个屏幕上,这种灵活性是 Flutter 等方案难以比拟的。
知名企业的实战经验
- Bitkey (区块链钱包):他们使用 KMP 共享了95%的代码,包括核心的加密和业务逻辑。起初双端UI分离,后来为了提升效率并保证UI一致性,全面转向 Compose Multiplatform。他们认为 KMP 让开发团队不再割裂,感觉像一个团队在维护一个项目。
- 阿里巴巴 (1688.com):他们在复杂的搜索筛选业务中使用 KMP。将之前 Android 和 iOS 两套独立的逻辑(筛选模型、请求策略、埋点)统一到共享模块中。结果是人力投入减少约30%,并且彻底解决了双端逻辑不一致、排查困难的问题。
- Netguru (电商App):他们使用 KMP 构建了一个包含 AR(增强现实)功能的家具电商App。所有业务逻辑共享,而AR这类对性能要求极高的功能则通过原生实现,再由 KMP 调用,展示了其强大的原生能力扩展性。
总结与适用场景
总的来说,KMP 特别适合以下几种情况:
- 逻辑密集型应用:如复杂的业务工具、金融应用、需要强一致性的客户端。
- 从单平台扩展到多平台:如果你已经有了一个成熟的 Android 应用,KMP 是将业务逻辑复用到 iOS 端的理想路径。
- 希望逐步采用跨平台方案的团队:可以先从一个小模块开始尝试,风险低,回报可见。
它不太适合对UI 动态化要求极高的场景(如频繁更换主题的UI),或者你希望一套代码搞定所有事情(包括UI)的场景。KMP 的理念是“在共享业务逻辑的地方共享,在需要原生体验的地方保持原生”。