—— 从 GlobalScope、CoroutineScope、LifecycleScope 到 ViewModelScope彻底讲透 Kotlin 协程最核心的设计哲学前面几篇我们已经讲了CoroutineContext ↓ Job ↓ Dispatcher ↓ launch / async ↓ Exception学到这里很多同学已经能够熟练使用协程。但是真正的协程核心 其实还没出现。这个核心就是Structured Concurrency 结构化并发如果说CoroutineContext 是协程运行环境 Job 是协程生命周期管理器 Dispatcher 是协程调度器那么Structured Concurrency 就是整个协程体系的灵魂。甚至可以说没有结构化并发 就没有 Kotlin 协程。一、什么叫结构化并发先不要看官方定义。直接看现实生活。假设公司里有个项目经理项目经理 │ ├── 开发A │ ├── 开发B │ └── 开发C项目经理离职A继续上班 B继续上班 C继续上班显然不合理。正常情况项目经理离开 ↓ 整个项目结束这就是结构化而协程也是一样。例如viewModelScope.launch { launch { } launch { } launch { } }结构ViewModelScope │ └── Parent Job │ ├── Child1 │ ├── Child2 │ └── Child3这棵树有父亲 有孩子 有归属这就是结构化并发二、为什么 Google 要发明结构化并发先看 Java 时代。例如new Thread(() - { }).start();随便开线程。问题线程是谁创建的 什么时候结束 谁负责回收不知道。于是线程越来越多 生命周期越来越乱 内存泄漏越来越严重Kotlin 团队发现协程不能重蹈覆辙于是提出所有协程必须有归属即Scope三、Scope 到底是什么很多人学协程CoroutineScope一直背协程作用域然后结束。实际上Scope Job树的根节点例如val scope CoroutineScope( SupervisorJob() Dispatchers.Main )结构CoroutineScope │ └── SupervisorJob后面所有scope.launch { }都会挂到这棵树下面。四、为什么 GlobalScope 被疯狂吐槽很多人刚学协程GlobalScope.launch { }觉得很方便。但问题来了。结构GlobalScope │ └── launch没有真正父节点。例如Activity关闭协程继续运行例如GlobalScope.launch { delay(10000) updateUI() }10秒后Activity早没了直接崩溃或者内存泄漏所以Google一直不推荐GlobalScope原因就在这里。五、为什么 ViewModelScope 这么好用例如viewModelScope.launch { }内部SupervisorJob() Dispatchers.Main结构ViewModel │ └── ViewModelScope │ ├── 协程A │ ├── 协程B │ └── 协程C当onCleared()执行。内部scope.cancel()结果整个Job树取消所有协程自动结束这就是生命周期感知的本质。六、LifecycleScope 又是什么例如lifecycleScope.launch { }结构Activity │ └── LifecycleScope当onDestroy()执行。内部取消整个Scope于是所有子协程结束和ViewModelScope本质一样。七、为什么 repeatOnLifecycle 这么安全你最近正好在用repeatOnLifecycle( Lifecycle.State.STARTED )很多人只会背推荐使用不知道为什么。实际上repeatOnLifecycle { }每次STARTED创建新的子Job每次STOPPED取消这个子Job结构LifecycleScope │ └── Repeat Job │ ├── collectA │ ├── collectB │ └── collectC所以页面不可见 ↓ 自动取消收集页面回来重新创建Job这就是Flow生命周期安全的根源。八、之前为什么要把5个 collect 合并前面优化过lifecycleScope.launch { repeatOnLifecycle( Lifecycle.State.STARTED ) { launch { collectA() } launch { collectB() } launch { collectC() } launch { collectD() } launch { collectE() } } }当时的理解代码更整洁其实更重要的是形成结构化并发结构LifecycleScope │ └── Repeat Job │ ├── A ├── B ├── C ├── D └── E生命周期结束整棵树取消不用一个一个取消九、为什么说 Scope 才是协程世界的边界其实CoroutineContext 解决运行环境 Job 解决生命周期 Dispatcher 解决调度而Scope 解决归属问题例如这个协程属于谁答案属于Scope例如页面关闭怎么办答案取消Scope例如ViewModel销毁怎么办答案取消Scope所以Scope 才是协程生命周期边界。十、终于理解 Google 的设计哲学现在回头看Java线程特点想开就开而Kotlin协程特点必须有Scope因为Google不希望出现孤儿协程希望每个协程 都有归属这就是Structured Concurrency的核心思想。十一、最终总结如果让我一句话解释结构化并发我会这样说所有协程都必须挂在一棵Job树上。如果让我解释Scope我会说Scope就是这棵树的根节点。如果让我解释GlobalScope我会说没有归属的野孩子。如果让我解释ViewModelScope我会说跟随ViewModel生命周期的Job树。如果让我解释LifecycleScope我会说跟随Activity/Fragment生命周期的Job树。真正理解结构化并发以后会发现协程最牛逼的地方 从来不是轻量级线程。而是生命周期可控。下篇预告到这里CoroutineContext ✓ Job ✓ Dispatcher ✓ launch / async / withContext ✓ Exception ✓ Structured Concurrency ✓整个协程骨架已经出来了。那么最后一个经常让人迷糊的问题来了SupervisorJob supervisorScope 到底有什么区别 为什么一个是Context里的配置项 一个是挂起函数 它们到底应该怎么选下一篇我们继续《Kotlin 协程设计思想七SupervisorJob 与 supervisorScope 到底有什么区别》从 Job 树、异常传播到作用域隔离彻底讲透 Kotlin 协程中的“异常隔离设计”。