本文将分10个步骤,详细指导如何将现有的 Android 和 iOS 应用迁移到 Kotlin Multiplatform(KMP),内容涵盖从仓库设计、模块化到单元测试迁移的完整流程。
1. 准备仓库
在打开 Android Studio 并开始将类迁移到多平台模块之前,我们需要先规划代码仓库结构。如果你的 Android 和 iOS 应用目前位于两个独立的 Git 仓库中,引入 KMP 后,这种结构通常需要调整。因为 KMP 的核心是让两个原生应用共享同一套 Kotlin 代码,因此需要确保这套代码能被双方便捷地访问,具体有以下几种配置方案:
方案 1:为多平台模块创建独立仓库
第一种方案是将新的多平台模块视为一个独立的库,放置在单独的仓库中,两个原生应用将其作为外部依赖来使用:
- Android 端:将多平台代码导出为 Android 库(AAR),通过 Maven 仓库进行分发。
- iOS 端:将多平台代码导出为 Apple framework,通过 CocoaPods 或 Swift Package Manager(SPM)分发。

为了简化并自动化这一分发过程,可以使用 KMMBridge 工具。这是一个添加到多平台模块的 Gradle 插件,开箱即支持通过 Maven、CocoaPods 和 SPM 进行发布。
三仓库配置:
- Android 项目仓库(包含 Android 应用)
- iOS 项目仓库(包含 iOS 应用)
- 共享代码仓库(包含 Kotlin Multiplatform 共享模块)
该方案复杂度最高,主要推荐给大型项目和开发者数量较多的团队。由于将多平台模块作为库来管理,如果能有专门的团队与 Android、iOS 团队协同工作,效果最佳。这样可以确保多平台库得到充分的文档说明、严格的版本控制,并按常规周期进行发布。
方案 2:将共享模块放入 Android 仓库
并非所有项目都规模庞大到需要专门团队来开发多平台库。第二种方案是将多平台模块直接放入 Android 项目仓库中:
- Android 端:多平台模块与普通 Android 模块没有本质区别,都使用 Kotlin 语言和 Gradle 构建,可以直接集成到项目中,无需额外的构建和分发步骤。
- iOS 端:分发流程与方案 1 一致,通过 KMMBridge 等工具将多平台模块导出为 Apple framework,再通过 CocoaPods 或 SPM 发布给 iOS 应用使用。
两仓库配置:
- Android 项目仓库(包含 Android 应用和 Kotlin Multiplatform 共享模块)
- iOS 项目仓库(包含 iOS 应用)

该方案适用于主要由 Android 团队负责多平台代码开发的场景。Android 开发者可以像使用普通 Android 模块一样使用多平台模块,简化了开发流程;而 iOS 团队则将其作为外部依赖。建议为多平台模块添加版本控制和文档,方便 iOS 团队将其视为一个库来使用;如果两队沟通紧密,也可省略这一步骤。
方案 3:合并应用到单体仓库(Monorepo)
将共享模块放入 Android 仓库会导致团队间的不对称——Android 工程师开发通用代码,而 iOS 同事仅使用这些代码。若项目希望避免这种情况,可采用第三种方案:不新增专门团队,所有开发者共同负责多平台代码开发。
具体做法是将原有的独立仓库合并为一个单体仓库,并在其中添加多平台模块。由于所有代码都位于同一仓库,无需依赖 Maven、CocoaPods 或 SPM 等远程制品分发工具:
- Android 端:通过 Gradle 模块依赖将 KMP 模块关联到应用。
- iOS 端:通过 Xcode Build Phases 配置直接关联 KMP 模块。
单体仓库配置:
- 单体仓库(包含 Android 应用、iOS 应用和 Kotlin Multiplatform 共享模块)

这种方案非常适合 Android 和 iOS 开发者希望紧密协作、形成一个单一团队的场景。它不仅能简化开发流程、提升效率,还能促进工程师之间的知识共享。
若不想创建单体仓库,也可通过 Git SubModule 实现类似效果:将多平台模块放在独立仓库中,再通过子模块将其引入 Android 和 iOS 项目仓库,无需远程分发工具即可直接关联到两个原生应用。
2. 考虑模块化设计
无论选择哪种仓库配置,都可能需要对共享代码进行模块化拆分。良好的模块化架构有助于实现代码封装、提升构建性能,并支持团队内或跨团队的并行开发。Kotlin Multiplatform 与 Android 一样依赖 Gradle,因此可以直接沿用 Android 项目中相似的模块化策略。
但 iOS 不使用 Gradle,无法像 Android 那样直接通过源码依赖关联多平台模块。iOS 应用需要将共享的 Kotlin 代码打包为 iOS framework,再通过 Xcode Build Phases、CocoaPods 或 SPM 进行关联。

多模块项目的问题
假设存在两个多平台模块(例如 login 和 home),且它们都依赖于第三个模块 networking(包含通用的 HTTP 客户端):
- Android 端:应用分别导入
login 和 home 模块时,会共享 networking 模块的同一个 HTTP 客户端实例。
- iOS 端:如果为
login 和 home 分别生成独立的 framework,每个 framework 都会包含其依赖的所有代码副本——这意味着两个模块不会共享 HTTP 客户端,而是各自拥有独立的副本。
这种行为会引发一系列问题:
- 模块间无法共享状态:如果多个模块通过某个模块(如
networking)存储通用状态,编译到 iOS 后,每个模块都会拥有独立的状态副本,导致状态同步失效。
- 应用体积膨胀:不同 framework 中存在重复代码,增加了不必要的安装包体积。
解决方案:引入伞形模块(Umbrella Module)
为解决多模块项目在 iOS 端的上述问题,应在 KMP 多模块项目中引入一个伞形模块(Umbrella Module)——这是唯一会生成 iOS framework 的模块。这样:
- iOS 应用仅需依赖这一个伞形模块生成的 framework,无需关联其他独立模块。
- Android 应用可以通过 Gradle 关联单个模块,也可以使用伞形模块以保持与 iOS 端架构的一致性。

伞形模块本身依赖所有多平台模块,并控制哪些模块可被原生应用访问:
- 仅用于多平台代码内部的模块:在伞形模块中添加为
implementation 依赖。
- 需要暴露给原生应用的模块:在伞形模块中添加为
api 依赖,并从 iOS framework 中导出。
以下是伞形模块的 build.gradle.kts 配置示例:
kotlin {
val iosTargets = listOf(iosArm64(), iosSimulatorArm64())
configure(iosTargets) {
binaries.framework {
baseName = "shared"
isStatic = true
// 这些模块可被 iOS 应用访问
export(projects.shared.login)
export(projects.shared.home)
}
}
sourceSets {
commonMain.dependencies {
// 这些模块可被原生应用访问
api(projects.shared.login)
api(projects.shared.home)
// 这些模块仅用于内部使用
implementation(projects.shared.networking)
}
}
}
3. 使 Kotlin 代码兼容 Swift
在 iOS 端,Kotlin 代码会先被转换为 Objective-C 代码,以确保兼容 Apple 生态系统中的各类项目。但这要求开发者在设计公共 Kotlin API 时格外谨慎,因为部分 Kotlin 语言特性在转换为 Objective-C 后可能丢失或变得难以使用。
为使 Kotlin 代码在 Swift 中拥有更好的体验,可采取以下措施:
- 参考官方 Kotlin-Swift 互操作性文档,了解所有 Kotlin 特性在转换为 Objective-C 后,在 Swift 中如何使用。
- 观看 KotlinConf 2024 的相关演讲,深入理解互操作性原理及优化技巧。
- 在公共模块中添加 SKIE 插件。这是一个 Kotlin Multiplatform 插件,可为特定语言特性(如协程、Flow、密封类等)直接生成 Swift 代码,而非 Objective-C 代码,极大降低了在 Swift 中的使用难度。
- 关注 Kotlin-Swift 直接互操作性 的最新动态。该特性已列入 2024 年 Kotlin Multiplatform 路线图,旨在减少对 Objective-C 中间层的依赖,阅读本文时可能已有重大进展或已正式发布。
4. 配置依赖注入(DI)
几乎所有现代移动应用都会使用某种依赖注入方案。在 Android 领域,最流行的是 Google 官方推出的 Hilt 库——它基于 Java 编写的 Dagger 框架构建,因此无法在多平台代码中直接使用。
为在 KMP 共享模块中实现依赖注入,需要切换到 Koin 库:
- Koin 是一个轻量级、实用主义的依赖注入框架,100% 基于 Kotlin 编写,技术成熟,是 Android 开发者中仅次于 Hilt 的第二大热门选择。
Koin 在 KMP 中的最佳实践
建议仅在共享模块内部使用 Koin,具体方式如下:
- 在共享模块中创建一个独立的
KoinApplication 实例。
- 仅对外暴露需要被原生应用访问的类的
get 方法。
示例代码:
object SharedModule {
private var koinApplication: KoinApplication? = null
private val koin: Koin
get() = koinApplication?.koin ?: error("SharedModule not intialized")
fun init() {
check(koinApplication == null) { "SharedModule intialized" }
koinApplication = koinApplication {
modules(loginModule, homeModule, networkingModule)
}
}
fun getLoginViewModel(): LoginViewModel = koin.get()
}
该方案的优势
- 不强制 Android 应用使用 Koin:Android 应用可以继续使用 Hilt,仅需将
SharedModule 与 Hilt 关联即可。
- 原生应用使用方式统一:Koin 运行在通用 Kotlin 代码中,无法直接在 Swift 中使用。将其隐藏在多平台模块内部,可以对外暴露简单、与框架无关的统一 API。
- 原生应用可能无需额外 DI 方案:如果将所有 ViewModel 都移至通用 Kotlin 代码,原生应用可以直接通过
SharedModule 的公共 get 方法访问,无需依赖 Hilt、Koin 等任何 DI 工具。
Android(Compose)使用示例:
@Composable
fun LoginScreen() {
val viewModel = viewModel { SharedModule.getLoginViewModel() }
}
iOS(SwiftUI)使用示例:
struct LoginScreen: View {
let viewModel = SharedModule.getLoginViewModel()
}
5. 迁移模型(Model)
任何应用都包含模型类,用于表示业务对象(如 User、Product、Article)。这些类通常仅存储数据或实现通用业务规则,不依赖后端服务、数据库、系统 API 或 UI 组件,因此是 KMP 迁移的最佳起点。

基础迁移
以简单的 Product 数据模型为例,迁移到多平台模块通常无需任何修改,即可直接在 iOS 应用中使用:
Kotlin 通用代码:
data class Product(
val id: String,
val name: String,
val description: String,
val price: Double
)
iOS(Swift)使用示例:
import shared
class ProductsRepository {
func createNewProduct(
name: String,
description: String,
price: Double
) {
let newProduct = Product(
id: UUID().uuidString,
name: name,
description: description,
price: price
)
...
}
}
替换 Java 类型为 Kotlin 类型
原生 Android 应用的 Kotlin 代码中,模型可能依赖 Java 类型(如 LocalDate、LocalDateTime),这些类型无法在多平台代码中使用。幸运的是,Kotlin 团队在 kotlinx-datetime 库中提供了纯 Kotlin 实现的替代类型,通常只需替换导入语句即可:
修改前(依赖 Java 类型):
import java.time.LocalDate
data class Product(
val id: String,
val name: String,
val description: String,
val price: Double,
val dateAdded: LocalDate,
)
修改后(使用 Kotlin 类型):
import kotlinx.datetime.LocalDate
data class Product(
val id: String,
val name: String,
val description: String,
val price: Double,
val dateAdded: LocalDate,
)
为不支持的类型提供平台特定实现
部分 Java 类型没有直接的 Kotlin 替代方案(如 URL),此时可以通过 Kotlin 的 expect/actual 机制提供平台特定实现:
- 在通用代码集(
commonMain)中定义预期声明(expect):
expect class URL(path: String)
-
在 Android 代码集(androidMain)中使用 Java URL 实现:
import java.net.URL
actual class URL(private val url: URL) {
actual constructor(path: String) : this(URL(path))
override fun toString(): String {
return url.toString()
}
}
-
在 iOS 代码集(iosMain)中使用 Apple NSURL 实现:
import platform.Foundation.NSURL
actual class URL(private val url: NSURL) {
actual constructor(path: String) : this(NSURL(string: path))
override fun toString(): String {
return url.absoluteString.orEmpty()
}
}
通过模型迁移,团队可以初步掌握 KMP 的核心使用方式:如何在多平台模块中编写 Kotlin 代码、如何在 iOS 应用中使用,以及如何通过 expect/actual 处理平台特定逻辑。
6. 迁移数据源(Data Sources)
完成模型迁移后,可以继续迁移更复杂的数据源,包括远程数据源(API 调用)和本地数据源(数据库、文件存储)。

迁移方式主要取决于后端通信和本地存储所使用的技术栈,以下是常见方案的迁移指南:
远程数据源迁移
REST 通信
- 若项目已使用 Ktor Client + Kotlin Serialization:这两个工具均为 JetBrains 官方提供的纯 Kotlin 实现,天然支持 KMP,直接将网络代码移至多平台模块即可。
- 若项目使用 Retrofit + Moshi/Gson(Java 库):需要重构为 Ktor Client + Kotlin Serialization,步骤如下:
- 在
build.gradle.kts 中配置新的依赖库。
- 修改 DTO 模型的注解(将 Moshi/Gson 注解替换为
@Serializable 和 @SerialName)。
- 将 Retrofit 接口重构为使用
HttpClient 的 Ktor 类。
重构前(Retrofit + Moshi):
@JsonClass(generateAdapter = true)
data class ProductApiModel(
@Json(name = "product_id") val productId: String,
@Json(name = "name") val name: String,
@Json(name = "description") val description: String,
@Json(name = "price") val price: Double
)
interface ProductsService {
@GET("products/{product_id}")
suspend fun getProduct(@Path("product_id") productId: Int): ProductApiModel
}
重构后(Ktor + Kotlin Serialization):
@Serializable
data class ProductApiModel(
@SerialName("product_id") val productId: String,
@SerialName("name") val name: String,
@SerialName("description") val description: String,
@SerialName("price") val price: Double
)
class ProductsService(private val httpClient: HttpClient) {
suspend fun getProduct(productId: Int): ProductApiModel {
return httpClient.get("products/${productId}").body()
}
}
GraphQL 通信
若项目使用 GraphQL 与后端交互,大概率依赖 Apollo Kotlin 库——该库为纯 Kotlin 实现,完全支持 KMP,可以直接将相关的网络代码移至多平台模块。
Firebase
Firebase 是常用的云后端平台(用于 Crashlytics、Analytics 等),其 Android SDK 为 Java 库,无法直接在多平台模块中使用。解决方案是使用开源的 Firebase Kotlin SDK,该 SDK 的 API 设计与原生 Firebase Android SDK 相似,并支持协程、序列化等现代 Kotlin 特性。
本地数据源迁移
Jetpack DataStore 和 Room
这两个 Google 官方本地存储库均已支持 KMP,可以直接将现有实现移至多平台模块,无需更换工具。
其他存储方案
若不使用 Jetpack 库,也可选择其他支持 KMP 的存储方案:
- SQLDelight:通过 SQL 文件生成类型安全代码的关系型数据库方案。
- Realm:面向对象的 NoSQL 存储方案。
7. 迁移原生 API 和 SDK
原生应用通常需要与系统 API 或第三方原生 SDK 交互(如检查网络连接、获取位置信息、使用蓝牙等)。
架构良好的应用会将这类交互抽象为接口。迁移时,可以通过以下两种方式处理:
方式 1:在多平台模块中直接实现平台特定代码
KMP 支持与 iOS 平台的双向互操作性,可以直接在 Kotlin 代码中访问 iOS 的原生 API。

迁移步骤如下:
- 将抽象接口移至
commonMain。
- 在
androidMain 中保留原有的 Android 特定实现。
- 在
iosMain 中,通过 Kotlin 调用 iOS 系统 API,实现 iOS 特定逻辑。
示例:网络连接检测
- 通用接口(
commonMain):
interface NetworkConnection {
val isAvailable: Boolean
}
-
Android 实现(androidMain):
import android.net.*
internal class AndroidNetworkConnection(
private val connectivityManager: ConnectivityManager,
) : NetworkConnection, ConnectivityManager.NetworkCallback() {
override var isAvailable: Boolean = true
private set
init {
val request = NetworkRequest.Builder().build()
connectivityManager.registerNetworkCallback(request, this)
}
override fun onAvailable(network: Network) {
isAvailable = true
}
override fun onLost(network: Network) {
isAvailable = false
}
}
-
iOS 实现(iosMain):
import platform.Network.*
import platform.darwin.*
internal class IosNetworkConnection : NetworkConnection {
override var isAvailable: Boolean = true
private set
init {
val monitor = nw_path_monitor_create()
nw_path_monitor_set_update_handler(monitor) { path ->
val status = nw_path_get_status(path)
isAvailable = status == nw_path_status_satisfied
}
nw_path_monitor_set_queue(monitor, dispatch_get_main_queue())
nw_path_monitor_start(monitor)
}
}
方式 2:由原生应用提供平台特定实现
若原生 API 或 SDK 较为复杂,直接在 Kotlin 中调用的语法可能比较繁琐,可选择由原生应用自行实现接口,再传递给多平台模块。

- 通用接口仍保留在
commonMain。
- Android 应用在原生代码中实现接口(沿用原有逻辑)。
- iOS 应用在 Swift 中实现接口(使用更自然的原生语法)。
- 多平台模块通过初始化参数接收这些原生实现。
示例:iOS Swift 实现接口
class IosNetworkConnection: NetworkConnection {
private(set) var isAvailable: Bool = true
init() {
let monitor = NWPathMonitor()
let queue = DispatchQueue.main
monitor.pathUpdateHandler = { path in
let status = path.status
self.isAvailable = status == .satisfied
}
monitor.start(queue: queue)
}
}
多平台模块接收实现并初始化:
object SharedModule {
fun init(networkConnection: NetworkConnection) {
check(koinApplication == null) { "库已启动" }
val nativeAppModule = module {
single { networkConnection }
}
koinApplication = koinApplication {
modules(nativeAppModule, loginModule, homeModule, networkingModule)
}
}
}
8. 迁移业务逻辑
完成数据源、原生 API/SDK 的迁移后,原生应用的后端通信、本地存储、外部依赖交互等底层逻辑已全部由多平台模块共享。此时,可以开始迁移更上层的业务逻辑。

业务逻辑的实现形式多样(如 Repository、Use Case、Manager 等),其迁移流程通常非常简单:由于业务逻辑所依赖的接口(系统 API、数据源等)已经在多平台模块中可用,因此只需将 Android 应用中的业务逻辑代码直接移至 commonMain 即可。
9. 迁移表现层(Presentation Layer)
完成上述步骤后,Android 和 iOS 应用的代码共享率可能已达到 50%-60%。若想进一步提升共享率,可以迁移表现层——目前大多数应用使用 ViewModel 来实现表现层逻辑,以下是具体的迁移方案:

使用 Jetpack ViewModel
Android 应用的 ViewModel 通常继承自 Jetpack ViewModel。好消息是,androidx.lifecycle:lifecycle-viewmodel 这个库已被 Google 迁移至 KMP,因此 ViewModel 类可以直接在多平台模块中使用,无需修改代码。
生命周期管理
- Android 端:Activity、Fragment 等组件天然实现了
ViewModelStoreOwner 接口,ViewModel 的生命周期被自动管理。
- iOS 端:UIViewController 和 View 不支持该接口,需要手动处理。可以通过创建一个包装类来实现
ViewModelStoreOwner 接口,确保 ViewModel 的生命周期与视图一致(例如,关联的 CoroutineScope 在 ViewModel 不再需要时被取消)。
iOS 包装类示例(Swift):
class UIHostingControllerWrapper<V: View, VM: ViewModel> : UIHostingController<V>, ViewModelStoreOwner {
let viewModelStore = ViewModelStore()
init(rootView: V, viewModel: VM) {
super.init(rootView: rootView)
let key = String(describing: VM.self)
viewModelStore.put(key: key, viewModel: viewModel)
}
deinit {
viewModelStore.clear()
}
}
在 UI 中收集 Flow 数据
Android 端的 ViewModel 通常通过 Coroutine Flow 向 UI 暴露数据流。迁移后:
- Compose 端:无变化,仍使用
collectAsState 等函数收集数据。
- SwiftUI 端:SKIE 插件会将 Kotlin Flow 转换为 Swift AsyncSequence,但 SwiftUI 没有内置的观察方法。可以创建一个自定义的
ViewModifier 来实现。
自定义 ViewModifier 示例(Swift):
private struct StateBinding<State>: ViewModifier {
@Binding var state: State
let stateFlow: SkieSwiftStateFlow<State>
func body(content: Content) -> some View {
content.task {
for await state in stateFlow {
self.state = state
}
}
}
}
extension View {
func stateBinding<State>(
_ state: Binding<State>,
_ stateFlow: SkieSwiftStateFlow<State>
) -> some View {
modifier(StateBinding(state: state, stateFlow: stateFlow))
}
}
SwiftUI 中使用示例:
struct ProductListView: View {
let viewModel: ProductListViewModel
@State var products: [Product] = []
var body: some View {
VStack {
...
}
.stateBinding($products, viewModel.products)
}
}
处理可序列化(Parcelable)数据
Android 应用中,在导航时通常通过 Parcelable 机制序列化对象来传递参数。但 android.os.Parcelable 依赖无法在多平台代码中使用。解决方案仍然是 expect/actual 机制:
-
在 commonMain 中定义预期接口和注解:
expect interface Parcelable
@OptIn(ExperimentalMultiplatform::class)
@OptionalExpectation
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
expect annotation class Parcelize
- 在
androidMain 中关联 Android 原生实现:
actual interface Parcelable : android.os.Parcelable
actual typealias Parcelize = kotlinx.parcelize.Parcelize
- 在
iosMain 中提供空实现(无实际作用,仅用于编译):
actual interface Parcelable
// actual annotation class Parcelize 可留空或提供无操作实现
10. 迁移单元测试
单元测试是代码库的重要组成部分,覆盖各个层级。Android 端通常使用 JUnit(测试框架)、MockK(模拟工具)和 Truth/AssertJ(断言库),但这些工具依赖 Java/JVM 机制(如反射),无法直接在多平台模块的 commonTest 中使用。迁移可以分两步进行:
第一步:暂时保留 Android 特定测试工具
首先,将所有单元测试移至 androidTest 而非 commonTest。这样可以继续使用 JUnit、MockK 等熟悉的工具,无需在迁移初期大量改写测试代码,降低了初始成本。
第二步:逐步迁移到 Kotlin 多平台测试工具
后续可以逐步将测试从 androidTest 迁移至 commonTest,并替换为支持 KMP 的工具:
- 测试框架:将 JUnit 替换为 Kotlin Test(语法相似,轻量简洁)或 Kotest(功能更丰富,专为 KMP 设计)。
- 断言库:Truth/AssertJ 可以替换为 AssertK(纯 Kotlin 实现,语法一致);如果已使用 Kotest Assertions,可以直接沿用(天然支持 KMP)。
- 模拟工具:MockK 目前暂无功能完全对等的 KMP 替代方案(现有的 KMP 模拟库功能有限)。建议采用 Google 推荐的“伪对象(Fake)”来替代模拟对象——伪对象的行为与真实实现一致,可复用、能促进架构简化,且无需任何第三方工具。
总结
若你拥有原生移动应用,希望优化开发维护流程、降低成本,可能曾考虑过 Flutter 或 React Native 等跨平台方案,但这些方案通常需要招聘新团队、完全重写应用,投资风险较高。
Kotlin Multiplatform 是连接原生开发与跨平台开发的突破性技术,是唯一能与现有原生应用无缝集成、渐进式采用的跨平台方案,风险远低于其他竞品。其核心迁移策略并非彻底重构技术栈,而是可以继续使用现有的原生工具和团队:只需逐步将 Android 应用中的 Kotlin 代码移至多平台模块,集成到 iOS 应用中,再删除对应的 Swift 实现即可。
迁移的复杂度主要取决于应用的架构设计和所使用的 Java 工具数量,但本文已针对常见挑战提供了经过实践验证的解决方案。若你仔细阅读并遵循这些步骤,即可顺利启动 Kotlin Multiplatform 的迁移之旅。