1. KMP共享业务逻辑工程导论

Kotlin Multiplatform(KMP)是 JetBrains 主导的跨平台技术方案,它的核心主张与 Flutter、React Native 这类“跨平台 UI 框架”有本质区别:KMP 不要求你统一 UI,而是让你把与平台无关的业务逻辑抽出来,编译成各平台原生的产物(Android 上是 JVM 字节码,iOS 上是 Native 二进制,Web 上是 JS/Wasm),再由各平台用自己的原生 UI 去消费。换句话说,KMP 解决的是“一份逻辑,多端复用”,而不是“一份界面,多端渲染”。

理解这个定位,需要先看清一个典型移动项目的分层。最上层是 UI 层,Android 用 Compose/View,iOS 用 SwiftUI/UIKit,它们与平台强绑定,复用收益低、风险高;中间是业务逻辑层,包括领域模型、用例、校验规则、状态机、网络与数据访问;最下层是平台能力层,如文件系统、定位、蓝牙、推送。KMP 的共享边界通常落在中间层,并向下通过 expect/actual 机制按需接入平台能力。下面这张图给出了 KMP 在工程中的实际位置与共享边界。

KMP 共享边界与工程分层 平台 UI 层(不共享) Android · Compose / View iOS · SwiftUI / UIKit Web / Desktop · 原生 UI 共享业务逻辑层(KMP 核心复用区) 领域模型 / 实体 Order, User, Money 用例 / 业务规则 校验、计算、状态机 数据访问 / 网络 Repository, Ktor 序列化 / 存储 kotlinx.serialization 编译产物:Android → JVM 字节码 | iOS → Native 二进制 | Web → JS / Wasm 平台能力层(expect / actual 桥接) 文件 / 数据库驱动 定位 / 蓝牙 / 传感器 推送 / 系统 API 调用共享逻辑 按需调用平台能力 共享边界原则:平台无关的规则与数据流下沉共享,平台相关的实现留在各自源集

为什么值得做这件事?以电商 App 的“购物车金额计算”为例:它涉及商品单价、数量、优惠券叠加规则、满减门槛、税费与运费。如果 Android 和 iOS 各写一遍,任何一次促销规则调整都要改两处,且极易出现“Android 显示 88 元、iOS 显示 92 元”的对账事故。把这段逻辑放进 KMP 的 commonMain,两端调用同一个 CartCalculator,规则只维护一份,测试也只写一份。这就是共享业务逻辑最直接的价值:消除重复实现带来的行为漂移。

共享的收益并非没有代价。KMP 的工程化目标可以归纳为四条:其一,边界清晰——明确哪些代码进 commonMain、哪些留在 androidMain/iosMain,避免把平台 API 泄漏进共享层;其二,可测试——共享逻辑应能在 JVM 上以纯单元测试快速验证,不依赖模拟器;其三,可交付——共享模块要能作为标准依赖被各端集成,Android 走 Gradle 依赖,iOS 走 XCFramework 或 CocoaPods;其四,可演进——新增平台(如 Desktop、Wasm)时,共享层尽量零改动。

判断一段代码是否适合共享,可以问三个问题:它是否依赖具体平台 API?它是否包含 UI 渲染?它的行为是否需要在多端保持完全一致?前两个答案为“否”、第三个为“是”时,通常就是理想的共享候选。

从工程结构上看,一个典型的 KMP 项目会包含以下源集(source set):commonMain 存放共享逻辑,commonTest 存放共享测试,androidMain 与 iosMain 分别存放平台实现,iosMain 下还可细分 iosX64、iosArm64、iosSimulatorArm64 等目标。Gradle 构建脚本中通过 kotlin { androidTarget(); iosX64(); iosArm64(); iosSimulatorArm64() } 声明目标,再用 sourceSets 配置依赖。下面是一段最小化的构建配置示例,用于说明共享模块如何声明多平台目标:

// shared/build.gradle.kts
kotlin {
    androidTarget()
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
                implementation("io.ktor:ktor-client-core:2.3.11")
            }
        }
        val commonTest by getting {
            dependencies { implementation(kotlin("test")) }
        }
        val androidMain by getting {
            dependencies { implementation("io.ktor:ktor-client-okhttp:2.3.11") }
        }
        val iosMain by getting {
            dependencies { implementation("io.ktor:ktor-client-darwin:2.3.11") }
        }
    }
}

注意这里的一个关键设计:ktor-client-core 放在 commonMain,而具体的 HTTP 引擎 okhttp 与 darwin 分别放在平台源集。这正是 KMP 工程化的典型手法——接口与抽象共享,实现按平台注入。共享层只依赖 HttpClient 抽象,运行时由各平台提供引擎,从而既复用了请求构建、序列化、错误处理逻辑,又保留了平台网络栈的原生能力。

学习路径上,本课程会沿着“认知 → 搭建 → 共享 → 桥接 → 交付”的主线推进。先建立对 KMP 定位与边界的判断力,再动手搭建可运行的多平台工程骨架;随后深入共享层的核心构件:协程与并发模型、序列化、网络与数据持久化;接着处理最容易被低估的环节——expect/actual 桥接与平台能力接入;最后落到工程交付:iOS 框架集成、依赖管理、测试策略与 CI 构建。每一章都会围绕真实可运行的代码与可观察的结果展开,而不是停留在概念介绍。