故事背景:
"协程奶茶连锁店" 生意火爆,总部为了高效管理,设立了 4 个核心部门,每个部门负责不同类型的任务,且有严格的 "上下班时间"(生命周期):
- 总公司长期项目组(GlobalScope) :负责跨门店的长期任务(如年度供应链优化),除非主动停掉,否则一直运行。
- 前台接待组(MainScope) :负责门店前台的即时服务(如点单、结账),必须在前台营业时间内工作,打烊时全部停手。
- 后厨备餐组(ViewModelScope) :配合前台备餐,前台换班(ViewModel 销毁)时,未完成的备餐任务必须立刻停掉。
- 临时促销组(LifecycleScope) :负责门店外的临时促销,摊位撤掉(Activity/Fragment 销毁)时,促销活动马上终止。
这些部门本质上都是CoroutineScope ,但因 "职责" 不同,它们的coroutineContext
(核心配置)和 "生命周期触发条件" 不同。下面逐个拆解:
一、总公司长期项目组:GlobalScope
部门特点:
- 任务周期长(可能跨多天),不受单个门店生命周期影响。
- 没有 "自动下班" 机制,必须手动终止,否则会一直运行(可能 "加班摸鱼" 导致资源浪费)。
代码里的 GlobalScope:
kotlin
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("门店关门了,但总公司任务还在跑...")
}
运行结果:
text
第1天:优化供应链中...
第2天:优化供应链中...
第3天:优化供应链中...
门店关门了,但总公司任务还在跑...
第4天:优化供应链中...
// 即使主线程结束,任务仍在后台运行(直到JVM退出)
实现原理:
GlobalScope
的coroutineContext
是固定的:
kotlin
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:
kotlin
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() // 手动取消
}
运行结果:
text
开始点单(线程:main) // 确保在主线程执行
前台打烊!停止所有接待任务
// 后续"结账"不会执行,因为被取消了
实现原理:
MainScope
的coroutineContext
是:
kotlin
fun MainScope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
- Dispatchers.Main:强制在主线程执行(Android 中对应 UI 线程,确保能更新 UI)。
- SupervisorJob() :子任务失败不影响其他任务(比如一个顾客点单失败,不影响另一个顾客结账)。
- 手动取消 :必须通过
scope.cancel()
触发(比如 Android 中在onDestroy
调用)。
三、后厨备餐组:ViewModelScope
部门特点:
- 专门配合前台备餐,前台换班(ViewModel 被销毁)时,自动停止所有备餐任务(避免 "给已走的顾客做奶茶")。
代码里的 ViewModelScope(Android 场景):
kotlin
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
为:
kotlin
val ViewModel.viewModelScope: CoroutineScope
get() = CoroutineScope(ViewModelCoroutineScope.DispatcherProvider.main + job)
- Dispatcher.Main:默认在主线程(也可指定其他线程)。
- 内部 Job :和 ViewModel 绑定,当 ViewModel 的
onCleared()
被调用时,这个 Job 会自动cancel()
,所有子协程随之终止。
四、临时促销组:LifecycleScope
部门特点:
- 在门店外的临时摊位工作,摊位撤掉(Activity/Fragment 销毁)时,自动停止所有促销活动。
代码里的 LifecycleScope(Android 场景):
kotlin
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("摊位撤掉,促销停止")
}
}
实现原理:
lifecycleScope
是LifecycleOwner
(如 Activity/Fragment)的扩展属性,其coroutineContext
为:
kotlin
val LifecycleOwner.lifecycleScope: CoroutineScope
get() = lifecycle.coroutineScope
- 内部关联
Lifecycle
,当生命周期走到ON_DESTROY
时,自动调用cancel()
。 - 默认使用
Dispatchers.Main
,确保能更新 UI(如更新促销进度)。
五、各 Scope 的生命周期时序图
1. GlobalScope 时序图(无自动取消)
text
┌───────────┐ ┌─────────────┐ ┌───────────┐
│ 用户代码 │ │ GlobalScope │ │ 子协程 │
└─────┬─────┘ └───────┬─────┘ └─────┬─────┘
│ │ │
│ launch协程 │ │
├───────────────────>│ │
│ │ 启动子协程 │
│ ├─────────────────>│
│ │ │ 执行中...
│ │ │<─────┐
│ (无自动取消) │ │ │
│ │ │ │
│ (需手动cancel) │ │ │
│ (否则一直运行) │ │ │
2. ViewModelScope 时序图(ViewModel 销毁时取消)
text
┌─────────────┐ ┌────────────────┐ ┌───────────┐
│ ViewModel │ │ ViewModelScope │ │ 子协程 │
└───────┬─────┘ └───────┬────────┘ └─────┬─────┘
│ │ │
│ 初始化scope │ │
├───────────────────>│ │
│ │ │
│ launch协程 │ │
├───────────────────>│ │
│ │ 启动子协程 │
│ ├────────────────────>│
│ │ │ 执行中...
│ │ │
│ onCleared()触发 │ │
├───────────────────>│ │
│ │ 调用Job.cancel() │
│ ├────────────────────>│
│ │ │ 终止执行
│ │ │<─────┐
3. LifecycleScope 时序图(ON_DESTROY 时取消)
text
┌─────────────┐ ┌────────────────┐ ┌───────────┐
│ Activity │ │ LifecycleScope │ │ 子协程 │
└───────┬─────┘ └───────┬────────┘ └─────┘─────┘
│ │ │
│ 初始化scope │ │
├───────────────────>│ │
│ │ │
│ launch协程 │ │
├───────────────────>│ │
│ │ 启动子协程 │
│ ├────────────────────>│
│ │ │ 执行中...
│ │ │
│ 生命周期到ON_DESTROY│ │
├───────────────────>│ │
│ │ 调用Job.cancel() │
│ ├────────────────────>│
│ │ │ 终止执行
│ │ │<─────┐
总结:选对 Scope,就像找对部门
Scope 类型 | 生命周期绑定 | 核心配置(coroutineContext) | 适用场景 |
---|---|---|---|
GlobalScope | 无(需手动取消) | Dispatchers.Default + SupervisorJob | 跨组件的长期任务(谨慎使用) |
MainScope | 手动控制 | Dispatchers.Main + SupervisorJob | 顶层 UI 组件(如 Application) |
ViewModelScope | ViewModel.onCleared | Dispatchers.Main + 内部 Job | 配合 ViewModel 的备餐 / 数据请求 |
LifecycleScope | Activity/Fragment 销毁 | Dispatchers.Main + Lifecycle 关联 Job | 临时 UI 任务(如促销、弹窗倒计时) |
记住:Scope 的本质是给协程找个 "归属",让协程在合适的时间开始,在该结束的时候乖乖结束,避免 "野协程" 捣乱~