Kotlin Multiplatform 跨平台开发实战:15分钟掌握核心机制与多端部署
2026/9/10 4:36:40 网站建设 项目流程

1. 背景与核心概念

Kotlin 早已不是那个只属于 Android 世界的“备胎”语言了。如果你还停留在“Kotlin 就是用来写 Android 的”这个认知上,那可能会错过一个极其强大的多平台开发工具链。在实际项目中,我们常常面临这样的困境:业务逻辑需要在 Web 前端、移动端(Android/iOS)和后端服务中重复实现,这不仅带来巨大的开发成本,更埋下了逻辑不一致的深坑。Kotlin Multiplatform(KMP)正是为解决这一痛点而生,它允许你用一套 Kotlin 代码来编写跨平台共享的业务逻辑。

简单来说,Kotlin 是一门运行在 Java 虚拟机(JVM)上的静态类型编程语言,由 JetBrains 开发。而 Kotlin Multiplatform 是 Kotlin 语言的一个特性,它让你能够将代码编译到多个目标平台:JVM(用于后端和 Android)、JavaScript(用于浏览器和 Node.js)、原生二进制文件(通过 Kotlin/Native 用于 iOS、macOS、Windows、Linux 等),甚至 WebAssembly(Wasm)。其核心思想是“共享逻辑,差异化 UI”:将网络请求、数据模型、业务规则等核心代码写在共享模块中,而平台特定的 UI 和系统 API 调用则写在各自的平台代码里。

为什么开发者需要掌握它?对于全栈或移动端开发者而言,它意味着代码复用率的大幅提升和逻辑一致性的根本保证。对于团队而言,它能减少重复劳动,让 iOS 和 Android 开发者基于同一套业务逻辑进行 UI 开发,极大降低沟通和联调成本。接下来,我们将用最短的路径,带你体验 Kotlin 代码如何在不同平台上“跑起来”。

2. 环境准备与版本说明

在开始我们的“15分钟环游”之前,需要确保你的开发环境已经就绪。本文的示例将基于以下环境,但请注意,Kotlin 工具链更新较快,具体版本请根据官方文档和你的项目需求调整。

核心环境:

  • 操作系统:macOS、Windows 或 Linux 均可。部分平台(如 iOS 模拟器)的编译需要 macOS。
  • IDEIntelliJ IDEA Community 或 Ultimate 版。这是 Kotlin 的“亲爹”开发工具,对 KMP 支持最为完善。Android Studio(基于 IDEA)也可用于 Android 和共享模块开发。
  • JDK:版本 11 或以上。推荐使用 Azul Zulu 或 Oracle JDK。

关键工具与版本:

  • Kotlin 编译器:本文示例使用1.9.22。你可以通过 IDEA 的 Kotlin 插件或项目构建脚本管理版本。
  • Kotlin Multiplatform 插件:IDEA 内置支持。
  • Android SDK:用于编译 Android 目标。
  • Xcode:仅当需要编译和运行 iOS 目标时需要,且必须在 macOS 上。
  • Node.js:用于运行 Kotlin/JS 编译后的 JavaScript 代码。

项目结构预览:我们将创建一个标准的 KMP 项目,其典型结构如下:

my-kmp-project/ ├── build.gradle.kts # 项目级构建脚本 ├── settings.gradle.kts # 项目设置 ├── shared/ # 共享模块(核心!) │ ├── build.gradle.kts │ ├── src/ │ │ ├── commonMain/ # 所有平台共享的代码和预期声明 │ │ ├── androidMain/ # Android 平台实现 │ │ ├── iosMain/ # iOS 平台实现 │ │ └── jsMain/ # JS 平台实现 │ └── ... ├── androidApp/ # Android 应用模块 ├── iosApp/ # iOS 应用模块 (Xcode项目) └── webApp/ # Web 前端项目

这个结构清晰地体现了“共享模块+平台应用”的架构思想。

3. 核心语法、配置与原理拆解

在深入多平台之前,我们先快速回顾并理解 Kotlin 中支撑多平台能力的几个关键概念。

3.1 预期声明与实际声明

这是 KMP 的基石机制,用于定义跨平台 API。

  • expect(预期声明):在共享模块的commonMain中声明一个函数、属性或类,但不提供实现。它定义了一个契约,告诉编译器“每个目标平台都必须提供这个的实现”。
  • actual(实际声明):在各个平台特定的源码目录(如androidMain,iosMain)中,为对应的expect声明提供具体的平台实现。
// 文件:shared/src/commonMain/kotlin/Platform.kt // 在通用代码中声明一个期待的平台信息获取函数 expect fun platformName(): String // 文件:shared/src/androidMain/kotlin/Platform.kt // 在 Android 平台上提供实际实现 actual fun platformName(): String { return "Android" } // 文件:shared/src/iosMain/kotlin/Platform.kt // 在 iOS 平台上提供实际实现 actual fun platformName(): String { return "iOS" }

通过这种方式,共享模块中的通用代码可以调用platformName(),而在编译到不同平台时,链接的会是各自平台的实际实现。

3.2 多平台项目配置(Gradle KTS)

项目的跨平台能力是通过 Gradle 构建脚本(推荐使用 Kotlin DSL,即.kts后缀)配置的。核心是kotlin块中的target声明。

// 文件:shared/build.gradle.kts kotlin { // 1. 声明目标平台 androidTarget() // 会自动配置 Android 目标 jvm() // 普通的 JVM 目标(用于桌面或后端) iosX64() // 适用于 Intel 芯片的 iOS 模拟器 iosArm64() // 适用于真机 (iPhone) 或 Apple Silicon 模拟器 // iosSimulatorArm64() // 适用于 Apple Silicon 模拟器 (较新版本) js(IR) { // 使用新的 IR 编译器后端,功能更强大 browser() // 编译为在浏览器中运行 // nodejs() // 编译为在 Node.js 中运行 } // 2. 配置源代码集依赖关系(默认已关联 commonMain 到各平台) sourceSets { val commonMain by getting { dependencies { // 在这里添加所有平台共享的依赖,例如 kotlinx-coroutines-core implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") } } val androidMain by getting { dependencies { // Android 平台特定的依赖 implementation("androidx.core:core-ktx:1.12.0") } } val iosMain by getting { // iOS 依赖通常通过 CocoaPods 或直接使用系统框架 } } }

3.3 平台间交互与内存模型

这是 KMP 进阶时必须理解的难点。

  • Kotlin/Native 并发模型:Kotlin/Native(用于编译到 iOS 等原生平台)最初有一套严格的“冻结”和单线程调度模型,以防止数据竞争。这给共享可变状态带来了挑战。
  • 新版内存管理器:从 Kotlin 1.7.20 开始实验,到 1.9.20 已稳定,Kotlin/Native 采用了新的自动内存管理器,其并发模型与 JVM 和 JS 更加一致,大大简化了跨平台并发编程。在gradle.properties中设置kotlin.native.binary.memoryModel=experimental(旧版本)或直接使用新版本即可享受这一改进。
  • 线程安全:尽管内存管理器统一了,但在共享模块中编写并发代码时,仍需谨慎处理共享状态。使用kotlinx.coroutines提供的协程原语(如Mutex,Flow)是推荐的做法。

4. 完整实战案例:一个跨平台“时间戳格式化器”

让我们通过一个完整的例子,创建一个简单的共享模块,它包含一个格式化当前时间的函数,并在 JVM、Android、iOS 和 JS 上运行它。

4.1 创建项目与共享模块

  1. 在 IntelliJ IDEA 中,选择New Project
  2. 选择Kotlin Multiplatform下的Application模板。命名项目为KmpQuickTour
  3. Project Template中,选择Full-Stack Web ApplicationMobile and Desktop Application以获取一个预配置了多个目标的起点。这里我们选后者。
  4. IDEA 会自动生成一个包含shared,composeApp(桌面UI),androidApp,iosApp模块的项目。为了演示更纯粹,我们稍作调整。

4.2 编写共享模块核心逻辑

我们首先在共享模块中定义一个期望的时钟接口和格式化器。

// 文件:shared/src/commonMain/kotlin/com/example/kmp/TimeFormatter.kt package com.example.kmp // 预期声明:一个获取当前时间的接口 expect interface Clock { fun currentTimeMillis(): Long } // 一个纯 Kotlin 的通用格式化函数,不依赖平台 class TimeFormatter(private val clock: Clock) { fun formatCurrentTime(): String { val timeMillis = clock.currentTimeMillis() // 使用 Kotlin 标准库进行简单格式化 (仅作示例,生产环境应用更健壮的格式化) return "Current timestamp: $timeMillis" // 更复杂的格式化可以借助 kotlinx-datetime 等多平台库 } }

4.3 为各平台提供实际实现

Android/JVM 实现:对于 Android 和 JVM,我们可以使用标准的System.currentTimeMillis()

// 文件:shared/src/androidMain/kotlin/com/example/kmp/TimeFormatter.kt package com.example.kmp // JVM/Android 的实际时钟实现 actual interface Clock { actual fun currentTimeMillis(): Long = System.currentTimeMillis() }

jvmMain的实现可以与此相同,或者通过androidMainjvmMain的公共源集来共享代码。

iOS 实现:在 iOS 上,我们需要使用平台的 API。

// 文件:shared/src/iosMain/kotlin/com/example/kmp/TimeFormatter.kt package com.example.kmp import platform.Foundation.NSDate // 导入 iOS Foundation 框架 actual interface Clock { actual fun currentTimeMillis(): Long { // NSDate.timeIntervalSince1970 返回的是秒,需要乘以 1000 return (NSDate().timeIntervalSince1970 * 1000).toLong() } }

JavaScript 实现:在 JS 环境中,使用Date.now()

// 文件:shared/src/jsMain/kotlin/com/example/kmp/TimeFormatter.kt package com.example.kmp actual interface Clock { actual fun currentTimeMillis(): Long { // 使用 Kotlin/JS 的 external 声明调用 JS 的 Date.now() return Date.now().toLong() } } // 声明外部 JS 对象 external class Date { companion object { fun now(): Number } }

4.4 在平台应用中调用共享代码

在 Android App 中调用:

// 文件:androidApp/src/main/kotlin/com/example/kmp/android/MainActivity.kt package com.example.kmp.android import androidx.appcompat.app.AppCompatActivity import com.example.kmp.TimeFormatter import com.example.kmp.Clock // 实际会链接到 androidMain 的实现 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val formatter = TimeFormatter(object : Clock {}) // 使用平台实现 val timeString = formatter.formatCurrentTime() Log.d("KMP", timeString) // 输出:Current timestamp: 1712... // 更新 UI... } }

在 iOS App 中调用(SwiftUI):首先,共享模块编译后会生成一个 iOS 框架。在 Swift 中,你可以像使用普通框架一样导入它。

// 文件:iosApp/iosApp/ContentView.swift import SwiftUI import shared // 这是你的 Kotlin 共享模块生成的框架 struct ContentView: View { // 在 Swift 中访问 Kotlin 对象 let formatter = Shared.TimeFormatter(clock: Shared.Clock()) var body: some View { Text(formatter.formatCurrentTime()) .onAppear { print(formatter.formatCurrentTime()) // 输出到控制台 } } }

在 JavaScript/Web 中调用:构建 JS 目标后,会生成.js文件。在 HTML 中引入并调用。

// 首先,确保在 jsMain 或 commonMain 中有一个方便的入口点 // 文件:shared/src/commonMain/kotlin/com/example/kmp/HelloWorld.kt fun helloWorld(): String { val formatter = TimeFormatter(object : Clock {}) return formatter.formatCurrentTime() }

构建后,在 HTML 中:

<!DOCTYPE html> <html lang="en"> <head> <script src="shared.js"></script> <!-- 引入编译后的 Kotlin/JS 代码 --> </head> <body> <script> console.log(helloWorld()); // 调用 Kotlin 函数,输出到浏览器控制台 document.body.innerText = helloWorld(); </script> </body> </html>

4.5 编译与运行

  1. 编译所有目标:在 IDEA 的 Gradle 工具窗口中,找到shared模块下的build任务,或直接运行./gradlew build
  2. 运行 Android:选择androidApp配置,运行到模拟器或真机。
  3. 运行 iOS:在 macOS 上,用 Xcode 打开iosApp目录下的.xcodeproj文件,选择模拟器运行。
  4. 运行 JS:执行./gradlew jsBrowserRun,Gradle 会启动一个开发服务器并打开浏览器。

5. 常见问题与排查思路

问题现象常见原因解决思路
编译错误:Unresolved reference: expect1. 代码放错了源码集目录。
2. 对应的actual实现缺失。
1. 检查expect声明是否在commonMain下。
2. 确保每个目标平台(androidMain,iosMain等)都提供了actual实现。
iOS 编译失败:找不到platform.FoundationKotlin/Native 与 iOS 框架链接问题。1. 确保iosMain依赖了正确的包。
2. 在sharedbuild.gradle.ktsiosMain依赖中添加implementation(kotlin(\"stdlib\"))
运行时错误:iOS 上调用 Kotlin 代码崩溃内存访问或并发问题,特别是涉及冻结对象。1. 确保使用新版内存管理器(Kotlin >=1.9.20)。
2. 避免在共享模块中直接传递复杂的、可变的 Kotlin 对象到 Swift。使用@Serializable数据类或纯函数接口。
JS 输出文件很大包含了整个 Kotlin 标准库。1. 使用kotlin.js.compiler.production=true(Gradle 属性)进行生产构建。
2. 使用webpack等工具进行代码分片和压缩。
Gradle 同步慢或失败网络问题或仓库配置错误。1. 检查build.gradle.kts中的仓库是否配置正确(如mavenCentral())。
2. 使用国内镜像源。
3. 尝试离线模式或清理 Gradle 缓存。
actual实现无法访问平台特定 API在错误的sourceSet中尝试调用平台 API。确保平台 API 的调用只存在于对应的平台源码集(如androidMain,iosMain)中。通用代码(commonMain)只能调用expect声明或纯 Kotlin 库。

6. 最佳实践与工程建议

将 Kotlin 用于多平台开发不仅仅是技术可行,更需要良好的工程实践来保证项目的可维护性和开发效率。

  1. 清晰架构,职责分离

    • 共享模块是核心:仅将真正的业务逻辑放入共享模块。例如:数据模型(data class)、网络请求定义(使用ktor-client等多平台库)、仓库层接口、Use Case 等。
    • 平台专属代码放两边:UI、系统服务调用(如通知、传感器)、平台特定的存储(SharedPreferenceson Android,UserDefaultson iOS)都应留在各自的应用模块中。
  2. 依赖管理策略

    • 优先使用多平台库:对于网络(ktor-client)、序列化(kotlinx.serialization)、协程(kotlinx-coroutines-core)、日期时间(kotlinx-datetime),务必使用其多平台版本。
    • 谨慎引入平台依赖:在共享模块中,通过expect/actual机制来抽象平台依赖。避免在commonMain中直接引入androidx.*UIKit等库。
  3. API 设计面向契约

    • 使用expect接口或抽象类来定义平台需要实现的契约。保持接口精简,聚焦能力而非实现细节。
    • 考虑使用interfacedependency injection(如 Koin 或 Kodein-DI 的多平台版本)来管理平台实现的注入,提高代码的可测试性。
  4. iOS 交互优化

    • 简化交互对象:在 Kotlin 和 Swift 之间传递的数据结构应尽可能简单。使用@Serializable标记的数据类,它们会被编译为易于 Swift 理解的格式。
    • 处理并发:如果共享模块使用协程,在 iOS 端暴露挂起函数(suspend)时,Kotlin/Native 编译器会生成带有completionHandler的 Objective-C 方法,方便在 Swift 中通过回调或async/await(Swift 5.5+)调用。
  5. 构建与持续集成

    • 配置缓存:启用 Gradle 构建缓存(org.gradle.caching=true)和 Kotlin 编译缓存,大幅提升构建速度。
    • CI/CD 流水线:为每个目标平台设置独立的构建和测试任务。对于 iOS,需要在 macOS 代理上运行。
    • 版本对齐:使用kotlin(\"bom\")或手动对齐所有 Kotlin 相关依赖(kotlinx-*系列)的版本,避免因版本不兼容导致的诡异问题。
  6. 渐进式采用

    • 不要试图一次性将整个应用重写为 KMP。可以从一个独立的、相对稳定的业务模块(如用户认证、配置管理、分析统计 SDK)开始,将其重构为共享模块,供各平台应用集成。
    • 先实现 Android 和 JVM 目标,再逐步扩展到 iOS 和 JS,以控制风险。

7. 总结与学习路线

通过以上步骤,我们完成了一次 Kotlin 代码在 JVM、Android、iOS 和 JS 平台上的快速穿梭。核心在于理解expect/actual机制和 Gradle 的多平台配置。你创建了一个共享的格式化器,并在四个不同的环境中执行了它,这证明了“一次编写,多处运行”的可行性。

本文关键点回顾:

  1. 概念:Kotlin Multiplatform 用于共享业务逻辑,UI 保持原生。
  2. 核心机制expect声明契约,actual提供平台实现。
  3. 配置:在build.gradle.kts中通过kotlin { targets { ... } }声明目标平台。
  4. 开发流程:在shared模块写通用逻辑和expect,在各平台源码集写actual实现和 UI。
  5. 交互:iOS 通过生成的框架调用,JS 通过编译后的.js文件调用。

下一步学习路线:

  1. 深入官方文档:仔细阅读 Kotlin 官方多平台文档 ,这是最权威的信息源。
  2. 探索多平台库:学习使用ktor-client(网络)、kotlinx.serialization(序列化)、sqldelight(数据库)等明星多平台库来构建更复杂的共享功能。
  3. UI 共享实践:如果你对共享 UI 感兴趣,可以研究Compose Multiplatform,它允许你用相同的声明式 UI 代码构建 Android、桌面和 Web 应用(iOS 仍处于 Alpha 阶段)。
  4. 实战小项目:尝试用 KMP 构建一个简单的笔记应用或天气应用,涵盖数据层共享和基础 UI 调用。
  5. 关注社区:关注 Kotlin 官方博客、Kotlin Slack 频道和 GitHub 上的热门 KMP 项目,了解最佳实践和新动态。

Kotlin Multiplatform 正在走向成熟,尤其是在 Compose Multiplatform 的推动下,其生态日益繁荣。虽然初期会遇到一些工具链和生态上的小挑战,但它为减少重复开发、保证逻辑一致性所提供的价值是巨大的。开始你的第一个 KMP 模块,从共享一个工具类或网络模型开始,逐步体验多平台开发的魅力吧。如果在实践中遇到具体问题,多查阅文档和社区讨论,大多数坑都已经有前人踩过并提供了解决方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询