说实话,听到“跨平台”这三个字,很多开发者心里可能先咯噔一下。脑海里浮现的可能是几年前那些卡顿的Hybrid应用,或者是需要维护两套甚至三套代码库的痛苦日子。但今天我们要聊的Kotlin Multiplatform(简称KMP),真的有点不一样。它不是那种“写一次,到处跑”的魔法咒语,而更像是一种精密的外科手术——把业务逻辑从UI框架中剥离出来,让Android和iOS共享核心代码,同时保留各自原生界面的极致体验。
咱们不整那些虚头巴脑的定义,直接切入正题。想象一下,你有一个电商App。购物车逻辑、用户认证、支付接口对接、数据解析……这些都不依赖界面。以前,你得让Android团队用Java/Kotlin写一遍,iOS团队用Swift写一遍。现在?只需要写一次,两边通用。而且,因为底层的UI还是原生的,用户体验不会打折。这就是KMP的核心魅力:逻辑共享,UI原生。
为什么要折腾这个?别被“全栈”忽悠了
首先得澄清一个误区:KMP不是要取代Swift或Kotlin for Android。它不是让你去学一套新的UI语言(比如Flutter的Dart)。相反,它尊重现有的生态。如果你已经是个熟练的Android开发者,你知道Kotlin语法有多优雅,那恭喜你,你几乎不需要额外学习新语法就能上手KMP。
为什么选择KMP而不是React Native或者Flutter?
- 性能接近原生:因为UI层是原生的,没有WebView的开销,也没有JS Bridge的通信延迟。
- 类型安全:这是Kotlin/Java体系的强项。编译期就能发现大部分错误,而不是等到运行时崩溃。
- 渐进式采用:你不需要重构整个App。你可以先从网络层、数据库层开始共享代码,慢慢扩大范围。这种“小步快跑”的策略对大型项目非常友好。
环境搭建:第一步往往最劝退,但我们来搞定它
很多教程卡在第一步,因为配置太繁琐。其实,JetBrains官方已经提供了非常便捷的脚手架。
你需要安装IntelliJ IDEA Ultimate版(社区版不支持多平台项目模板)或者Android Studio Hedgehog及以上版本。确保你的Kotlin插件是最新的。
创建一个新项目时,选择 Multiplatform Mobile。这一步很关键,它会自动为你生成三个模块:
androidApp: Android原生模块。iosApp: iOS原生模块(SwiftUI视图)。shared: 共享逻辑模块。
这个shared模块就是战场。在这里,你会看到几个特殊的源集(Source Sets):
commonMain: 这里写的代码,Android和iOS都能用。androidMain: 仅Android可用。iosMain: 仅iOS可用。
实战演练:共享一个网络请求层
光说不练假把式。我们来构建一个最简单的功能:获取用户信息。假设我们有一个API端点 /api/user/{id}。
在shared/src/commonMain/kotlin/com/example/app/network目录下,创建一个类:
package com.example.app.network
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.http.*
// 定义数据模型,纯Kotlin,无平台依赖
data class User(val id: Int, val name: String, val email: String)
class UserRepository {
private val client = HttpClient(CIO) // 使用CIO引擎,轻量且跨平台
suspend fun getUser(userId: Int): User {
return client.get("https://jsonplaceholder.typicode.com/users/$userId") {
contentType(ContentType.Application.Json)
}.body()
}
}
注意看,这里用了ktor-client,并且指定了CIO引擎。CIO是Kotlin开发的异步HTTP客户端,支持JVM和Native,非常适合跨平台。
接下来,我们需要在Android和iOS端分别引入这个依赖。在shared/build.gradle.kts中:
kotlin {
// ... 其他配置
sourceSets {
commonMain.dependencies {
implementation("io.ktor:ktor-client-core:2.3.5")
implementation("io.ktor:ktor-client-cio:2.3.5")
}
}
}
而在Android的build.gradle.kts中,你可能还需要添加一些JVM特定的依赖,但在Common部分,我们保持纯净。
现在,在Android端调用它变得极其简单:
// AndroidActivity.kt
val repo = UserRepository()
val user = repo.getUser(1)
Log.d("User", "Name: ${user.name}")
iOS端的挑战与Swift互操作
这才是真正体现KMP价值的地方。iOS端如何使用这段Kotlin代码?
KMP通过一种叫“Kotlin/Native”的技术,将Kotlin代码编译成原生二进制文件。对于Swift来说,它看起来就像是一个普通的Objective-C/Swift框架。
在Xcode中,你需要配置CocoaPods或SPM(Swift Package Manager)来集成KMP生成的框架。通常,使用CocoaPods更常见。在iosApp/Podfile中:
target 'iosApp' do
pod 'KMPSharedModule', :path => '../shared'
end
然后在Swift代码中,你可以直接导入并使用:
import KMPSharedModule
class ViewModel {
func fetchUser() {
let repo = UserRepository()
Task {
do {
let user = try await repo.getUser(userId: 1)
print("Name: \(user.name)")
} catch {
print("Error: \(error)")
}
}
}
}
看,是不是非常自然?你不需要写任何桥接代码,不需要处理JSON序列化,不需要担心类型转换。Kotlin的数据类自动映射到了Swift的结构体上。
处理平台特定实现:当逻辑不得不分叉时
有时候,共享逻辑会遇到瓶颈。比如,读取生物特征指纹。Android用BiometricPrompt,iOS用LocalAuthentication。这时候,expect/actual机制就派上用场了。
在commonMain中声明期望:
// commonMain/BiometricAuth.kt
expect class BiometricAuth {
fun authenticate(): Boolean
}
然后在androidMain中实现:
// androidMain/BiometricAuth.kt
actual class BiometricAuth {
actual fun authenticate(): Boolean {
// 调用Android BiometricManager API
return true // 简化示例
}
}
在iosMain中实现:
// iosMain/BiometricAuth.swift
import LocalAuthentication
public class BiometricAuth: BiometricAuthProtocol { // 需要符合协议
public func authenticate() -> Bool {
// 调用LAContext
return true // 简化示例
}
}
这样,你在commonMain的业务逻辑中,就可以调用BiometricAuth().authenticate(),而不必关心底层是哪个平台。这种解耦方式,让代码既保持了共享性,又具备了平台的灵活性。
数据库共享:Room vs CoreData
这是KMP目前的一个痛点,也是亮点。Android端通常用Room,iOS端用CoreData或Realm。虽然不能直接共享SQLite数据库文件(因为格式和加密可能不同),但你可以共享数据模型和ORM映射逻辑。
更好的方案是使用SQLDelight。它是一个类型安全的SQL库,支持多平台。
- 定义
.sq文件,描述你的数据库schema。 - SQLDelight自动生成对应的Kotlin接口。
- 在Android端,使用
SqlDelightDriver指向SQLite。 - 在iOS端,使用
SqlDelightDriver指向SQLite(iOS原生支持SQLite)。
这样,你的查询逻辑完全共享,且编译期检查SQL语法,避免运行时崩溃。
-- User.sq
SELECT * FROM users WHERE id = :id;
生成的Kotlin代码让你像写普通函数一样执行SQL,类型安全拉满。
常见坑与避指南
- 内存管理:Kotlin/Native使用引用计数(ARC),而不是垃圾回收(GC)。这意味着循环引用会导致内存泄漏。在共享代码中,尽量避免强引用环。使用
WeakReference或重构对象关系。 - 线程模型:KMP默认在非UI线程运行协程。在Android端,这通常没问题。但在iOS端,如果你需要更新UI,必须切换到主线程。使用
Dispatchers.Main在commonMain中可能不可用,需要通过expect/actual切换调度器,或者在iOS端使用@MainActor注解。 - 第三方库兼容性:不是所有Android库都有iOS版本。在选择依赖时,优先选择KMP官方支持的库(如Ktor, Koin, SQLDelight)。对于不支持的库,你需要自己封装一层
expect/actual来实现。 - 构建时间:首次编译KMP项目较慢,因为需要为多个平台交叉编译。后续增量编译会快很多。建议使用Gradle守护进程和缓存。
为什么这能提升开发效率?
假设你的团队有2个Android开发,1个iOS开发。以前,修改一个支付逻辑,需要三个人分别测试。现在,逻辑改一次,两端自动同步。测试用例也可以共享(使用JUnit 5 + Kotlin Test)。
更重要的是,招聘变得容易了。你可以招一个Kotlin开发者,他既能做Android,又能做iOS后端逻辑。人才利用率大幅提升。
未来展望:KMP不仅仅是移动开发
随着SwiftUI和Compose Multiplatform的发展,KMP的边界正在扩展。Compose Multiplatform允许你用Kotlin编写桌面应用(Windows, macOS, Linux)和Web应用。虽然目前还在早期阶段,但趋势很明显:一套语言,多种平台。
对于初学者,我的建议是:不要试图一开始就共享所有代码。从一个简单的网络层开始,感受一下类型安全和代码复用的快感。然后逐步扩展到数据库、业务逻辑。你会发现,KMP不是洪水猛兽,而是一个强大的盟友。
最后,记住一点:技术是为了服务于人的。KMP的价值在于减少重复劳动,让开发者有更多时间去创造真正的创新,而不是在两个平台间复制粘贴。当你看到同一份代码在iPhone和Pixel手机上完美运行时,那种成就感,无可替代。
