【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 正面交锋。大乘境·中阶见。

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

相关推荐
XiaoLeisj28 分钟前
Android Memory Profiler:堆内存指标、内存抖动与泄漏定位
android·性能优化·内存抖动·内存泄露·memory profiler
未来猫咪花30 分钟前
Everything is ViewModel:让状态管理回到对象世界
android·flutter·ios
古法安卓36 分钟前
Android-重启流程源码解析
android·java·android studio
协议的旁观者2 小时前
Android 高级逆向实战(一):对抗 360 企业加固,Native 抽取还原与 Activity 生命周期重建
android
mmsx4 小时前
osmdroid 屏幕坐标与测量坐标互转:中心点+比例尺仿射换算
android·源码·地图·osmdroid
00后程序员张4 小时前
Android证书绑定抓包失败?Android SSL Pinning绕过实战指南
android·网络协议·计算机网络·网络安全·adb·https·ssl
杉氧5 小时前
页面栈与路由:React Navigation 与 Expo Router 深度实践
android·前端·react native
XiaoLeisj5 小时前
Jetpack Compose 知识点
android·kotlin·android jetpack·compose
又见情义6 小时前
RK3568 Android 13开机时间优化-系统应用裁剪实践
android