Android KMP 笔记

0 阅读6分钟

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组件和平台特定实现。

案例概述

我们将创建一个简单的跨平台应用,它能够:

  1. 共享业务逻辑:从一个假想的 API 获取用户资料。
  2. 共享 UI 组件:使用 Compose Multiplatform 创建一个在 Android 和 iOS 上都能显示的资料卡片。
  3. 处理平台差异:使用 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 的理念是“在共享业务逻辑的地方共享,在需要原生体验的地方保持原生”。