用 “奶茶连锁店的部门分工” 理解各种 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 的本质是给协程找个 "归属",让协程在合适的时间开始,在该结束的时候乖乖结束,避免 "野协程" 捣乱~

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui