为什么生成 LiveData 或 Flow 的函数不必从 CoroutineScope 调用?
Why functions that produce LiveData or Flow, don't have to be called from CoroutineScope?
我们平时用Room的时候,都是用Kotlin Coroutine,做一个DAO去访问Room,去获取结果。大多数函数通常在函数开头有 suspend
修饰符,但 LiveData
和 Flow
。
例如,让我们看一下下面的这两个代码。
@Query("SELECT * FROM MockTable")
suspend fun allMockDataWithSuspend(): List<MockData>
@Query("SELECT * FROM MockTable")
fun allMockData(): Flow<List<MockData>> // or LiveData<List<MockData>>
当我们使用suspend
修饰符时,我们需要在协程范围内调用该函数,因为该函数具有suspend修饰符。但是当函数的结果是 LiveData
或 Flow
时,我们不需要在协程中调用函数,即使它是 I/O 访问。
这怎么可能?
没有什么需要在协程上下文中创建 Flow 对象。这只是创建最终异步生成数据的对象的问题。
Flow collection(异步获取实际的 Flow 结果)完全是另一回事。
你读过Flow documentation了吗?它在那里进行了相当详细的解释。
基本上(据我了解,我自己使用它们的时间并不长)您的暂停函数返回 List
,即一次性返回所有结果。如果生成该结果列表可能需要一些时间,您可以添加 suspend
关键字来表示。然后通过调用该函数在协程中异步获取列表。
Flow
s 是不同的——它们的重点是在任意时间提供结果,并且可能永远不会停止!数据项在它们发出时被传送,而不是在一个集合中一次传送。
因此,当您创建 Flow
时,您实际上还没有做任何工作。这就是为什么您的函数不是 suspend
函数,它只是创建对象。要实际获取这些项目,您需要对其调用 collect
,并且 that 需要在协程内部发生,因为这是异步内容实际发生的地方。
带Flow
和LiveData
的Dao函数没有挂起,可以从任何地方调用。
至于尽管它的 IO 访问是如何工作的?
您需要了解 async
编程的工作原理,async
编程背后的基本思想是 callback
。因此,您无需等待 IO 调用完成,而是注册一个回调,该回调将在响应准备就绪时调用。
在 LiveData
或 Flow
的情况下,您明确指定回调 。函数调用不会阻塞(不需要线程卸载),它只是请求一些数据并注册一个回调,当数据准备好时应该调用回调
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来说我们可以说是对协程进行分组和控制。
我们平时用Room的时候,都是用Kotlin Coroutine,做一个DAO去访问Room,去获取结果。大多数函数通常在函数开头有 suspend
修饰符,但 LiveData
和 Flow
。
例如,让我们看一下下面的这两个代码。
@Query("SELECT * FROM MockTable")
suspend fun allMockDataWithSuspend(): List<MockData>
@Query("SELECT * FROM MockTable")
fun allMockData(): Flow<List<MockData>> // or LiveData<List<MockData>>
当我们使用suspend
修饰符时,我们需要在协程范围内调用该函数,因为该函数具有suspend修饰符。但是当函数的结果是 LiveData
或 Flow
时,我们不需要在协程中调用函数,即使它是 I/O 访问。
这怎么可能?
没有什么需要在协程上下文中创建 Flow 对象。这只是创建最终异步生成数据的对象的问题。
Flow collection(异步获取实际的 Flow 结果)完全是另一回事。
你读过Flow documentation了吗?它在那里进行了相当详细的解释。
基本上(据我了解,我自己使用它们的时间并不长)您的暂停函数返回 List
,即一次性返回所有结果。如果生成该结果列表可能需要一些时间,您可以添加 suspend
关键字来表示。然后通过调用该函数在协程中异步获取列表。
Flow
s 是不同的——它们的重点是在任意时间提供结果,并且可能永远不会停止!数据项在它们发出时被传送,而不是在一个集合中一次传送。
因此,当您创建 Flow
时,您实际上还没有做任何工作。这就是为什么您的函数不是 suspend
函数,它只是创建对象。要实际获取这些项目,您需要对其调用 collect
,并且 that 需要在协程内部发生,因为这是异步内容实际发生的地方。
带Flow
和LiveData
的Dao函数没有挂起,可以从任何地方调用。
至于尽管它的 IO 访问是如何工作的?
您需要了解 async
编程的工作原理,async
编程背后的基本思想是 callback
。因此,您无需等待 IO 调用完成,而是注册一个回调,该回调将在响应准备就绪时调用。
在 LiveData
或 Flow
的情况下,您明确指定回调 。函数调用不会阻塞(不需要线程卸载),它只是请求一些数据并注册一个回调,当数据准备好时应该调用回调
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来说我们可以说是对协程进行分组和控制。