【Kotlin 协程修仙录 · 大乘境 · 初阶】 | 跳出三界:协程在 KMP 与后端开发中的跨平台之道

前言

合体大圆满,你已将协程与 Android 四大组件完美融合。CoroutineWorkerRoomRetrofitCompose 皆与协程浑然一体,你的 Android 协程修为已臻化境。

然而,一个全新的世界正在向你招手:

公司决定采用 Kotlin Multiplatform(KMM)统一 iOS 和 Android 的业务逻辑。你需要在 iOS 上也能运行协程 ?Swift 的异步怎么办? 后端团队开始用 Ktor 搭建服务,他们也在用协程处理高并发。后端协程和 Android 协程是一回事吗? 你听说协程可以编译成 WebAssembly ,甚至能跑在 JavaScript 引擎上。协程的"挂起"魔法在单线程的 JS 里如何实现?

这些问题指向协程的更高境界------跨平台与后端开发。协程从来不只是 Android 的专属武器。它是 Kotlin 语言级的并发框架,理论上可以运行在任何支持 Kotlin 的平台上。理解协程的跨平台本质,你才能真正跳出 Android 的桎梏,看到协程设计的大道真意。

本讲是大乘境的初阶修炼。你将跳出 Android 三界,俯瞰协程的跨平台天地:

  • 理解 Kotlin 协程的多平台架构:commonjvmjsnative 模块的分工。
  • 掌握 KMM 中如何共享协程代码,以及在 iOS 上的实际运行机制。
  • 对比 Android 协程与后端 Ktor 协程的异同------线程池、调度器、并发模型。
  • 了解协程在 JavaScript 单线程环境中的实现原理(Promise 转换)。
  • 展望协程在未来的演进方向(虚拟线程、结构化并发推广)。

准备好跳出三界,以更高维度审视协程了吗?我们开始。

千曲 而后晓声,观千剑 而后识器。虐它千百遍 方能通晓其真意


协程的多平台架构:一套代码,四处运行

KMP 的协程模块

Kotlin 协程库从设计之初就考虑了多平台支持。打开协程的源码仓库,你会看到这样的模块结构:

bash 复制代码
kotlinx.coroutines/
├── common/           # 跨平台公共代码
├── jvm/              # JVM 平台特定实现
├── js/               # JavaScript 平台特定实现
├── native/           # Native (iOS/macOS/Linux) 平台特定实现
└── android/          # Android 平台扩展

common 模块定义了协程的核心抽象:suspend 关键字、CoroutineScopeJobChannelFlow 等 API 的接口和部分实现。各平台模块则负责实现与底层线程/调度系统的对接。

flowchart LR subgraph Common[common 公共模块] C1[suspend 抽象] C2[CoroutineScope 接口] C3[Flow / Channel API] end subgraph JVM[JVM 平台] J1[Thread 线程池] J2[CoroutineScheduler] end subgraph Native[Native 平台 iOS] N1[Grand Central Dispatch] N2[Worker 线程] end subgraph JS[JavaScript 平台] S1[Promise / Event Loop] S2[单线程调度] end Common --> JVM Common --> Native Common --> JS style Common fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style JVM fill:#e3f2fd,stroke:#1976d2,stroke-width:2px style Native fill:#fff3e0,stroke:#f57c00,stroke-width:2px style JS fill:#ffcdd2,stroke:#b71c1c,stroke-width:2px

核心设计哲学:协程的"挂起与恢复"是平台无关的(通过状态机和 Continuation),但"挂起后线程的调度"是平台相关的。这种分离使得协程能横跨 JVM、iOS、JS 三大生态。

KMM 中共享协程代码

KMM 项目的典型结构:

bash 复制代码
shared/
├── commonMain/       # 公共业务逻辑,包含 suspend 函数、Flow 等
├── androidMain/      # Android 特定实现(如 Dispatchers.Main)
└── iosMain/          # iOS 特定实现

commonMain 中,你可以像在 Android 中一样写协程代码:

kotlin 复制代码
// shared/commonMain/kotlin/UserRepository.kt
expect class PlatformDispatcher {
    val io: CoroutineDispatcher
    val main: CoroutineDispatcher
}

class UserRepository(private val api: UserApi) {
    suspend fun fetchUser(id: String): User {
        return withContext(PlatformDispatcher.io) {
            api.getUser(id)
        }
    }
}

androidMainiosMain 中分别提供 actual 实现:

kotlin 复制代码
// androidMain
actual class PlatformDispatcher {
    actual val io = Dispatchers.IO
    actual val main = Dispatchers.Main
}

// iosMain
actual class PlatformDispatcher {
    actual val io = Dispatchers.Default // iOS 上通常用 Default
    actual val main = Dispatchers.Main
}

这样,核心业务逻辑的协程代码就可以一次编写,双端共享


iOS 上的协程:GCDWorker 的幕后功臣

协程如何在 iOS 上运行?

Kotlin/Native 将 Kotlin 代码编译为 LLVM 字节码,最终生成 iOS 可执行的二进制。协程在 iOS 上的调度器,底层绑定的是 Apple 的 Grand Central Dispatch(GCD)Worker 线程机制。

  • Dispatchers.Main:绑定到主队列 dispatch_get_main_queue()
  • Dispatchers.Default:使用 CPU 核心数个 Worker 线程的池。
  • Dispatchers.IO:在 Kotlin/Native 中目前与 Default 相同(未来可能优化)。
flowchart LR subgraph Kotlin[Native 协程] S[suspend 函数] D[Dispatchers.Main] end subgraph iOS[iOS 底层] GCD[GCD 主队列] Worker[Worker 线程池] end S --> D --> GCD S --> IO[Dispatchers.IO] --> Worker style Kotlin fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style iOS fill:#e3f2fd,stroke:#1976d2,stroke-width:2px

Swift 异步的互操作

Swift 5.5 引入了 async/await,其底层同样是基于 GCD 的协程(Swift 称为"任务")。Kotlin 协程与 Swift 异步可以通过回调或 suspend 函数的导出进行互操作。例如,你可以将一个 Kotlin 的 suspend 函数导出为 Swift 的 async 函数,让 iOS 开发者像调用原生异步函数一样使用。

kotlin 复制代码
// Kotlin 侧
@Throws(IOException::class)
suspend fun fetchData(): String = ...

// 导出后,Swift 侧可以这样调用:
// Task {
//     let data = try await FetchDataKt.fetchData()
// }

这种互操作能力,使得协程成为 KMM 中统一异步逻辑的最佳选择。


后端协程:Ktor 与高并发场景的协程实践

后端协程与 Android 协程的异同

对比维度 Android 协程 后端协程(Ktor/Spring)
主线程调度器 Dispatchers.Main(UI 线程) 通常无主线程概念
IO 调度器 弹性线程池(最大 64) 同样弹性,但可配置更大
典型并发模型 少量协程处理 UI 事件 海量协程处理网络请求(C10K)
生命周期管理 依赖 viewModelScope 等 Android 组件 依赖请求上下文(如 Ktor 的 call
结构化并发 同样遵循 同样遵循,但更强调请求边界
flowchart LR subgraph Android[Android 协程] A1[Dispatchers.Main UI] A2[viewModelScope] A3[lifecycleScope] end subgraph Backend[后端协程] B1[Dispatchers.IO / Default] B2[请求级 CoroutineScope] B3[应用级 SupervisorJob] end style Android fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style Backend fill:#ffcdd2,stroke:#b71c1c,stroke-width:2px

Ktor 中的协程:请求即协程

Ktor 是 Kotlin 官方推出的异步 Web 框架,它从设计之初就完全基于协程。每一个 HTTP 请求都在一个协程中处理,框架自动管理协程的生命周期。

kotlin 复制代码
// Ktor Application.kt
fun Application.module() {
    routing {
        get("/user/{id}") {
            val id = call.parameters["id"] ?: throw BadRequestException("Missing id")
            val user = userRepository.findById(id) // suspend 函数
            call.respond(user)
        }
    }
}

在 Ktor 中,你可以直接调用 suspend 函数进行数据库查询、网络调用,而无需关心线程池的细节。框架使用 Dispatchers.IO 或自定义调度器处理阻塞操作,并通过结构化并发确保请求结束时所有子协程被自动取消。

高并发下的协程优势

传统 Servlet 容器(如 Tomcat)为每个请求分配一个线程。当并发请求达到数千时,线程上下文切换的开销会急剧上升,内存也会被线程栈耗尽。

协程后端框架(Ktor、Spring WebFlux with coroutines)则可以为每个请求分配一个协程。协程的内存占用极小(几百字节),且能在 I/O 等待时让出线程,使得少量线程就能支撑海量并发。

flowchart LR subgraph ThreadModel[线程模型 每请求一线程] R1[请求1] --> T1[线程1] R2[请求2] --> T2[线程2] R3[请求3] --> T3[线程3] R1000[请求1000] --> T1000[线程1000 内存爆] end subgraph CoroutineModel[协程模型 少量线程] C1[协程1] --> W1[Worker线程1] C2[协程2] --> W1 C3[协程3] --> W2[Worker线程2] C1000[协程1000] --> W2 end style ThreadModel fill:#ffcdd2,stroke:#b71c1c,stroke-width:2px style CoroutineModel fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px

JavaScript 单线程上的协程:Promise 的华丽转身

JS 平台的协程实现

Kotlin/JS 将 Kotlin 代码编译为 JavaScript,运行在浏览器或 Node.js 的单线程事件循环上。协程的"挂起"在 JS 中如何实现不阻塞事件循环?

答案在于 Promise 。Kotlin/JS 的协程编译器会将 suspend 函数转换为返回 Promise 的 JavaScript 函数。状态机的挂起与恢复,最终映射为 Promise.then() 的链式调用。

javascript 复制代码
// Kotlin suspend fun fetchData(): String
// 编译后大致等价于:
function fetchData(continuation) {
    return new Promise((resolve, reject) => {
        // 状态机逻辑...
    });
}
flowchart LR subgraph Kotlin[Kotlin 源码] S[suspend fun] end subgraph Compiler[编译器] C[转换为状态机 + Promise] end subgraph JS[JavaScript 运行时] P[Promise] EL[Event Loop] end S --> C --> P --> EL style Kotlin fill:#c8e6c9,stroke:#2e7d32 style Compiler fill:#fff3e0,stroke:#f57c00 style JS fill:#ffcdd2,stroke:#b71c1c

JavaScript 异步互操作

你可以直接在 Kotlin/JS 中调用返回 Promise 的 JavaScript 函数,Kotlin 提供了 Promise<T>.await() 扩展函数,将其转换为 suspend 函数:

kotlin 复制代码
suspend fun fetchFromJsApi(): String {
    val promise = js("fetch('/api/data').then(res => res.text())")
    return promise.await()
}

同样,Kotlin 的 suspend 函数也可以导出为返回 Promise 的函数,供 JavaScript 代码调用。


协程的未来:虚拟线程与结构化并发的推广

Project Loom 与协程的殊途同归

Java 19 引入了虚拟线程,其设计目标与 Kotlin 协程高度相似:让开发者以同步代码风格编写高并发程序,且不阻塞平台线程。

对比项 Kotlin 协程 Java 虚拟线程
挂起机制 编译器生成状态机 JVM 底层支持,可挂起任意栈帧
语言关键字 suspend 无需关键字,普通方法即可
调度器 用户态调度器 JVM 内置调度器
成熟度 生产可用多年 较新,生态仍在建设

虚拟线程的普及可能会让协程在后端领域面临竞争,但在 Android 和 KMM 领域,协程的地位仍然不可撼动。

结构化并发的跨语言影响

Kotlin 协程提出的结构化并发 理念,正在影响其他语言的异步设计。Swift 的 TaskTaskGroup、Rust 的 async 作用域、甚至 Java 的 StructuredTaskScope,都在不同程度上借鉴了"父子协程生命周期约束"的思想。

协程的修仙之路,不仅在 Kotlin 内部延续,更在塑造整个异步编程的未来。

flowchart LR subgraph Kotlin[Kotlin 结构化并发] K1[coroutineScope] K2[supervisorScope] end subgraph Influence[跨语言影响] I1[Swift TaskGroup] I2[Java StructuredTaskScope] I3[Rust async scopes] end Kotlin --> Influence style Kotlin fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style Influence fill:#e3f2fd,stroke:#1976d2,stroke-width:2px

实战:KMM 共享 ViewModel 中的协程

让我们构建一个 KMM 项目中的共享 ViewModel,使用协程加载数据。

kotlin 复制代码
// commonMain/kotlin/SharedViewModel.kt
expect class SharedViewModel() {
    val uiState: StateFlow<UiState>
    fun loadData()
}

// androidMain/kotlin/SharedViewModel.kt
actual class SharedViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    actual val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    actual fun loadData() {
        viewModelScope.launch {
            _uiState.value = try {
                val data = repository.fetchData()
                UiState.Success(data)
            } catch (e: Exception) {
                UiState.Error(e.message)
            }
        }
    }
}

// iosMain/kotlin/SharedViewModel.kt
actual class SharedViewModel {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    actual val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    actual fun loadData() {
        scope.launch {
            _uiState.value = try {
                val data = repository.fetchData()
                UiState.Success(data)
            } catch (e: Exception) {
                UiState.Error(e.message)
            }
        }
    }
    
    fun clear() {
        scope.cancel()
    }
}

在 iOS 侧,你需要在合适的时机调用 clear() 来取消协程(例如在 deinit 中)。

flowchart LR subgraph Common[commonMain 共享] API[expect SharedViewModel] State[UiState StateFlow] end subgraph Android[androidMain] VM1[actual ViewModel] Scope1[viewModelScope] end subgraph iOS[iosMain] VM2[actual 普通类] Scope2[手动管理 CoroutineScope] end API --> VM1 API --> VM2 VM1 --> Scope1 VM2 --> Scope2 style Common fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style Android fill:#c8e6c9,stroke:#388e3c style iOS fill:#e3f2fd,stroke:#1976d2

常见误区与避坑指南

误区 1:认为 KMMDispatchers.MainiOS 上不可用

Dispatchers.Main 在 Kotlin/Native 的 iOS 目标上是完全可用 的,它绑定到主线程的 GCD 队列。你可以在 iosMain 中安全地使用它来更新 UI(通过 Compose Multiplatform 或其他 UI 框架)。

误区 2:在后端中滥用 GlobalScope

kotlin 复制代码
// 后端错误示例
fun handleRequest() {
    GlobalScope.launch {
        // 请求结束时协程不会自动取消
        process()
    }
}

正确做法 :使用请求级别的 CoroutineScope,或框架提供的 Scope。

误区 3:在 JS 平台使用 runBlocking

runBlocking 在 JS 平台上不可用 ,因为 JS 是单线程、无法阻塞事件循环。所有协程启动必须使用 launchasync 配合回调/Promise。


最佳实践

  1. 在 KMM 中,将 suspend 函数和 Flow 定义在 commonMain:最大化代码复用。
  2. 使用 expect/actual 为不同平台提供调度器配置 :而不是在公共代码中硬编码 Dispatchers.IO
  3. 后端开发中,始终使用请求级或应用级的 CoroutineScope:避免协程泄漏。
  4. 在 JS 平台,优先使用 Promise.await() 与 JavaScript 异步互操作
  5. 关注 Project Loom 与协程的演进,适时调整技术选型

九、总结与下回预告

恭喜,你已跳出 Android 三界,看到了协程在跨平台与后端开发的广袤天地!大乘境初阶修炼完成!

本讲核心收获

  • Kotlin 协程通过 common 模块定义抽象,各平台负责底层实现。
  • iOS 上的协程基于 GCD 和 Worker 线程,可与 Swift 异步互操作。
  • 后端协程(Ktor)以请求为 Scope,少量线程支撑海量并发。
  • JS 平台将协程编译为 Promise,运行于单线程事件循环。
  • 结构化并发的理念正在影响 Swift、Java、Rust 等语言。

在下一讲 【大乘境·中阶】 中,我们将深入协程的源码核心:CoroutineScheduler 的任务分发算法、DispatchedContinuation 的拦截机制、以及协程的启动流程全解析。届时你将真正看透协程的每一行源码。


【当前境界修为面板】

当前境界 修炼技能 修炼进度 修炼心得
大乘境 · 初阶 1、KMM 跨平台协程诀 2、Ktor 后端协程术 3、JS Promise 转换法 当前进度35% 修为350/1000 下一突破[大乘境 · 中阶] (需领悟:CoroutineScheduler 源码、DispatchedContinuation 拦截机制、协程启动流程) 协程不限于Android。一套suspend代码,横跨JVMiOSJS三界。

【本讲思考题】

  1. 表象题 :Kotlin 协程在 iOS 上的 Dispatchers.Main 底层绑定的是什么?

  2. 场景题 :你需要设计一个 KMM 共享的 ImageLoader,它使用协程加载网络图片,并需要在 Android 和 iOS 上都能限制最大并发数为 5。如何设计这个类的 expect/actual 结构?

  3. 原理题 :为什么 runBlocking 在 Kotlin/JS 平台上不可用?请从 JavaScript 事件循环的特性解释。


道友,大乘境的大门已敞开。协程的跨平台之道,让你不再局限于 Android 一隅。下一讲,我们将潜入协程源码的最深处,与 CoroutineSchedulerDispatchedContinuation 正面交锋。大乘境·中阶见。

欢迎一键四连关注 + 点赞 + 收藏 + 评论

相关推荐
千里马学框架8 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台8 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone8 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc9 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo10 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770011 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work11 小时前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼12 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work12 小时前
ch21 签名、校验与发版
kotlin
AFinalStone13 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui