Kotlin: Kotlin Multiplatform入门详解
最后更新:2026-08-26
KMP 是 Kotlin 的终极愿景——Charlie 的 写一次,在 JVM 后端、iOS 应用、JS 前端、Wasm 浏览器中复用。/ 机制让平台差异只在边界处存在。
1. 你将学到
- KMP 架构: / / /
- 共享模块策略:网络层、数据模型、业务逻辑
- Gradle 多平台配置
- 互操作:JVM ↔ Java / iOS ↔ Swift / JS ↔ JavaScript
- Charlie 实战: 在 commonMain 共享
2. 一个架构师的真实故事
(1) 痛点:三端重复实现同一逻辑
Charlie 的公司有 JVM 后端、iOS 应用和 JS 前端,同样的 逻辑在三个平台分别用 Java/Swift/TypeScript 实现了三遍。修一个 Bug 要改三处,一个月出了 2 次不一致 Bug。
(2) KMP 共享代码的解法
KOTLIN
// commonMain: ONE implementation for all platforms
class OrderValidator {
fun validate(order: Order): List`<String>` {
val errors = mutableListOf`<String>`()
if (!order.id.startsWith("ORD-")) errors.add("Invalid ID")
if (order.total < 0) errors.add("Negative total")
return errors
}
}
一份代码,三端复用。修 Bug 只需改一处,行为天然一致。
3. KMP 架构
(1) 项目结构
TEXT
📖 仅展示
shared/
├── src/
│ ├── commonMain/kotlin/ # Shared code (all platforms)
│ │ └── com/order/
│ │ ├── Order.kt
│ │ ├── OrderValidator.kt
│ │ └── Platform.kt # expect declarations
│ ├── commonTest/kotlin/ # Shared tests
│ ├── jvmMain/kotlin/ # JVM-specific code
│ │ └── com/order/
│ │ └── Platform.kt # actual for JVM
│ ├── iosMain/kotlin/ # iOS-specific code
│ │ └── com/order/
│ │ └── Platform.kt # actual for iOS
│ └── jsMain/kotlin/ # JS-specific code
│ └── com/order/
│ └── Platform.kt # actual for JS
└── build.gradle.kts
(2) expect / actual 机制
KOTLIN
// commonMain: declare what you need (expect)
expect fun getPlatformName(): String
expect class DateFormatter() {
fun format(timestamp: Long): String
}
// jvmMain: provide JVM implementation (actual)
actual fun getPlatformName(): String = "JVM"
actual class DateFormatter actual constructor() {
actual fun format(timestamp: Long): String =
java.text.SimpleDateFormat("yyyy-MM-dd").format(timestamp)
}
// iosMain: provide iOS implementation (actual)
actual fun getPlatformName(): String = "iOS"
actual class DateFormatter actual constructor() {
actual fun format(timestamp: Long): String {
// Use NSDateFormatter
return NSDateFormatter().apply {
dateFormat = "yyyy-MM-dd"
}.stringFromDate(NSDate(timestamp / 1000.0))
}
}
// jsMain: provide JS implementation (actual)
actual fun getPlatformName(): String = "JS"
actual class DateFormatter actual constructor() {
actual fun format(timestamp: Long): String {
// Use JavaScript Date
return js("new Date(timestamp).toISOString().split('T')[0]")
}
}
(3) KMP 多平台架构图
flowchart TD
A[commonMain<br/>Shared Business Logic] --> B[jvmMain<br/>JVM Actual]
A --> C[iosMain<br/>iOS Actual]
A --> D[jsMain<br/>JS Actual]
A --> E[wasmMain<br/>Wasm Actual]
B --> B1[Spring Boot<br/>Backend]
C --> C1[iOS App<br/>Swift Interop]
D --> D1[Node.js / Browser]
E --> E1[Web Assembly Runtime]
4. Gradle 多平台配置
(1) build.gradle.kts
KOTLIN
plugins {
kotlin("multiplatform") version "1.9.22"
}
group = "com.order"
version = "1.0.0"
repositories {
mavenCentral()
}
kotlin {
// Declare target platforms
jvm {
compilations.all {
kotlinOptions.jvmTarget = "17"
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
}
iosX64()
iosArm64()
iosSimulatorArm64()
js(IR) {
browser()
nodejs()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val jvmMain by getting
val jvmTest by getting
val iosMain by getting
val jsMain by getting
}
}
5. 共享模块策略
(1) 共享什么、不共享什么
| 层 | 共享策略 | 原因 |
|---|---|---|
| 数据模型 | ✅ 完全共享 | 纯数据,无平台依赖 |
| 业务逻辑 | ✅ 完全共享 | 核心规则,必须一致 |
| 网络层 | ✅ 共享接口 + expect HTTP | 接口统一,实现各异 |
| 序列化 | ✅ 共享(kotlinx.serialization) | 多格式原生支持 |
| UI | ❌ 各平台独立 | UI 框架差异大 |
| 数据库 | ⚠️ 共享 SQL/expect | SQL 可共享,驱动各异 |
| 日志 | ⚠️ expect/actual | 各平台日志 API 不同 |
(2) 共享策略模式
KOTLIN
// Pattern 1: Pure shared code (no expect/actual needed)
data class Order(val id: String, val total: Double, val status: String)
class OrderValidator {
fun validate(order: Order): List`<String>` = buildList {
if (!order.id.startsWith("ORD-")) add("Invalid ID format")
if (order.total < 0) add("Negative total")
if (order.status !in validStatuses) add("Invalid status")
}
companion object {
private val validStatuses = setOf("PENDING", "CONFIRMED", "SHIPPED", "CANCELLED")
}
}
// Pattern 2: Interface + expect factory
interface HttpClient {
suspend fun get(url: String): String
suspend fun post(url: String, body: String): String
}
expect fun createHttpClient(): HttpClient
// Pattern 3: expect function for platform-specific behavior
expect fun logDebug(tag: String, message: String)
6. 互操作
(1) 各平台互操作方式
| 平台 | 互操作 | 说明 |
|---|---|---|
| JVM ↔ Java | 双向无缝 | Kotlin 直接调用 Java,Java 可调用 Kotlin |
| iOS ↔ Swift | 双向 | Kotlin 编译为 Obj-C 框架,Swift 无缝调用 |
| JS ↔ JavaScript | 双向 | 函数调用 JS, 导出 Kotlin |
| Wasm ↔ JS | 单向(JS 调 Kotlin) | Wasm 模块导出函数供 JS 调用 |
(2) JVM 互操作示例
KOTLIN
// Kotlin calling Java
val order = JavaOrderService() // Java class
order.processOrder("ORD-001") // Java method
// Java calling Kotlin (generated bytecode is standard)
// OrderKt.processOrder(order); // Top-level function
(3) JS 互操作示例
KOTLIN
// Kotlin calling JavaScript
fun fetchFromApi(url: String): dynamic {
return js("fetch(url).then(r => r.json())")
}
// Export Kotlin to JavaScript
@JsExport
class OrderValidator {
fun validate(id: String, total: Double): Boolean {
return id.startsWith("ORD-") && total >= 0
}
}
7. 完整示例:跨平台 OrderValidator
KOTLIN
// ============================================
// OrderProcessor - KMP Shared Module
// Feature: Shared OrderValidator with platform logging
// ============================================
// --- commonMain ---
data class Order(val id: String, val total: Double, var status: String, val customer: String)
// expect: platform-specific declaration
expect fun logInfo(tag: String, message: String)
class OrderValidator {
fun validate(order: Order): ValidationResult {
val errors = mutableListOf`<String>`()
if (!order.id.startsWith("ORD-")) errors.add("Invalid ID format: ${order.id}")
if (order.total < 0) errors.add("Negative total: ${order.total}")
if (order.total > 1_000_000) errors.add("Total exceeds maximum: ${order.total}")
if (order.status !in VALID_STATUSES) errors.add("Invalid status: ${order.status}")
val result = if (errors.isEmpty()) ValidationResult.Valid else ValidationResult.Invalid(errors)
logInfo("OrderValidator", "Validated ${order.id}: $result")
return result
}
fun calculatePriority(order: Order): String = when {
order.total > 10_000 -> "HIGH"
order.total > 1_000 -> "MEDIUM"
else -> "LOW"
}
companion object {
private val VALID_STATUSES = setOf("PENDING", "CONFIRMED", "SHIPPED", "DELIVERED", "CANCELLED")
}
}
sealed class ValidationResult {
object Valid : ValidationResult()
data class Invalid(val errors: List`<String>`) : ValidationResult()
}
// --- jvmMain ---
// actual fun logInfo(tag: String, message: String) {
// println("[$tag] $message") // Or use SLF4J
// }
// --- iosMain ---
// actual fun logInfo(tag: String, message: String) {
// NSLog("$tag: $message")
// }
// --- jsMain ---
// actual fun logInfo(tag: String, message: String) {
// console.log("[$tag] $message")
// }
// --- Demo (JVM target) ---
fun main() {
// Simulate JVM actual
// actual fun logInfo(tag: String, message: String) = println("[$tag] $message")
val validator = OrderValidator()
val orders = listOf(
Order("ORD-001", 299.99, "PENDING", "Alice"),
Order("BAD-002", -50.0, "INVALID", "Bob"),
Order("ORD-003", 15_000.00, "CONFIRMED", "Charlie"),
Order("ORD-004", 2_000_000.00, "PENDING", "Dave")
)
println("=== KMP OrderValidator Demo ===")
orders.forEach { order ->
val result = validator.validate(order)
val priority = validator.calculatePriority(order)
when (result) {
is ValidationResult.Valid -> println(" ✅ ${order.id}: Valid (Priority: $priority)")
is ValidationResult.Invalid -> println(" ❌ ${order.id}: ${result.errors}")
}
}
}
输出:
TEXT
📖 仅展示
=== KMP OrderValidator Demo ===
✅ ORD-001: Valid (Priority: LOW)
❌ BAD-002: [Invalid ID format: BAD-002, Negative total: -50.0, Invalid status: INVALID]
✅ ORD-003: Valid (Priority: HIGH)
❌ ORD-004: [Total exceeds maximum: 2000000.0]
❓ 常见问题
Q KMP 和 Flutter 有什么区别?
A KMP 共享业务逻辑(UI 各平台原生),Flutter 共享 UI(Skia 渲染)。KMP 更灵活(保留原生 UI 体验),Flutter 更统一(一套 UI)。两者可互补。
Q KMP 生产就绪了吗?
A JVM 和 Android 目标已完全稳定;iOS 目标稳定;JS 和 Wasm 在快速成熟中。Netflix、VMware、Cash App 等公司已大规模生产使用。
Q expect/actual 可以用于类吗?
A 可以。 声明跨平台接口, 提供平台实现。构造器和方法的签名必须完全匹配。
Q 共享模块和平台模块如何组织?
A 共享逻辑放 commonMain,平台差异用 expect/actual 边界化。避免 commonMain 中出现平台特定代码——用 expect 声明抽离。
Q KMP 对 iOS 开发者友好吗?
A 友好。KMP 编译为 Obj-C 框架,Swift 无缝调用。iOS 开发者可以只关心 Swift 侧代码,不需要懂 Kotlin。
Q KMP 的构建速度如何?
A 多目标编译会增加构建时间(每个目标独立编译)。Gradle 增量编译和 Kotlin 编译器缓存可缓解。CI/CD 中建议并行构建各目标。
📖 小节
- KMP 核心架构:(共享)+ (平台特定)
- 声明跨平台需求, 提供平台实现
- 数据模型和业务逻辑完全共享,UI 和平台 API 用 expect/actual
- Gradle 声明多目标
- 各平台互操作:JVM↔Java 无缝、iOS↔Swift 通过 Obj-C 框架、JS↔JS 通过
- KMP 不是全有或全无——可以只共享部分模块渐进式采用
📝 作业
- 基础题(难度⭐):创建一个 KMP 项目,在 中定义 ,在 JVM 和 JS 目标中使用。提示:
- 进阶题(难度⭐⭐):用 在 JVM 和 JS 平台返回不同值。提示: expect + / actual
- 挑战题(难度⭐⭐⭐):实现跨平台 HTTP 客户端: 定义接口, 用 实现, 用 API 实现。提示: