当前位置: 首页 > article >正文

源码解读笔记:协程的 ViewModel.viewModelScope和LifecycleOwner.lifecycleScope

分析下ViewModel.viewModelScope

public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        coroutineContext.cancel()
    }
}

1. ViewModel.viewModelScope:

这是一个公共的只读属性,用于获取或创建与 ViewModel 关联的 CoroutineScope。
get 函数用于计算属性值。它首先尝试从 ViewModel 的 tag(存储在 this.getTag(JOB_KEY) 中)获取已存在的 CoroutineScope。
如果找到了已存在的 CoroutineScope(scope != null),则直接返回它。
如果没有找到,它将创建一个新的 CloseableCoroutineScope 并将其设置为 ViewModel 的 tag,同时返回新创建的 CoroutineScope。
创建的 CoroutineScope 由一个 SupervisorJob 和 Dispatchers.Main.immediate 组成,这表示协程将在主线程上运行,并且具有超时监督策略。

2. CloseableCoroutineScope:

这是一个内部类,实现了 Closeable 和 CoroutineScope 接口。
实现了 CoroutineScope.coroutineContext 属性,它返回给定的 CoroutineContext 参数,通常是一个组合了 Job 和 Dispatcher 的上下文。
实现了 Closeable.close() 方法,当调用 close() 时,它会取消关联的 CoroutineContext,这通常意味着取消所有正在运行的协程。
总结一下,这段代码的主要目的是为 ViewModel 提供一个可管理的协程作用域,这个作用域与 ViewModel 的生命周期绑定,并且可以在需要时安全地取消所有协程。
CloseableCoroutineScope 确保了协程的生命周期管理,当不再需要时可以被关闭,从而避免资源泄漏。Dispatchers.Main.immediate 确保所有在此作用域内启动的协程都在主线程上执行,这对于更新 UI 是必要的。

3. 其中的SupervisorJob()

SupervisorJob 是 Kotlin 协程库中的一个类,它是 Job 的一个子类,主要用于管理协程的生命周期。
SupervisorJob 的主要特点是它采用了所谓的“非传播”异常策略,这意味着如果在其子协程中发生异常,SupervisorJob 不会因为这些异常而自动取消自身或其他子协程。
这种设计是为了防止一个子任务的错误导致整个工作树的崩溃,使得其他任务有机会完成或者独立处理错误。
使用 SupervisorJob 的场景通常包括:

  • 错误隔离:如果你希望一个子任务的失败不会影响其他子任务,可以使用 SupervisorJob 创建这些子任务的父级作用域。
  • 独立性:在多个任务之间需要保持独立性,即使其中一个失败,其他任务也应该继续运行。
  • 资源清理:在某些情况下,你可能希望即使有子任务失败,仍然能够执行清理操作,如关闭文件流或网络连接。
    在上述代码中,SupervisorJob() 被用来创建一个 CloseableCoroutineScope 的上下文,这意味着在这个作用域内启动的所有协程都将受到 SupervisorJob 的管理,它们会在主线程上运行,并且即使其中一个协程由于异常而终止,其他协程仍将继续执行。
    这对于 ViewModel 来说是一个很好的选择,因为它允许 ViewModel 中的不同任务独立处理错误,而不是整体崩溃。


分析下LifecycleOwner.lifecycleScope:

public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

定义了一个扩展属性 coroutineScope,适用于 Lifecycle 类型的对象。这个属性返回一个 LifecycleCoroutineScope 对象。

public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (mInternalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }
  • 使用无限循环来确保在多线程环境下正确获取或创建 LifecycleCoroutineScope。
  • 检查现有作用域:从 mInternalScopeRef 中获取现有的 LifecycleCoroutineScopeImpl 实例。 如果存在现有实例,则直接返回该实例。
  • 否则,创建新作用域:创建一个新的 LifecycleCoroutineScopeImpl 实例。
    • this 表示当前的 Lifecycle 对象。
    • SupervisorJob() 创建一个监督者作业,允许子作业独立于父作业失败。
    • Dispatchers.Main.immediate 确保协程在主线程上立即执行
  • 原子性设置:使用 compareAndSet 方法尝试原子地将 mInternalScopeRef 设置为新的 newScope。
    • 如果设置成功,则调用 newScope.register() 注册新作用域,并返回新作用域。
    • 如果设置失败(即已经有其他线程设置了 mInternalScopeRef),则继续循环,重新检查现有作用域。

总结

  • 目的:这个属性 coroutineScope 提供了一个与 Lifecycle 绑定的协程作用域,确保在生命周期管理下安全地执行协程。
  • 线程安全:通过无限循环和 compareAndSet 方法,确保在多线程环境下正确创建和获取 LifecycleCoroutineScope。
  • 作用域配置:使用 SupervisorJob 和 Dispatchers.Main.immediate 配置协程作用域,确保协程在主线程上立即执行,并且子协程可以独立于父协程失败。
    使用场景
  • 生命周期绑定:在 Fragment 或 Activity 中使用 lifecycleScope 可以确保协程在组件的生命周期内安全地执行,避免内存泄漏和资源浪费。
  • 主线程操作:适用于需要在主线程上执行的 UI 相关操作,如更新界面、处理用户交互等

http://www.kler.cn/a/411552.html

相关文章:

  • 验证视图状态 MAC 失败,配置machineKey
  • 快速理解微服务中Gateway的概念
  • AI智能体崛起:从“工具”到“助手”的进化之路
  • 【CSP CCF记录】201809-2第14次认证 买菜
  • JS听到了替罪的回响
  • centos安装小火车
  • 【MCU】微控制器的编程技术:ISP 与 IAP
  • VTS:基于Apache SeaTunnel的开源向量数据迁移工具
  • 鸿蒙学习自由流转与分布式运行环境-跨端迁移(2)
  • C++ STL - vector/list讲解及迭代器失效
  • 数据结构——小小二叉树第三幕(链式结构的小拓展,二叉树的创建,深入理解二叉树的遍历)超详细!!!
  • Vue进阶面试题目(四)
  • 【设计模式】【创建型模式(Creational Patterns)】之原型模式(Prototype Pattern)
  • 25A物联网微型断路器 智慧空开1P 2P 3P 4P-安科瑞黄安南
  • C# 泛型 学习理解记录
  • vue3+ts 我写了一个跟swagger.yml生成请求和响应实体(接口)
  • 电商平台数据获取:解锁商业洞察的多元渠道
  • #Verilog HDL# Verilog中的UDP原语
  • 2024算法基础公选课练习五(DFS2)
  • 前端---CSS(部分用法)
  • C++的中的继承
  • 计算机操作系统——进程控制(Linux)
  • 第八篇:CamX RawHdr Feature Enable
  • org.apache.log4j的日志记录级别和基础使用Demo
  • 【kafka01】消息队列与微服务之Kafka详解
  • 数据库(总结自小林coding)|索引失效的场景、慢查询、原因及如何优化?undo log、redo log、binlog 作用、MySQL和Redis的区别