注册

用 “奶茶连锁店的部门分工” 理解各种 CoroutineScope

故事背景:


“协程奶茶连锁店” 生意火爆,总部为了高效管理,设立了 4 个核心部门,每个部门负责不同类型的任务,且有严格的 “上下班时间”(生命周期):



  • 总公司长期项目组(GlobalScope) :负责跨门店的长期任务(如年度供应链优化),除非主动停掉,否则一直运行。
  • 前台接待组(MainScope) :负责门店前台的即时服务(如点单、结账),必须在前台营业时间内工作,打烊时全部停手。
  • 后厨备餐组(ViewModelScope) :配合前台备餐,前台换班(ViewModel 销毁)时,未完成的备餐任务必须立刻停掉。
  • 临时促销组(LifecycleScope) :负责门店外的临时促销,摊位撤掉(Activity/Fragment 销毁)时,促销活动马上终止。

这些部门本质上都是CoroutineScope,但因 “职责” 不同,它们的coroutineContext(核心配置)和 “生命周期触发条件” 不同。下面逐个拆解:


一、总公司长期项目组:GlobalScope


部门特点:



  • 任务周期长(可能跨多天),不受单个门店生命周期影响。
  • 没有 “自动下班” 机制,必须手动终止,否则会一直运行(可能 “加班摸鱼” 导致资源浪费)。

代码里的 GlobalScope:


import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

fun main() = runBlocking {
// 启动总公司的长期任务(优化供应链)
GlobalScope.launch {
repeat(10) { i -> // 模拟10天的任务
println("第${i+1}天:优化供应链中...")
delay(1000) // 每天做一点
}
}

// 模拟门店只观察3天就关门
delay(3500)
println("门店关门了,但总公司任务还在跑...")
}

运行结果


第1天:优化供应链中...
第2天:优化供应链中...
第3天:优化供应链中...
门店关门了,但总公司任务还在跑...
第4天:优化供应链中...
// 即使主线程结束,任务仍在后台运行(直到JVM退出)

实现原理:


GlobalScopecoroutineContext是固定的:


object GlobalScope : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Default + SupervisorJob()
}


  • Dispatcher.Default:默认在后台线程池执行(适合计算密集型任务)。
  • SupervisorJob() :特殊的 “组长”,它的子任务失败不会影响其他子任务(比如某个门店的供应链优化失败,不影响其他门店),且没有父 Job(所以不会被其他 Scope 取消)。

为什么少用?


就像总公司的长期任务如果不手动停,会一直占用人力物力,GlobalScope启动的协程如果忘记取消,会导致内存泄漏(比如 Android 中 Activity 销毁后,协程还在访问已销毁的 View)。


二、前台接待组:MainScope


部门特点:



  • 只在前台营业时间工作(比如门店 9:00-21:00),负责和顾客直接交互(必须在主线程执行)。
  • 打烊时(手动调用cancel()),所有接待任务立刻停止。

代码里的 MainScope:


import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
// 创建前台接待组(指定在主线程工作)
val frontDeskScope = MainScope()

// 启动接待任务:点单、结账
frontDeskScope.launch {
println("开始点单(线程:${Thread.currentThread().name})")
delay(1000) // 模拟点单耗时
println("点单完成,开始结账")
}

// 模拟21:00打烊
delay(500)
println("前台打烊!停止所有接待任务")
frontDeskScope.cancel() // 手动取消
}

运行结果


开始点单(线程:main) // 确保在主线程执行
前台打烊!停止所有接待任务
// 后续“结账”不会执行,因为被取消了

实现原理:


MainScopecoroutineContext是:


fun MainScope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)


  • Dispatchers.Main:强制在主线程执行(Android 中对应 UI 线程,确保能更新 UI)。
  • SupervisorJob() :子任务失败不影响其他任务(比如一个顾客点单失败,不影响另一个顾客结账)。
  • 手动取消:必须通过scope.cancel()触发(比如 Android 中在onDestroy调用)。

三、后厨备餐组:ViewModelScope


部门特点:



  • 专门配合前台备餐,前台换班(ViewModel 被销毁)时,自动停止所有备餐任务(避免 “给已走的顾客做奶茶”)。

代码里的 ViewModelScope(Android 场景):


import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class TeaViewModel : ViewModel() {
fun startPrepareTea() {
// 后厨备餐任务(自动绑定ViewModel生命周期)
viewModelScope.launch {
println("开始备餐:煮珍珠...")
delay(2000) // 模拟备餐耗时
println("备餐完成!")
}
}

// ViewModel销毁时(如Activity.finish),系统自动调用
override fun onCleared() {
super.onCleared()
println("前台换班,后厨停止备餐")
}
}

实现原理:


viewModelScope是 ViewModel 的扩展属性,其coroutineContext为:


val ViewModel.viewModelScope: CoroutineScope
get() = CoroutineScope(ViewModelCoroutineScope.DispatcherProvider.main + job)


  • Dispatcher.Main:默认在主线程(也可指定其他线程)。
  • 内部 Job:和 ViewModel 绑定,当 ViewModel 的onCleared()被调用时,这个 Job 会自动cancel(),所有子协程随之终止。

四、临时促销组:LifecycleScope


部门特点:



  • 在门店外的临时摊位工作,摊位撤掉(Activity/Fragment 销毁)时,自动停止所有促销活动。

代码里的 LifecycleScope(Android 场景):


import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class PromotionFragment : Fragment() {
override fun onStart() {
super.onStart()
// 临时促销任务(自动绑定Fragment生命周期)
lifecycleScope.launch {
println("开始促销:发传单...")
delay(3000) // 模拟促销耗时
println("促销结束!")
}
}

// Fragment销毁时,系统自动触发
override fun onDestroy() {
super.onDestroy()
println("摊位撤掉,促销停止")
}
}

实现原理:


lifecycleScopeLifecycleOwner(如 Activity/Fragment)的扩展属性,其coroutineContext为:


val LifecycleOwner.lifecycleScope: CoroutineScope
get() = lifecycle.coroutineScope


  • 内部关联Lifecycle,当生命周期走到ON_DESTROY时,自动调用cancel()
  • 默认使用Dispatchers.Main,确保能更新 UI(如更新促销进度)。

五、各 Scope 的生命周期时序图


1. GlobalScope 时序图(无自动取消)


┌───────────┐      ┌─────────────┐      ┌───────────┐
│ 用户代码 │ │ GlobalScope │ │ 子协程 │
└─────┬─────┘ └───────┬─────┘ └─────┬─────┘
│ │ │
│ launch协程 │ │
├───────────────────>│ │
│ │ 启动子协程 │
│ ├─────────────────>│
│ │ │ 执行中...
│ │ │<─────┐
│ (无自动取消) │ │ │
│ │ │ │
│ (需手动cancel) │ │ │
│ (否则一直运行) │ │ │

2. ViewModelScope 时序图(ViewModel 销毁时取消)


┌─────────────┐      ┌────────────────┐      ┌───────────┐
│ ViewModel │ │ ViewModelScope │ │ 子协程 │
└───────┬─────┘ └───────┬────────┘ └─────┬─────┘
│ │ │
│ 初始化scope │ │
├───────────────────>│ │
│ │ │
│ launch协程 │ │
├───────────────────>│ │
│ │ 启动子协程 │
│ ├────────────────────>│
│ │ │ 执行中...
│ │ │
│ onCleared()触发 │ │
├───────────────────>│ │
│ │ 调用Job.cancel() │
│ ├────────────────────>│
│ │ │ 终止执行
│ │ │<─────┐

3. LifecycleScope 时序图(ON_DESTROY 时取消)


┌─────────────┐      ┌────────────────┐      ┌───────────┐
│ Activity │ │ LifecycleScope │ │ 子协程 │
└───────┬─────┘ └───────┬────────┘ └─────┘─────┘
│ │ │
│ 初始化scope │ │
├───────────────────>│ │
│ │ │
│ launch协程 │ │
├───────────────────>│ │
│ │ 启动子协程 │
│ ├────────────────────>│
│ │ │ 执行中...
│ │ │
│ 生命周期到ON_DESTROY│ │
├───────────────────>│ │
│ │ 调用Job.cancel() │
│ ├────────────────────>│
│ │ │ 终止执行
│ │ │<─────┐

总结:选对 Scope,就像找对部门


Scope 类型生命周期绑定核心配置(coroutineContext)适用场景
GlobalScope无(需手动取消)Dispatchers.Default + SupervisorJob跨组件的长期任务(谨慎使用)
MainScope手动控制Dispatchers.Main + SupervisorJob顶层 UI 组件(如 Application)
ViewModelScopeViewModel.onClearedDispatchers.Main + 内部 Job配合 ViewModel 的备餐 / 数据请求
LifecycleScopeActivity/Fragment 销毁Dispatchers.Main + Lifecycle 关联 Job临时 UI 任务(如促销、弹窗倒计时)

记住:Scope 的本质是给协程找个 “归属”,让协程在合适的时间开始,在该结束的时候乖乖结束,避免 “野协程” 捣乱~


作者:Android童话镇
来源:juejin.cn/post/7563197374418157604

0 个评论

要回复文章请先登录注册