源码解读笔记:协程的 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 相关操作,如更新界面、处理用户交互等
这行代码: ```kotlin val viewModel = ViewModelProvider(this).get(MainSaveViewModel::class.java) ``` 的作用是**获取一个与当前组件(如 Activity)生命周期绑定的 ViewModel 实例**。 我们来逐部分详细解析这行代码的含义执行流程。 --- ### 🔍 代码结构拆解 ```kotlin ViewModelProvider(this).get(MainSaveViewModel::class.java) ``` #### 1. `this`(即 `ViewModelProvider` 的构造参数) - `this` 是指当前的 `Activity`(或 `Fragment`)。 - 它实现了 `ViewModelStoreOwner` 接口,表示这个组件拥有一个 `ViewModelStore`,用于存储 `ViewModel` 实例。 - 每个 `Activity` 或 `Fragment` 都有唯一的 `ViewModelStore`,用于管理 `ViewModel` 的生命周期。 #### 2. `ViewModelProvider(this)` - 创建一个 `ViewModelProvider` 实例。 - 它的作用是:**根据给定的 `ViewModelStoreOwner`,提供对应的 `ViewModel` 实例。** - 内部会从 `this.viewModelStore` 获取或创建 `ViewModel` 实例。 #### 3. `.get(MainSaveViewModel::class.java)` - 调用 `get()` 方法,传入 `ViewModel` 的类类型。 - `ViewModelProvider` 会检查当前 `ViewModelStore` 中是否已经存在该类型的 `ViewModel`: - 如果存在,直接返回已有的实例。 - 如果不存在,就通过反射创建一个新的实例。 --- ### ⚠️ 存在的问题(关键点) 你的 `MainSaveViewModel` 定义如下: ```kotlin class MainSaveViewModel(private val savedState: SavedStateHandle) : ViewModel() ``` 它的构造函数需要一个 `SavedStateHandle` 参数,但你使用的是: ```kotlin ViewModelProvider(this).get(MainSaveViewModel::class.java) ``` 这种方式默认使用**无参构造函数**来创建 `ViewModel`,而你的 `MainSaveViewModel` 没有无参构造函数,会导致运行时抛出异常: ``` java.lang.InstantiationException: java.lang.NoSuchMethodException: <init> [] ``` --- ### ✅ 正确写法 你应该使用带有 `Factory` 的 `ViewModelProvider` 来创建带有 `SavedStateHandle` 的 `ViewModel`: ```kotlin val viewModel = ViewModelProvider(this, defaultViewModelProviderFactory) .get(MainSaveViewModel::class.java) ``` #### 🔍 `defaultViewModelProviderFactory` 是什么? - 它是 `ComponentActivity` 提供的一个默认的 `ViewModelProvider.Factory`。 - 它能自动注入 `SavedStateHandle`,支持构造函数带参数的 `ViewModel`。 - 适用于大多数标准场景。 --- ### 🧠 工作流程图解 ``` ViewModelProvider(this, factory).get(MainSaveViewModel::class.java) │ └──→ 检查 ViewModelStore 是否已有该 ViewModel ├── 是 → 返回已有实例 └── 否 → 使用 factory 创建新实例 └── factory 会注入 SavedStateHandle ``` --- ### ✅ 总结 | 写法 | 是否推荐 | 说明 | |------|----------|------| | `ViewModelProvider(this).get(VM::class.java)` | ❌ 不推荐 | 默认使用无参构造函数,无法注入 SavedStateHandle | | `ViewModelProvider(this, factory).get(VM::class.java)` | ✅ 推荐 | 使用 factory 可支持带 SavedStateHandle 的 ViewModel | | `by viewModels()` | ✅ 推荐 | Kotlin 扩展函数,自动使用 factory 创建 ViewModel | --- ### ✅ 附加说明 如果你使用的是 `androidx.activity:activity-ktx`,你还可以简化为: ```kotlin val viewModel: MainSaveViewModel by viewModels() ``` 它内部已经自动使用了 `defaultViewModelProviderFactory`,是最简洁的写法。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值