Kotlin: Kotlin Multiplatform入门详解

最后更新:2026-08-26

KMP 是 Kotlin 的终极愿景——Charlie 的 写一次,在 JVM 后端、iOS 应用、JS 前端、Wasm 浏览器中复用。/ 机制让平台差异只在边界处存在。

1. 你将学到


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 多平台架构图

100%
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 中建议并行构建各目标。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个 KMP 项目,在 中定义 ,在 JVM 和 JS 目标中使用。提示:
  2. 进阶题(难度⭐⭐):用 在 JVM 和 JS 平台返回不同值。提示: expect + / actual
  3. 挑战题(难度⭐⭐⭐):实现跨平台 HTTP 客户端: 定义接口, 用 实现, 用 API 实现。提示:

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏