为什么生成 LiveData 或 Flow 的函数不必从 CoroutineScope 调用?

Why functions that produce LiveData or Flow, don't have to be called from CoroutineScope?

我们平时用Room的时候,都是用Kotlin Coroutine,做一个DAO去访问Room,去获取结果。大多数函数通常在函数开头有 suspend 修饰符,但 LiveDataFlow。 例如,让我们看一下下面的这两个代码。

@Query("SELECT * FROM MockTable")
suspend fun allMockDataWithSuspend(): List<MockData>

@Query("SELECT * FROM MockTable")
fun allMockData(): Flow<List<MockData>> // or LiveData<List<MockData>>

当我们使用suspend修饰符时,我们需要在协程范围内调用该函数,因为该函数具有suspend修饰符。但是当函数的结果是 LiveDataFlow 时,我们不需要在协程中调用函数,即使它是 I/O 访问。

这怎么可能?

没有什么需要在协程上下文中创建 Flow 对象。这只是创建最终异步生成数据的对象的问题。

Flow collection(异步获取实际的 Flow 结果)完全是另一回事。

你读过Flow documentation了吗?它在那里进行了相当详细的解释。

基本上(据我了解,我自己使用它们的时间并不长)您的暂停函数返回 List,即一次性返回所有结果。如果生成该结果列表可能需要一些时间,您可以添加 suspend 关键字来表示。然后通过调用该函数在协程中异步获取列表。

Flows 是不同的——它们的重点是在任意时间提供结果,并且可能永远不会停止!数据项在它们发出时被传送,而不是在一个集合中一次传送。

因此,当您创建 Flow 时,您实际上还没有做任何工作。这就是为什么您的函数不是 suspend 函数,它只是创建对象。要实际获取这些项目,您需要对其调用 collect,并且 that 需要在协程内部发生,因为这是异步内容实际发生的地方。

FlowLiveData的Dao函数没有挂起,可以从任何地方调用。

至于尽管它的 IO 访问是如何工作的?

您需要了解 async 编程的工作原理,async 编程背后的基本思想是 callback。因此,您无需等待 IO 调用完成,而是注册一个回调,该回调将在响应准备就绪时调用。

LiveDataFlow 的情况下,您明确指定回调 。函数调用不会阻塞(不需要线程卸载),它只是请求一些数据并注册一个回调,当数据准备好时应该调用回调

dao.allMockData().collect { data ->
     // This is the callback
}

dao.allMockData().observe(lifeCycleOwner, Observer {
    // This is the callback
}

suspend 函数的情况下,callback 是隐式的 ,kotlin 负责创建它并致电

coroutineScope.launch{
     val data = allMockDataWithSuspend()
}

此函数调用将 suspend 并且一旦数据库中的数据可用,将调用回调(隐式)并将响应存储在 data 字段中。 kotlin 协程实现使其看起来像一个普通的旧顺序函数调用。

一个异步挂起函数return一个单一的值,一个流return多个异步计算的值。

流是冷的,没有活动的收集器就不会发出数据(流生成器中的代码在收集之前不会 运行)。这是 return 流的函数不是 suspend 函数的一个关键原因,因为在调用任何终端运算符之前什么都不会发生。因此,在创建 Flow 时,没有人收集它,因此没有完成任何工作。 尝试使用流或 flowOf 函数创建 flow,您会发现它们也不是 suspend 函数。他们 return 很快就没有等待任何事情。中介操作符也不会触发流收集,因此不会暂停功能,因为它们通常将上游流转换为新流,它们与流本身一样冷。另一方面,终端运算符在开始收集流时是挂起函数。

当谈到 LiveData 时,您有一个始终为 null 的初始值,Room 确保那些 returning LiveData 的查询是 运行在后台线程上,无需您手动执行。

LiveData 和 Kotlin 的 Flows 基本上是一种 observables,这意味着某些事情会在某个时间点发生,这些 observables 会通知它。

所以当我们从可观察性的角度考虑时,从用户的角度来看,我们只需要保持一个盒子准备好并打开以在它到达时捕获可观察物,直到它到来让盒子关闭(暂停状态)

Android 观察方式是通过 LiveData

lveViewmodel.liveData.observe(this, {

  })

Kotlin 的观察方式是通过 Flow 完成的(Flow 的 use-case 之一就像一个 observable)

lveViewmodel.stateFlow.collect {
               //suspendable block
            }

而CoroutineScope不一样,它就像是对某些事物的分组,这里用kotlin来说我们可以说是对协程进行分组和控制。