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

故事背景:

"协程奶茶连锁店" 生意火爆,总部为了高效管理,设立了 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退出)

实现原理:

GlobalScopecoroutineContext是固定的:

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) // 确保在主线程执行
前台打烊!停止所有接待任务
// 后续"结账"不会执行,因为被取消了

实现原理:

MainScopecoroutineContext是:

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("摊位撤掉,促销停止")
    }
}

实现原理:

lifecycleScopeLifecycleOwner(如 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 的本质是给协程找个 "归属",让协程在合适的时间开始,在该结束的时候乖乖结束,避免 "野协程" 捣乱~

相关推荐
用户2018792831672 小时前
CoroutineDispatcher的"自由精灵" - Dispatchers.Unconfined
android
黄额很兰寿3 小时前
深入源码理解LiveData的实现原理
android
黄额很兰寿3 小时前
flow 的冷流和热流 是设么有什么区别?
android
Digitally3 小时前
如何将 Android 联系人备份到 Mac 的 4 种简单
android·macos
2501_915918414 小时前
iOS 混淆与 IPA 加固一页式行动手册(多工具组合实战 源码成品运维闭环)
android·运维·ios·小程序·uni-app·iphone·webview
不吃凉粉12 小时前
Android Studio USB串口通信
android·ide·android studio
zhangphil12 小时前
android studio设置大内存,提升编译速度
android·android studio
编程乐学13 小时前
安卓非原创--基于Android Studio 实现的天气预报App
android·ide·android studio·课程设计·大作业·天气预报·安卓大作业
大熊的瓜地14 小时前
Android automotive 框架
android·android car