以异步方式返回多个返回值的方案:
在 Kotlin 协程 Coroutine 中 , 使用 suspend 挂起函数 以异步的方式 返回单个返回值肯定可以实现 , 如果要 以异步的方式 返回多个元素的返回值 , 可以使用如下方案 :
- 集合
- 序列
- Suspend 挂起函数
- Flow 异步流
同步调用返回多个值的弊端:
同步调用函数时 , 如果函数耗时太长或者中途有休眠 , 则会阻塞主线程导致 ANR 异常 ;
协程中 调用挂起函数 返回集合
如果要 以异步方式 返回多个返回值 , 可以在协程中调用挂起函数返回集合 , 但是该方案只能一次性返回多个返回值 , 不能持续不断的 先后 返回 多个 返回值 ;
// 携程中调用挂起函数返回多个值
// 调用 " 返回 List 集合的挂起函数 " , 并遍历返回值
runBlocking {
listFunction().forEach {
// 遍历打印集合中的内容
Log.e(TAG, "$it")
}
}
使用 Flow 异步流持续获取不同返回值
序列可以先后返回多个返回值 , 但是会阻塞线程 ;序列可以先后返回多个返回值 , 但是会阻塞线程 ;
本篇博客中开始引入 Flow 异步流的方式 , 持续性返回多个返回值 ;
调用 flow 构建器 , 可创建 Flow 异步流 , 在该异步流中, 异步地产生指定类型的元素 ;
public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
在 flow 异步流构建器中 , 通过调用 FlowCollector#emit 生成一个元素 ; 函数原型如下 :
/**
* [FlowCollector]用作流的中间或终端收集器,并表示接收[Flow]发出的值的实体。
* 该接口通常不应该直接实现,而是在实现自定义操作符时作为[flow]构建器中的接收器使用。
* 这个接口的实现不是线程安全的。
*/
public interface FlowCollector<in T> {
/**
* 收集上游发出的值。
* 此方法不是线程安全的,不应该并发调用。
*/
public suspend fun emit(value: T)
}
调用 Flow#collect 函数, 可以获取在异步流中产生的元素 , 并且该操作是异步操作, 不会阻塞调用线程 ;
public interface Flow<out T> {
/**
* 接收给定的[collector]并[发出][FlowCollector]。向它发射]值。
* 永远不应该实现或直接使用此方法。
*
* 直接实现“Flow”接口的唯一方法是扩展[AbstractFlow]。
* 要将它收集到特定的收集器,可以使用' collector. emitall (flow) '或' collect{…}的扩展
* 应该使用。这样的限制确保了上下文保存属性不被侵犯,并防止了大多数情况
* 与并发性、不一致的流调度程序和取消相关的开发人员错误。
*/
@InternalCoroutinesApi
public suspend fun collect(collector: FlowCollector<T>)
}
案例
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 协程中调用挂起函数flowFunction()返回一个 Flow 异步流
runBlocking{
// 调用 Flow#collect 函数, 可以获取在Flow异步流中产生的元素值it
val mFlow: Flow<Int> = flowFunction()
mFlow.collect(collector = {
Log.e(TAG," 收集Flow异步流冷流mFlow的协程上下文 : ${Thread.currentThread().name}")
// 每隔 500ms 即可 获取 Flow异步流中的一个Int 元素
// 并且该操作是异步操作, 不会阻塞调用线程
Log.e(TAG, "收集Flow异步流冷流mFlow中的一个个元素it=$it")
})
}
/**
Flow 异步流冷流mFlow的构建器的上下文 : main
收集Flow异步流冷流mFlow的协程上下文 : main
Flow 异步流冷流mFlow发射元素值i=0
收集Flow异步流冷流mFlow中的一个个元素it=0
Flow 异步流冷流mFlow发射元素值i=1
收集Flow异步流冷流mFlow中的一个个元素it=1
Flow 异步流冷流mFlow发射元素值i=2
收集Flow异步流冷流mFlow中的一个个元素it=2
*/
}
/**
* 使用 flow 构建器 Flow 异步流
* 在该异步流中, 异步地产生 Int 元素
*/
suspend fun flowFunction(): Flow<Int>{
val mFlow : Flow<Int> = flow<Int>(block = {
Log.e(TAG, "输出接受者对象this=${this}")
Log.e(TAG, "Flow 异步流冷流mFlow的构建器的上下文 : ${Thread.currentThread().name}")
for (i in 0..2) {
// 挂起函数 挂起 500ms
// 在协程中, 该挂起操作不会阻塞调用线程, 会继续执行其它代码指令
// 500ms 恢复执行, 继续执行挂起函数之后的后续代码指令
delay(500)
// 每隔 500ms 产生一个元素
// 通过调用 FlowCollector#emit 生成一个元素
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i")
this.emit(i)
}
})
return mFlow
}
}
Flow 异步流获取返回值方式与其它方式对比
① 异步流构建方式 : Flow 异步流是通过 flow 构建器函数 创建的 ;
public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
② 构建器可调用挂起函数 : flow 构建器代码块中的代码 , 是可以挂起的 , 可以在其中调用 挂起函数 , 如 kotlinx.coroutines.delay
函数等 ;
/**
* 使用 flow 构建器 Flow 异步流
* 在该异步流中, 异步地产生 Int 元素
*/
suspend fun flowFunction(): Flow<Int>{
val mFlow : Flow<Int> = flow<Int>(block = {
Log.e(TAG, "输出接受者对象this=${this}")
Log.e(TAG, "Flow 异步流冷流mFlow的构建器的上下文 : ${Thread.currentThread().name}")
for (i in 0..2) {
// 挂起函数 挂起 500ms
// 在协程中, 该挂起操作不会阻塞调用线程, 会继续执行其它代码指令
// 500ms 恢复执行, 继续执行挂起函数之后的后续代码指令
delay(500)
// 每隔 500ms 产生一个元素
// 通过调用 FlowCollector#emit 生成一个元素
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i")
this.emit(i)
}
})
return mFlow
}
③ suspend 关键字可省略 : 返回值为 Flow 异步流的函数 , 其默认就是 suspend 挂起函数 , suspend 关键字可以省略 , 上述函数中不标注 suspend 也可 ;
/**
* 使用 flow 构建器 Flow 异步流
* 在该异步流中, 异步地产生 Int 元素
*/
// suspend fun flowFunction(): Flow<Int>{
fun flowFunction(): Flow<Int>{
val mFlow : Flow<Int> = flow<Int>(block = {
Log.e(TAG, "输出接受者对象this=${this}")
Log.e(TAG, "Flow 异步流冷流mFlow的构建器的上下文 : ${Thread.currentThread().name}")
for (i in 0..2) {
// 挂起函数 挂起 500ms
// 在协程中, 该挂起操作不会阻塞调用线程, 会继续执行其它代码指令
// 500ms 恢复执行, 继续执行挂起函数之后的后续代码指令
delay(500)
// 每隔 500ms 产生一个元素
// 通过调用 FlowCollector#emit 生成一个元素
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i")
this.emit(i)
}
})
return mFlow
}
④ 生成元素 : 在 Flow 异步流中 , 通过调用 FlowCollector#emit
函数生成元素 ;
⑤ 收集元素 : 在 Flow 异步流中 , 通过调用 Flow#collect
函数可以收集 在 Flow 异步流中生成的元素 ;
在 Android 中 使用 Flow 异步流下载文件
Android 中主线程不可执行网络相关操作 , 因此只能在 子线程 中下载文件 ,可以在协程中使用 Dispatcher.IO 调度器在子线程下载文件 ,下载文件时需要实时显示下载百分比进度 ,这个进度需要上报给主线程 , 在主线程中更新 UI 显示下载进度 ,在 Flow 异步流中 , 可以 使FlowCollector#emit 向主线程中发送进度值 。在主线程中 , 可以 使用 Flow#collect 函数 收集 Flow 异步流中发射出来的数据 , 如 : 进度 , 捕获的异常 , 下载状态等 ;
完整流程 , 如下图所示 :
Flow 冷流 ( 流被收集时运行 )
Flow 异步流 的 构建器函数 flow 函数 中的 代码 ,在 调用 Flow#collect 函数 时 , 也就是在 Flow 异步流 收集元素时 ,才会 执行 flow 构建器 中的代码 ;这种机制的异步流 称为 冷流 ;
Flow 异步流冷流代码示例 :
在 flow 构建器的开始位置 , 发射元素 , 在主线程中 Flow#collect 收集元素位置 , 添加日志信息 , 查看日志打印的时机 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 协程中调用挂起函数flowFunction()返回一个 Flow 异步流
runBlocking{
// 调用 Flow#collect 函数, 可以获取在Flow异步流中产生的元素值it
val mFlow: Flow<Int> = flowFunction()
mFlow.collect(collector = {
Log.e(TAG," 收集Flow异步流冷流mFlow的协程上下文 : ${Thread.currentThread().name}")
// 每隔 500ms 即可 获取 Flow异步流中的一个Int 元素
// 并且该操作是异步操作, 不会阻塞调用线程
Log.e(TAG, "收集Flow异步流冷流mFlow中的一个个元素it=$it")
})
}
/**
Flow 异步流冷流mFlow的构建器的上下文 : main
收集Flow异步流冷流mFlow的协程上下文 : main
Flow 异步流冷流mFlow发射元素值i=0
收集Flow异步流冷流mFlow中的一个个元素it=0
Flow 异步流冷流mFlow发射元素值i=1
收集Flow异步流冷流mFlow中的一个个元素it=1
Flow 异步流冷流mFlow发射元素值i=2
收集Flow异步流冷流mFlow中的一个个元素it=2
*/
}
/**
* 使用 flow 构建器 Flow 异步流
* 在该异步流中, 异步地产生 Int 元素
*/
suspend fun flowFunction(): Flow<Int>{
val mFlow : Flow<Int> = flow<Int>(block = {
Log.e(TAG, "输出接受者对象this=${this}")
Log.e(TAG, "Flow 异步流冷流mFlow的构建器的上下文 : ${Thread.currentThread().name}")
for (i in 0..2) {
// 挂起函数 挂起 500ms
// 在协程中, 该挂起操作不会阻塞调用线程, 会继续执行其它代码指令
// 500ms 恢复执行, 继续执行挂起函数之后的后续代码指令
delay(500)
// 每隔 500ms 产生一个元素
// 通过调用 FlowCollector#emit 生成一个元素
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i")
this.emit(i)
}
})
return mFlow
}
}
Flow 流的连续性
Flow 流 的 每次调用 Flow#collect 收集元素的操作 , 都是 按照 固定顺序 执行的 , 使用 特殊操作符 可以改变该顺序 ;
Flow 异步流 中的元素 , 按照顺序进行 FlowCollector#emit 发射元素操作 , 则 调用 Flow#collect 收集元素时获取的元素 也是按照顺序获取的 ;
在流的 上游发射元素 到 下游收集元素 的过程中 , 会 使用 过渡操作符 处理每个 FlowCollector#emit 发射出的元素 , 最终才交给 最末端的 操作符 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 协程中调用挂起函数返回一个 Flow 异步流
runBlocking{
// 使用下面的方式asFlow()可以快速构建一个 Flow 流
//上游发射元素
val mFlow : Flow<Int> = (0..5).asFlow()
val filterFlow: Flow<Int> = mFlow.filter (predicate={
if( it % 2 == 1){
Log.e(TAG,"Flow 异步流冷流filterFlow发射过滤后符合条件的元素值it=${it}")
}
// 奇数才能继续向下流,偶数被过滤掉了
it % 2 == 1 //Lambda最后一行最为返回值
})
filterFlow.collect(collector = {
Log.e(TAG, "下游收集Flow异步流冷流filterFlow过滤后满足条件的一个个元素it=$it")
})
//将Flow异步流冷流filterFlow的元素值进行转换后,返回一个新的Flow流 ,然后对流的元素进行发射
val mapFlow : Flow<String> = filterFlow.map<Int,String>(transform={
// 遍历元素, 将其拼接成字符串
val str = "学号 : $it"
Log.e(TAG,"Flow 异步流冷流mapFlow发射转换后的元素值str=$str")
str //Lambda最后一行最为返回值
})
mapFlow.collect(collector = {
Log.e(TAG, "下游收集Flow异步流冷流mapFlow的一个个元素it=$it")
})
}
/**
* Flow 异步流冷流filterFlow发射过滤后符合条件的元素值it=1
*下游收集Flow异步流冷流filterFlow过滤后满足条件的一个个元素it=1
* Flow 异步流冷流filterFlow发射过滤后符合条件的元素值it=3
* 下游收集Flow异步流冷流filterFlow过滤后满足条件的一个个元素it=3
* Flow 异步流冷流filterFlow发射过滤后符合条件的元素值it=5
* 下游收集Flow异步流冷流filterFlow过滤后满足条件的一个个元素it=5
*
* Flow 异步流冷流mapFlow发射转换后的元素值str=学号 : 1
* 下游收集Flow异步流冷流mapFlow的一个个元素it=学号 : 1
* Flow 异步流冷流mapFlow发射转换后的元素值str=学号 : 3
* 下游收集Flow异步流冷流mapFlow的一个个元素it=学号 : 3
* Flow 异步流冷流mapFlow发射转换后的元素值str=学号 : 5
* 下游收集Flow异步流冷流mapFlow的一个个元素it=学号 : 5
*
* */
}
}
Flow流的构建器函数
1、使用 flow()函数 构建 Flow异步流冷流
在 flow 流构建器中 , 调用 FlowCollector#emit 函数 发射元素 , 然后在外部 调用 Flow#collect 函数 收集元素 ;
public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
2、使用flowOf()函数 构建 Flow异步流冷流
public fun <T> flowOf(vararg elements: T): Flow<T> = flow {
for (element in elements) {
emit(element)
}
}
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
// 协程中调用挂起函数返回一个 Flow 异步流
val mFlowOf: Flow<Int> = flowOf(0, 1, 2, 3)
val mOnEachFlow: Flow<Int> = mFlowOf.onEach {
// 每次发射元素时调用的代码块
delay(1000)
Log.e(TAG,"Flow 异步流冷流mOnEachFlow发射元素值it=$it")
}
mOnEachFlow.collect(collector = {
// 每隔 1 秒接收一个元素
Log.e(TAG, "下游收集Flow异步流冷流mOnEachFlow的一个个元素it=$it")
})
}
/**
* Flow 异步流冷流mOnEachFlow发射元素值it=0
*下游收集Flow异步流冷流mOnEachFlow的一个个元素it=0
*
* Flow 异步流冷流mOnEachFlow发射元素值it=1
* 下游收集Flow异步流冷流mOnEachFlow的一个个元素it=1
*
* Flow 异步流冷流mOnEachFlow发射元素值it=2
* 下游收集Flow异步流冷流mOnEachFlow的一个个元素it=2
*
* Flow 异步流冷流mOnEachFlow发射元素值it=3
* 下游收集Flow异步流冷流mOnEachFlow的一个个元素it=3
* */
}
}
3、使用asFlow()函数 构建 Flow异步流冷流
public fun IntRange.asFlow(): Flow<Int> = flow {
forEach { value ->
emit(value)
}}
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 协程中调用挂起函数返回一个 Flow 异步流
runBlocking{
// 使用下面的方式asFlow()可以快速构建一个 Flow 流
//上游发射元素
val mFlow : Flow<Int> = (0..5).asFlow()
val filterFlow: Flow<Int> = mFlow.filter (predicate={
if( it % 2 == 1){
Log.e(TAG,"Flow 异步流冷流filterFlow发射过滤后符合条件的元素值it=${it}")
}
// 奇数才能继续向下流,偶数被过滤掉了
it % 2 == 1 //Lambda最后一行最为返回值
})
filterFlow.collect(collector = {
Log.e(TAG, "下游收集Flow异步流冷流filterFlow过滤后满足条件的一个个元素it=$it")
})
//将Flow异步流冷流filterFlow的元素值进行转换后,返回一个新的Flow流 ,然后对流的元素进行发射
val mapFlow : Flow<String> = filterFlow.map<Int,String>(transform={
// 遍历元素, 将其拼接成字符串
val str = "学号 : $it"
Log.e(TAG,"Flow 异步流冷流mapFlow发射转换后的元素值str=$str")
str //Lambda最后一行最为返回值
})
mapFlow.collect(collector = {
Log.e(TAG, "下游收集Flow异步流冷流mapFlow的一个个元素it=$it")
})
}
/**
* Flow 异步流冷流filterFlow发射过滤后符合条件的元素值it=1
*下游收集Flow异步流冷流filterFlow过滤后满足条件的一个个元素it=1
* Flow 异步流冷流filterFlow发射过滤后符合条件的元素值it=3
* 下游收集Flow异步流冷流filterFlow过滤后满足条件的一个个元素it=3
* Flow 异步流冷流filterFlow发射过滤后符合条件的元素值it=5
* 下游收集Flow异步流冷流filterFlow过滤后满足条件的一个个元素it=5
*
* Flow 异步流冷流mapFlow发射转换后的元素值str=学号 : 1
* 下游收集Flow异步流冷流mapFlow的一个个元素it=学号 : 1
* Flow 异步流冷流mapFlow发射转换后的元素值str=学号 : 3
* 下游收集Flow异步流冷流mapFlow的一个个元素it=学号 : 3
* Flow 异步流冷流mapFlow发射转换后的元素值str=学号 : 5
* 下游收集Flow异步流冷流mapFlow的一个个元素it=学号 : 5
*
* */
}
}
Flow 异步流的上下文
1、上下文保存
Flow 异步流 收集元素 的操作 , 一般是在 协程上下文 中进行的 , 如 : 在协程中调用 Flow#collect 函数 , 收集元素 ;
收集元素 时 的 协程上下文 , 会 传递给 发射元素 的 流构建器 , 作为 流构建器的 上下文 ;Flow 异步流 在 收集元素 时 , 才调用 流构建器 中的代码 , 收集元素操作在协程中执行 , 流构建器 也同样在相同的协程中运行 ;
流收集元素 和 发射元素 在相同的协程上下文中 的 属性 , 称为 上下文保存 ;
2、流收集函数原型
Flow#collect 函数原型如下 : Flow#collect 函数 由 suspend 关键字修饰 , 该函数是 suspend 挂起函数 , 因此 该函数必须在 协程中调用 ;
public suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
collect(object : FlowCollector<T> {
override suspend fun emit(value: T) = action(value)
})
3、流发射函数原型
Flow 异步流的 构建器 函数 : 流构建器 不是 suspend 挂起函数 , 可以在普通的线程中运行 , 不必在协程中运行 ;
使用 flow()函数 构建 Flow异步流冷流
public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
使用flowOf()函数 构建 Flow异步流冷流
public fun <T> flowOf(vararg elements: T): Flow<T> = flow {
for (element in elements) {
emit(element)
}
}
使用asFlow()函数 构建 Flow异步流冷流
@FlowPreview
public fun <T> (() -> T).asFlow(): Flow<T> = flow {
emit(invoke())
}
4、代码示例 - 查看流发射和收集的协程
代码示例 : 在 流收集 时 和 流构建时 , 分别打印线程名称 , 查看是在哪个线程中执行的 ;
执行结果 : 最终执行时 , 流构建器和流收集 都是在 主线程中执行的 , 这是 由 runBlocking 协程构建器 将 主线程 包装后的 协程 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 协程中调用挂起函数flowFunction()返回一个 Flow 异步流
runBlocking{
// 调用 Flow#collect 函数, 可以获取在Flow异步流中产生的元素值it
val mFlow: Flow<Int> = flowFunction()
mFlow.collect(collector = {
Log.e(TAG," 收集Flow异步流冷流mFlow的协程上下文 : ${Thread.currentThread().name}")
// 每隔 500ms 即可 获取 Flow异步流中的一个Int 元素
// 并且该操作是异步操作, 不会阻塞调用线程
Log.e(TAG, "收集Flow异步流冷流mFlow中的一个个元素it=$it")
})
}
/**
Flow 异步流冷流mFlow的构建器的上下文 : main
收集Flow异步流冷流mFlow的协程上下文 : main
Flow 异步流冷流mFlow发射元素值i=0
收集Flow异步流冷流mFlow中的一个个元素it=0
Flow 异步流冷流mFlow发射元素值i=1
收集Flow异步流冷流mFlow中的一个个元素it=1
Flow 异步流冷流mFlow发射元素值i=2
收集Flow异步流冷流mFlow中的一个个元素it=2
*/
}
/**
* 使用 flow 构建器 Flow 异步流
* 在该异步流中, 异步地产生 Int 元素
*/
suspend fun flowFunction(): Flow<Int>{
val mFlow : Flow<Int> = flow<Int>(block = {
Log.e(TAG, "输出接受者对象this=${this}")
Log.e(TAG, "Flow 异步流冷流mFlow的构建器的上下文 : ${Thread.currentThread().name}")
for (i in 0..2) {
// 挂起函数 挂起 500ms
// 在协程中, 该挂起操作不会阻塞调用线程, 会继续执行其它代码指令
// 500ms 恢复执行, 继续执行挂起函数之后的后续代码指令
delay(500)
// 每隔 500ms 产生一个元素
// 通过调用 FlowCollector#emit 生成一个元素
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i")
this.emit(i)
}
})
return mFlow
}
}
5、代码示例 - 不能在不同协程中执行相同流的发射和收集操作
修改流发射的协程上下文
在上述 流的收集 和 流的发射 都 必须在同一个协程中执行 , 这样并不是我们想要的 ;如 : 下载时 , 想要在后台线程中下载 , 在主线程中更新 UI , 那么对应 Flow 异步流应该是在 后台线程中 发射元素 , 在主线程中 收集元素 ;
使用 flowOn 操作符 , 可以修改 流发射 的协程上下文 , 不必必须在 流收集 的协程上下文中执行 流发射操作 ;
1、Flow#flowOn 函数原型
Flow#flowOn 函数原型如下 :
/**
* 将此流执行的上下文更改为给定的[context]。
* 此操作符是可组合的,仅影响前面没有自己上下文的操作符。
* 这个操作符是上下文保护的:[context] **不会**泄漏到下游流中。
*
* 例如:
*
* ```
* withContext(Dispatchers.Main) {
* val singleValue = intFlow // will be executed on IO if context wasn't specified before
* .map { ... } // Will be executed in IO
* .flowOn(Dispatchers.IO)
* .filter { ... } // Will be executed in Default
* .flowOn(Dispatchers.Default)
* .single() // Will be executed in the Main
* }
* ```
*
* 有关上下文保存的更多说明,请参考[Flow]文档。
*
* 如果更改上下文不需要更改,则此操作符保留流的_sequential_性质
* (调度)[CoroutineDispatcher]。否则,如果需要更改dispatcher,它将进行收集
* 使用指定[上下文]运行的协同例程中的流发射,并从另一个协同例程中发射它们
* 使用带有[default][channel]的通道与原始收集器的上下文连接。BUFFERED]缓冲区大小
* 在两个协程之间,类似于[buffer]操作符,除非显式调用[buffer]操作符
* 在' flowOn '之前或之后,请求缓冲行为并指定通道大小。
*
* 注意,跨不同调度程序操作的流在取消时可能会丢失一些正在运行的元素。
* 特别是,该操作符确保下游流不会在取消时恢复,即使元素
* 已经被上游的气流释放出来了。
*
* ###算子融合
*
* 相邻的[channelFlow]、[flowOn]、[buffer]和[produceIn]的应用是
* 始终融合,以便只有一个正确配置的通道用于执行。
*
* 多个“flowOn”操作符融合到一个具有组合上下文的单一“flowOn”。上下文的要素
* 第一个' flowOn '操作符自然优先于第二个' flowOn '操作符的元素
* 当它们具有相同的上下文键时,例如:
*
* ```
* flow.map { ... } // Will be executed in IO
* .flowOn(Dispatchers.IO) // This one takes precedence
* .flowOn(Dispatchers.Default)
* ```
*
* 请注意,[SharedFlow]的实例本身没有执行上下文,
* 所以应用' flowOn '到' SharedFlow '没有效果。参见[SharedFlow]关于Operator Fusion的文档。
*
* @throws [IllegalArgumentException] 如果所提供的上下文包含[Job]实例。
*/
public fun <T> Flow<T>.flowOn(context: CoroutineContext): Flow<T> {
checkFlowContext(context)
return when {
context == EmptyCoroutineContext -> this
this is FusibleFlow -> fuse(context = context)
else -> ChannelFlowOperatorImpl(this, context = context)
}
}
2、代码示例
流发射 在子线程中执行 , 流收集 在 主线程中执行 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking{
// 调用 Flow#collect 函数, 可以获取在Flow异步流中产生的元素值it
val mFlow: Flow<Int> = flowFunction2()
mFlow.collect(collector = {
// 每隔 500ms 即可 获取 Flow异步流中的一个Int 元素
// 并且该操作是异步操作, 不会阻塞调用线程
Log.e(TAG, "收集Flow异步流冷流mFlow中的一个个元素it=$it -------- mFlow的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
Flow 异步流冷流mFlow发射元素值i=0 -------- mFlow构建器所在的上下文 : DefaultDispatcher-worker-3
收集Flow异步流冷流mFlow中的一个个元素it=0 -------- mFlow的协程所在的上下文 : main
Flow 异步流冷流mFlow发射元素值i=1 -------- mFlow构建器所在的上下文 : DefaultDispatcher-worker-1
收集Flow异步流冷流mFlow中的一个个元素it=1 -------- mFlow的协程所在的上下文 : main
Flow 异步流冷流mFlow发射元素值i=2 -------- mFlow构建器所在的上下文 : DefaultDispatcher-worker-3
收集Flow异步流冷流mFlow中的一个个元素it=2 -------- mFlow的协程所在的上下文 : main
* */
}
/**
* 使用 flow 构建器 Flow 异步流
* 在该异步流中, 异步地产生 Int 元素
*/
suspend fun flowFunction2(): Flow<Int>{
val mFlow : Flow<Int> = flow<Int>(block = {
Log.e(TAG, "输出接受者对象this=${this}")
for (i in 0..2) {
// 挂起函数 挂起 500ms
// 在协程中, 该挂起操作不会阻塞调用线程, 会继续执行其它代码指令
// 500ms 恢复执行, 继续执行挂起函数之后的后续代码指令
delay(500)
// 每隔 500ms 产生一个元素
// 通过调用 FlowCollector#emit 生成一个元素
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i -------- mFlow构建器所在的上下文 : ${Thread.currentThread().name}")
this.emit(i)
}
}).flowOn(context=Dispatchers.IO)
return mFlow
}
}
调用 Flow#launchIn() 函数指定流收集的协程
1、指定流收集的协程
响应式编程 , 是 基于事件驱动 的 , 在 Flow 流中会产生源源不断的事件 , 就是 发射元素操作 ;拿到 Flow 流后 , 开始 收集元素 , 按照顺序逐个处理产生的事件 ( 元素 ) ;
调用 Flow#launchIn()函数 , 传入 协程作用域 作为参数 , 可以 指定 收集 Flow 流元素 的 协程 ;
我们知道调用 Flow#flowOn() 函数 , 可以 指定 Flow 流发射元素 的 协程 ;
Flow#launchIn() 函数返回值是 Job 对象 , 是 协程任务对象 , 可调用 Job#cancel 函数取消该协程任务 ;
2、Flow#launchIn() 函数原型
/**
* 终端流操作符,在[作用域]中[启动][启动]给定流的[收集][收集]。
* 它是“范围”(scope)的简称。启动{flow.collect()} '。
*
* 此操作符通常与[onEach], [onCompletion]和[catch]操作符一起使用,以处理所有发出的值
* 处理上游流或处理过程中可能发生的异常,例如:
*
* ```
* flow
* .onEach { value -> updateUi(value) }
* .onCompletion { cause -> updateUi(if (cause == null) "Done" else "Failed") }
* .catch { cause -> LOG.error("Exception: $cause") }
* .launchIn(uiScope)
* ```
*
* 注意,[launchIn]的结果值没有被使用,提供的作用域负责取消。
*/
public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
collect() // tail-call
}
3、代码示例
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val mFlowEvent: Flow<Int> = flowEvent()
val mOnEachFlow:Flow<Int> = mFlowEvent.onEach (action={
// 逐个处理产生的事件
Log.e(TAG, "收集Flow异步流冷流mFlowEvent中的一个个元素it=$it -------- mFlow的协程所在的上下文 : ${Thread.currentThread().name}")
})
// 该 launchIn 函数返回一个 Job 对象
val job: Job = mOnEachFlow.launchIn(CoroutineScope(Dispatchers.IO)) // 在指定的协程作用域中处理收集元素操作,
job.join() // 该协程不是 runBlocking 主协程 的子协程, 需要调用 join 等待协程执行完毕
}
/**
* Flow 异步流冷流mAsFlow发射元素值it=0 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流mFlowEvent中的一个个元素it=0 -------- mFlow的协程所在的上下文 : DefaultDispatcher-worker-3
*
* Flow 异步流冷流mAsFlow发射元素值it=1 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-3
* 收集Flow异步流冷流mFlowEvent中的一个个元素it=1 -------- mFlow的协程所在的上下文 : DefaultDispatcher-worker-1
*
* Flow 异步流冷流mAsFlow发射元素值it=2 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流mFlowEvent中的一个个元素it=2 -------- mFlow的协程所在的上下文 : DefaultDispatcher-worker-2
*
* Flow 异步流冷流mAsFlow发射元素值it=3 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
*收集Flow异步流冷流mFlowEvent中的一个个元素it=3 -------- mFlow的协程所在的上下文 : DefaultDispatcher-worker-1
* */
}
/**
* 使用 flow 构建器 Flow 异步流
* 产生事件的 事件源
*/
suspend fun flowEvent(): Flow<Int>{
// 将区间转为 Flow 流
val mAsFlow: Flow<Int> = (0..3).asFlow()
val mOnEachFlow:Flow<Int> = mAsFlow.onEach(action={
// 发射元素 ( 产生事件 ) 时挂起 500ms
delay(500)
Log.e(TAG,"Flow 异步流冷流mAsFlow发射元素值it=$it -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
})
val mFlowOn:Flow<Int> = mOnEachFlow.flowOn(context=Dispatchers.Default) // 设置发射元素的协程
return mFlowOn
}
}
通过取消流收集所在的协程取消流
Flow 流的 收集元素 操作 , 是在协程中执行 , 将 协程 取消 , 即可将 Flow 流收集操作 取消 , 也就是 将 Flow 流取消 ;
在 Flow#collect 代码块中 , 执行 Job#cancel 函数 , 即可取消该流收集操作所在的协程 , 进而取消了流 ;
代码示例1 :使用 withTimeoutOrNull(2000)
创建一个协程 , 该协程在 3000ms 后自动超时取消 , 同时在其中进行 流收集 的操作也一并取消 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
// 该协程作用域 2 秒后超时取消
withTimeoutOrNull(timeMillis=3000,block={
// 协程中调用挂起函数返回一个 Flow 异步流
val mFlowEvent: Flow<Int> = flowEvent()
mFlowEvent.collect(collector={
Log.e(TAG, "收集Flow异步流冷流mFlowEvent中的一个个元素it=$it -------- mFlowEvent的协程所在的上下文 : ${Thread.currentThread().name}")
})
})
Log.e(TAG,"协程作用域取消")
}
/**Flow 异步流冷流mAsFlow发射元素值it=0 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流mFlowEvent中的一个个元素it=0 -------- mFlowEvent的协程所在的上下文 : main
*
* Flow 异步流冷流mAsFlow发射元素值it=1 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流mFlowEvent中的一个个元素it=1 -------- mFlowEvent的协程所在的上下文 : main
*
* Flow 异步流冷流mAsFlow发射元素值it=2 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流mFlowEvent中的一个个元素it=2 -------- mFlowEvent的协程所在的上下文 : main
*
* Flow 异步流冷流mAsFlow发射元素值it=3 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流mFlowEvent中的一个个元素it=3 -------- mFlowEvent的协程所在的上下文 : main
*
* Flow 异步流冷流mAsFlow发射元素值it=4 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流mFlowEvent中的一个个元素it=4 -------- mFlowEvent的协程所在的上下文 : main
*
* 协程作用域取消
* */
}
/**
* 使用 flow 构建器 Flow 异步流
* 产生事件的 事件源
*/
private suspend fun flowEvent(): Flow<Int>{
// 将区间转为 Flow 流
val mAsFlow: Flow<Int> = (0..10).asFlow()
val mOnEachFlow:Flow<Int> = mAsFlow.onEach(action={
// 发射元素 ( 产生事件 ) 时挂起 500ms
delay(500)
Log.e(TAG,"Flow 异步流冷流mAsFlow发射元素值it=$it -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
})
val mFlowOn:Flow<Int> = mOnEachFlow.flowOn(context=Dispatchers.Default) // 设置发射元素的协程
return mFlowOn
}
}
调用 FlowCollector#emit 发射元素时自动执行 Flow 流的取消检测
在 Flow 流构建器 中 , 每次 调用 FlowCollector#emit 发射元素时 ,都会执行一个 ensureActive 检测 , 检测当前的流是否取消 ,因此 , 在 flow 流构建器 中 , 循环执行的 FlowCollector#emit 发射操作 , 是可以取消的 ;
在 Flow#collect 代码块中 , 执行 Job#cancel 函数 , 即可取消该流收集操作所在的协程 , 进而取消了流 ;
/**
* 用一个可选的cancel [cause]取消这个作用域,包括它的作业和它的所有子任务。
* 原因可用于指定错误消息或提供有关的其他详细信息
* 用于调试目的的取消原因。
* 如果作用域中没有作业,则抛出[IllegalStateException]。
*/
public fun CoroutineScope.cancel(cause: CancellationException? = null) {
val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
job.cancel(cause)
}
代码示例1 :在 Flow#collect 代码块中 , 执行 Job#cancel 函数 , 即可取消该流收集操作所在的协程 , 进而取消了流 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val mFlowEvent2 : Flow<Int> = flowEvent2()
mFlowEvent2.collect(collector={
Log.e(TAG, "收集Flow异步流冷流mFlowEvent2中的一个个元素it=$it -------- mFlowEvent2的协程所在的上下文 : ${Thread.currentThread().name}")
if(it==3){
// 收集到元素 3 时, 取消流
// 在流中 emit 发射 3 时, 就会自动爆出异常, 停止后续操作
cancel()
}
})
}
/**
*收集Flow异步流冷流mFlowEvent2中的一个个元素it=1 -------- mFlowEvent2的协程所在的上下文 : main
*Flow 异步流冷流mFlow发射元素值i=1 --------mFlow构建器所在的上下文 : main
*
* 收集Flow异步流冷流mFlowEvent2中的一个个元素it=2 -------- mFlowEvent2的协程所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=2 --------mFlow构建器所在的上下文 : main
*
* 收集Flow异步流冷流mFlowEvent2中的一个个元素it=3 -------- mFlowEvent2的协程所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=3 --------mFlow构建器所在的上下文 : main
*/
}
/**
* 使用 flow 构建器 Flow 异步流
*/
private suspend fun flowEvent2(): Flow<Int>{
val mFlow:Flow<Int> = flow<Int>(block = {
for(i in 1..6) {
delay(1000)
emit(i)
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i --------mFlow构建器所在的上下文 : ${Thread.currentThread().name}")
}
})
return mFlow
}
}
调用 Flow#cancellable() 函数启用检测 Flow 流的取消
在 Flow 流中 , 除 FlowCollector#emit 发射元素 之外 ,还有很多其它的 流操作 , 这些操作不会 自动执行 ensureActive 检测 ,因此这里需要我们 手动 进行 流取消检测 ;调用 Flow#cancellable() 函数 , 可以手动设置流取消检测 ;
代码示例1 :调用 Flow#cancellable() 函数 , 可以手动设置流取消检测 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
(0..5).asFlow().cancellable().collect {
Log.e(TAG, "收集Flow异步流冷流中的一个个元素it=$it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
// 收集到元素 2 时, 协程退出
if (it == 2) {
cancel()
}
}
Log.e(TAG,"协程作用域取消")
}
/**
* 收集Flow异步流冷流中的一个个元素it=0 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=1 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=2 --------流的协程所在的上下文 : main
*
* */
}
}
背压概念
" 背压 " 概念 指的是 数据 受到 与 流动方向 一致的压力 ,
数据 生产者 的 生产效率 大于 数据 消费者 的 消费效率 , 就会产生 背压 ;
处理背压问题 , 有 2 种方案 :
- 降低 数据 生产者 的生产效率 ;
- 提高 数据 消费者 的消费效率 ;
背压代码示例 :
以 100 ms间隔发射元素 , 以 200 ms 间隔收集元素 , 发射元素的效率 高于 收集元素的效率, 此时会产生背压 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val delta = measureTimeMillis {
// 以 200 ms 的间隔收集元素
// 发射元素的效率 高于 收集元素的效率, 此时会产生背压
flowEmit().collect{
delay(200)
Log.e(TAG, "收集Flow异步流冷流中的一个个元素it=$it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
}
}
Log.e(TAG,"Flow异步流冷流收集元素总共耗时 $delta ms")
}
/**
*收集Flow异步流冷流中的一个个元素it=0 --------流的协程所在的上下文 : main
*Flow 异步流冷流mFlow发射元素值i=0 --------mFlow构建器所在的上下文 : main
收集Flow异步流冷流中的一个个元素it=1 --------流的协程所在的上下文 : main
Flow 异步流冷流mFlow发射元素值i=1 --------mFlow构建器所在的上下文 : main
收集Flow异步流冷流中的一个个元素it=2 --------流的协程所在的上下文 : main
Flow 异步流冷流mFlow发射元素值i=2 --------mFlow构建器所在的上下文 : main
收集Flow异步流冷流中的一个个元素it=3 --------流的协程所在的上下文 : main
Flow 异步流冷流mFlow发射元素值i=3 --------mFlow构建器所在的上下文 :
main收集Flow异步流冷流中的一个个元素it=4 --------流的协程所在的上下文 : main
Flow 异步流冷流mFlow发射元素值i=4--------mFlow构建器所在的上下文 : main
main收集Flow异步流冷流中的一个个元素it=5 --------流的协程所在的上下文 : main
Flow 异步流冷流mFlow发射元素值i=5--------mFlow构建器所在的上下文 : main
Flow异步流冷流收集元素总共耗时 1815 ms
* */
}
/**
* 使用 flow 构建器 Flow 异步流
*/
suspend fun flowEmit(): Flow<Int> {
// 以 100 ms 的间隔发射元素
val mFlow:Flow<Int> = flow<Int>(block ={
for (i in 0..5) {
delay(100)
emit(i)
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i --------mFlow构建器所在的上下文 : ${Thread.currentThread().name}")
}
})
return mFlow
}
}
使用缓冲处理背压问题
调用 Flow#buffer 函数 , 为 收集元素 添加一个缓冲 , 可以指定缓冲区个数 ;
代码示例 :发射元素后 , 将发射的元素缓存起来 , 然后慢慢接收元素 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val delta = measureTimeMillis {
// 以 200 ms 的间隔收集元素
// 发射元素的效率 高于 收集元素的效率, 此时会产生背压
flowEmit().buffer(10).collect{
delay(200)
Log.e(TAG, "收集Flow异步流冷流中的一个个元素it=$it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
}
}
Log.e(TAG,"Flow异步流冷流收集元素总共耗时 $delta ms")
}
/**
* Flow 异步流冷流mFlow发射元素值i=0 --------mFlow构建器所在的上下文 : main
*Flow 异步流冷流mFlow发射元素值i=1 --------mFlow构建器所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=0 --------流的协程所在的上下文 : main
*
* Flow 异步流冷流mFlow发射元素值i=2 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=3 --------mFlow构建器所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=1 --------流的协程所在的上下文 : main
*
* Flow 异步流冷流mFlow发射元素值i=4 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=5 --------mFlow构建器所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=2 --------流的协程所在的上下文 : main
*
* 收集Flow异步流冷流中的一个个元素it=3 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=4 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=5 --------流的协程所在的上下文 : main
* Flow异步流冷流收集元素总共耗时 1320 ms
*
* */
}
/**
* 使用 flow 构建器 Flow 异步流
*/
suspend fun flowEmit(): Flow<Int> {
// 以 100 ms 的间隔发射元素
val mFlow:Flow<Int> = flow<Int>(block ={
for (i in 0..5) {
delay(100)
emit(i)
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i --------mFlow构建器所在的上下文 : ${Thread.currentThread().name}")
}
})
return mFlow
}
}
使用 flowOn 处理背压问题
上述 发射元素 和 收集元素 都是在同一个线程中执行的 , 这两个操作可以并行执行 , 即使用 flowOn 指定收集元素的线程 ;使用 flowOn 更改了协程上下文 , 使得 发射元素 与 收集元素 在不同的线程中并行执行 ;
代码示例 :
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val delta = measureTimeMillis {
// 以 200 ms 的间隔收集元素
// 发射元素的效率 高于 收集元素的效率, 此时会产生背压
flowEmit().flowOn(Dispatchers.Default).collect{
delay(200)
Log.e(TAG, "收集Flow异步流冷流中的一个个元素it=$it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
}
}
Log.e(TAG,"Flow异步流冷流收集元素总共耗时 $delta ms")
}
/**Flow 异步流冷流mFlow发射元素值i=0 --------mFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* Flow 异步流冷流mFlow发射元素值i=1 --------mFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流中的一个个元素it=0 --------流的协程所在的上下文 : main
*
* Flow 异步流冷流mFlow发射元素值i=2 --------mFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* Flow 异步流冷流mFlow发射元素值i=3 --------mFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流中的一个个元素it=1 --------流的协程所在的上下文 : main
*
* Flow 异步流冷流mFlow发射元素值i=4 --------mFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* Flow 异步流冷流mFlow发射元素值i=5 --------mFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流中的一个个元素it=2 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=3 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=4 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=5 --------流的协程所在的上下文 : main
* Flow异步流冷流收集元素总共耗时 1316 ms
* */
}
/**
* 使用 flow 构建器 Flow 异步流
*/
suspend fun flowEmit(): Flow<Int> {
// 以 100 ms 的间隔发射元素
val mFlow:Flow<Int> = flow<Int>(block ={
for (i in 0..5) {
delay(100)
emit(i)
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i --------mFlow构建器所在的上下文 : ${Thread.currentThread().name}")
}
})
return mFlow
}
}
从提高收集元素效率方向解决背压问题
从提高收集元素效率方向解决背压问题 :
❶调用 Flow#conflate 函数 , 合并发射元素项 , 不对每个值进行单独处理 ;
❷调用 Flow#collectLatest 函数 , 取消并重新发射最后一个元素 , 只关心最后一个结果 , 不关心中间的过程值 ;
Flow#conflate 代码示例:
发射了 6 个元素 , 但是只接收到了 4个元素 , 接收的元素 2 4 被过滤掉了 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val delta = measureTimeMillis {
// 以 200 ms 的间隔收集元素
// 发射元素的效率 高于 收集元素的效率, 此时会产生背压
flowEmit().conflate().collect{
delay(200)
Log.e(TAG, "收集Flow异步流冷流中的一个个元素it=$it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
}
}
Log.e(TAG,"Flow异步流冷流收集元素总共耗时 $delta ms")
}
/**
*Flow 异步流冷流mFlow发射元素值i=0 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=1 --------mFlow构建器所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=0 --------流的协程所在的上下文 : main
*
* Flow 异步流冷流mFlow发射元素值i=2 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=3 --------mFlow构建器所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=1 --------流的协程所在的上下文 : main
*
* Flow 异步流冷流mFlow发射元素值i=4 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=5 --------mFlow构建器所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=3 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=5 --------流的协程所在的上下文 : main
*
* Flow异步流冷流收集元素总共耗时 915 ms
*
* */
}
/**
* 使用 flow 构建器 Flow 异步流
*/
suspend fun flowEmit(): Flow<Int> {
// 以 100 ms 的间隔发射元素
val mFlow:Flow<Int> = flow<Int>(block ={
for (i in 0..5) {
delay(100)
emit(i)
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i --------mFlow构建器所在的上下文 : ${Thread.currentThread().name}")
}
})
return mFlow
}
}
Flow#collectLatest 代码示例:
只接收了最后一个元素 , 前几个元素没有接收 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val delta = measureTimeMillis {
// 以 200 ms 的间隔收集元素
// 发射元素的效率 高于 收集元素的效率, 此时会产生背压
flowEmit().collectLatest {
delay(200)
Log.e(TAG, "收集Flow异步流冷流中的一个个元素it=$it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
}
}
Log.e(TAG,"Flow异步流冷流收集元素总共耗时 $delta ms")
}
/**Flow 异步流冷流mFlow发射元素值i=0 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=1 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=2 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=3 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=4 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=5 --------mFlow构建器所在的上下文 : main
*
* 收集Flow异步流冷流中的一个个元素it=5 --------流的协程所在的上下文 : main
* Flow异步流冷流收集元素总共耗时 838 ms
*/
}
/**
* 使用 flow 构建器 Flow 异步流
*/
suspend fun flowEmit(): Flow<Int> {
// 以 100 ms 的间隔发射元素
val mFlow:Flow<Int> = flow<Int>(block ={
for (i in 0..5) {
delay(100)
emit(i)
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i --------mFlow构建器所在的上下文 : ${Thread.currentThread().name}")
}
})
return mFlow
}
}
Flow 操作符
Flow 操作符主要分类:过渡操作符、限长操作符、末端操作符
过渡操作符
过渡操作符 相关概念 :
❶转换流 : 使用 过渡操作符 转换 Flow 流 ;
❷作用位置 : 过渡操作符作用 于 流的上游 , 返回 流的下游 ;
❸非挂起函数 : 过渡操作符 不是挂起函数 , 属于冷操作符 ;
❹运行速度 : 过渡操作符 可以 快速返回 新的 转换流 ;
1.map 操作符
通过 map 操作符 , 可以操作每个元素 , 将元素转为另外一种类型的元素 ;
map 操作符原型 :
/**
* 返回一个流,其中包含对原始流的每个值应用给定[transform]函数的结果。
*/
public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value ->
return@transform emit(transform(value))
}
代码示例 : 将 Flow 中发射的 Int 元素 转为 字符串 ; 通过 map 操作符 , 将 Int 类型的元素 转为 字符串类型 元素 ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
(0..3).asFlow()
// 通过 map 操作符 将 Flow 中发射的 Int 元素it 转为 字符串
.map(transform={
stringConvert(it)
})
.collect(collector={
Log.e(TAG, "收集Flow异步流冷流中的一个个元素it=$it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
* 收集Flow异步流冷流中的一个个元素it=convert 0 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=convert 1 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=convert 2 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=convert 3 --------流的协程所在的上下文 : main
* */
}
// 将 Int 转为 字符串
suspend fun stringConvert(num: Int): String {
delay(1000)
return "convert $num"
}
}
2.transform 操作符
通过 transform 操作符 , 可以操作每个元素 , 可以在单个元素处理时 , 发射多次元素 ;
transform 操作符原型 :
/**
* 将[transform]函数应用到给定流的每个值。
*
* ' transform '的接收者是[FlowCollector],因此' transform '是一个
* 灵活的函数,可以转换发出的元素,跳过它或多次发出它。
*
* 该操作符泛化了[filter]和[map]操作符和
* 可以用作其他操作符的构建块,例如:
*
* ```
* fun Flow<Int>.skipOddAndDuplicateEven(): Flow<Int> = transform { value ->
* if (value % 2 == 0) { // Emit only even values, but twice
* emit(value)
* emit(value)
* } // Do nothing if odd
* }
* ```
*/
public inline fun <T, R> Flow<T>.transform(
@BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = flow { // 注意:这里使用的是安全流,因为收集器对每个操作的转换都是公开的
collect { value ->
// 没有它,单元将被退回,TCE将不会生效,KT-28938
return@collect transform(value)
}
}
代码示例 :
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
(0..3).asFlow()
// 通过 map 操作符 将 Flow 中发射的 Int 元素it 转为 字符串
.transform (transform={
Log.e(TAG,"接受者对象this=$this,回调的参数it=$it")
// 在 transform 操作符中发射 2 个元素
emit(it)
emit(stringConvert(it))
})
.collect(collector={
Log.e(TAG, "收集Flow异步流冷流中的一个个元素it=$it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
*收集Flow异步流冷流中的一个个元素it=0 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=convert 0 --------流的协程所在的上下文 : main
* 接受者对象this=Continuation at kotlinx.coroutines.flow.internal.SafeCollector,回调的参数it=1
*
* 收集Flow异步流冷流中的一个个元素it=1 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=convert 1 --------流的协程所在的上下文 : main
* 接受者对象this=Continuation at kotlinx.coroutines.flow.internal.SafeCollector,回调的参数it=2
*
* 收集Flow异步流冷流中的一个个元素it=2 --------流的协程所在的上下文 : main
*收集Flow异步流冷流中的一个个元素it=convert 2 --------流的协程所在的上下文 : main
* 接受者对象this=Continuation at kotlinx.coroutines.flow.internal.SafeCollector,回调的参数it=3
*
* 收集Flow异步流冷流中的一个个元素it=3 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=convert 3 --------流的协程所在的上下文 : main
*
* */
}
// 将 Int 转为 字符串
suspend fun stringConvert(num: Int): String {
delay(1000)
return "convert $num"
}
}
限长操作符
1.take 操作符
通过 take 操作符 , 可以选择选取指定个数的发射元素 ;
如 : 在 Flow 流中发射了 4 个元素 , 但是调用了 Flow#take(2) , 只收集其中 2 个元素 ;
take 操作符原型 :
/**
* 返回包含第一个[count]元素的流。
* 当[count]元素被消耗时,原始流将被取消。
* 如果[count]不是正数,抛出[IllegalArgumentException]。
*/
public fun <T> Flow<T>.take(count: Int): Flow<T> {
require(count > 0) { "Requested element count $count should be positive" }
return unsafeFlow {
var consumed = 0
try {
collect { value ->
// 注意:这个for take不是故意用collectWhile写的。
// 它首先检查条件,然后对emit或emitAbort进行尾部调用。
// 这样,正常的执行不需要状态机,只需要终止(emitAbort)。
// 有关不同方法的比较,请参阅“TakeBenchmark”。
if (++consumed < count) {
return@collect emit(value)
} else {
return@collect emitAbort(value)
}
}
} catch (e: AbortFlowException) {
e.checkOwnership(owner = this)
}
}
}
代码示例 :
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
(0..5).asFlow()
.take(3)
.collect(collector={
Log.e(TAG, "收集Flow异步流冷流中的一个个元素it=$it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
* 收集Flow异步流冷流中的一个个元素it=0 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=1 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素it=2 --------流的协程所在的上下文 : main
*
* */
}
}
末端(终端)操作符
何为末端(终端)操作符函数? 末端(终端)操作符的后面不可以再接其他操作符函数了,而是只能获取最终的运行结果。
末端操作符 指的是 在 Flow 流最末端 调用 挂起函数 收集元素 的操作符 , 最常见的 末端操作符 就是 collect 操作符 ;
常见的末端(终端)操作符 :
- 收集元素 : collect ;
- 将收集的元素转为集合 : toList , toSet ;
- 收集第一个元素 : first ;
- 发射单个元素 : single ;
- 规约流到单个值 : reduce , fold ;
1、collect 操作符
collect 操作符原型 :
/**
* 终端流操作符,使用提供的[动作]收集给定的流。
* 如果在收集过程中或在所提供的流中发生任何异常,则此方法将重新抛出此异常。
*
* 使用示例:
*
* ```
* val flow = getMyEvents()
* try {
* flow.collect { value ->
* println("Received $value")
* }
* println("My events are consumed successfully")
* } catch (e: Throwable) {
* println("Exception from the flow: $e")
* }
* ```
*/
public suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
collect(object : FlowCollector<T> {
override suspend fun emit(value: T) = action(value)
})
2、reduce 操作符
我个人认为reduce函数还是比较好理解的,它的基本公式如下:
flow.reduce { acc, value -> acc + value }
其中acc是累积值的意思,value则是当前值的意思。
也就是说,reduce函数会通过参数给我们一个Flow的累积值acc和一个Flow的当前值value ,我们可以在函数体中对它们进行一定的运算,运算的结果会作为下一个累积值继续传递到reduce函数当中。
reduce 操作符原型 :
从第一个元素开始累加值,并对当前累加器值和每个元素应用[操作]。
/**
* 从第一个元素开始累加值,并对当前累加器值和每个元素应用[操作]。
* 如果流为空,抛出[NoSuchElementException]。
*/
public suspend fun <S, T : S> Flow<T>.reduce(operation: suspend (accumulator: S, value: T) -> S): S {
var accumulator: Any? = NULL
collect { value ->
accumulator = if (accumulator !== NULL) {
@Suppress("UNCHECKED_CAST")
operation(accumulator as S, value)
} else {
value
}
}
if (accumulator === NULL) throw NoSuchElementException("Empty flow can't be reduced")
@Suppress("UNCHECKED_CAST")
return accumulator as S
}
reduce使用示例:
举一个更加具体点的例子,我们上学时学等差数列都会讲这个故事,高斯的老师让全班同学计算从1加到100的结果。今天我们不需要借助等差数列,只需要借助reduce函数就可以立刻算出结果了:
reduce函数是一个终端操作符函数,它的后面不可以再接其他操作符函数了,而是只能获取最终的运行结果。
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val mFlow : Flow<Int> = flow(block={
for (i in (1..100)) {
emit(i)
Log.e(TAG,"Flow 异步流冷流mFlow发射元素值i=$i --------mFlow构建器所在的上下文 : ${Thread.currentThread().name}")
}
})
val result:Int = mFlow.reduce(operation={ acc: Int, value:Int->
acc+value
})
Log.e(TAG,"返回最终计算结果result=$result")
}
/**
* Flow 异步流冷流mFlow发射元素值i=1 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=2 --------mFlow构建器所在的上下文 : main
* ...
* Flow 异步流冷流mFlow发射元素值i=99 --------mFlow构建器所在的上下文 : main
* Flow 异步流冷流mFlow发射元素值i=100 --------mFlow构建器所在的上下文 : main
* 返回最终计算结果result=5050
* */
}
}
runBlocking {
val mAsFlow:Flow<Int> = (1..100)
.asFlow() //asFlow=flow+for
val result:Int = mAsFlow.reduce(operation={ acc: Int, value:Int->
acc+value
})
Log.e(TAG,"返回最终计算结果result=$result")
}
// 返回最终计算结果result=5050
3、fold 操作符
从[initial]值开始累加值,并应用[operation]当前累加器值和每个元素
fold函数和reduce函数基本上是完全类似的,
主要的区别在于,fold函数需要传入一个初始值initial,这个初始值initial会作为首个累积值被传递到fold的函数体当中,它的基本公式如下:
flow.fold(initial) { acc, value -> acc + value }
注意:其实reduce函数和fold函数并不是只能用作数值计算,相反它们可以作用于任何类型的数据。
fold 操作符原型 :
/**
* 从[initial]值开始累加值,并应用[operation]当前累加器值和每个元素
*/
public suspend inline fun <T, R> Flow<T>.fold(
initial: R,
crossinline operation: suspend (acc: R, value: T) -> R
): R {
var accumulator = initial
collect { value ->
accumulator = operation(accumulator, value)
}
return accumulator
}
fold使用示例:
这里我们将字母A-Z进行了拼接,另外fold函数要求传入一个初始值initial,那么我们就再添加一个字符 串 “大写的字母:” 的头部值,
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val mFlow: Flow<Char> = flow(block = {
for (i in ('A'..'Z')) {
emit(i)
Log.e(TAG, "Flow 异步流冷流mFlow发射元素值i=$i --------mFlow构建器所在的上下文 : ${Thread.currentThread().name}")
}
})
val result: String = mFlow.fold(initial = "大写的字母: ", operation = { acc, value ->
acc + value
})
Log.e(TAG, "$result")
}
//大写的字母: ABCDEFGHIJKLMNOPQRSTUVWXYZ
}
}
4、single 操作符
single 操作符原型 :
终端操作符,等待一个且仅等待一个值发出。
/**
* 终端操作符,等待一个且仅等待一个值发出。
* 为空流抛出[NoSuchElementException],为流抛出[IllegalStateException]
* 包含多个元素的。
*/
public suspend fun <T> Flow<T>.single(): T {
var result: Any? = NULL
collect { value ->
require(result === NULL) { "Flow has more than one element" }
result = value
}
if (result === NULL) throw NoSuchElementException("Flow is empty")
return result as T
}
5、first 操作符
终端操作符,返回流发出的第一个元素,然后取消流的收集。
first 操作符原型 :
/**
* 终端操作符,返回流发出的第一个元素,然后取消流的收集。
* 如果流为空,则抛出[NoSuchElementException]。
*/
public suspend fun <T> Flow<T>.first(): T {
var result: Any? = NULL
collectWhile {
result = it
false
}
if (result === NULL) throw NoSuchElementException("Expected at least one element")
return result as T
}
6、toList 操作符
将给定的流收集到destination: MutableList<T>集合里
toList 操作符原型 :
/**
* 将给定的流收集到[destination]
*/
public suspend fun <T> Flow<T>.toList(destination: MutableList<T> = ArrayList()): List<T> = toCollection(destination)
7、toSet 操作符
将给定的流收集到 destination: MutableSet<T>
toSet 操作符原型 :
/**
* 将给定的流收集到[destination]
*/
public suspend fun <T> Flow<T>.toSet(destination: MutableSet<T> = LinkedHashSet()): Set<T> = toCollection(destination)
Flow 流组合
1、Flow#zip 组合多个流
调用 Flow#zip 函数 , 可以将两个 Flow 流合并为一个流 ;
将来自当前流(' this ')的值压缩到[其他]流,使用提供的[transform]函数应用到每对值。
在剩下的流上调用一个流完成和取消时,生成的流就会完成。
Flow#zip 函数原型 :
/**
* 将来自当前流(' this ')的值压缩到[其他]流,使用提供的[transform]函数应用到每对值。
* 在剩下的流上调用一个流完成和取消时,生成的流就会完成。
*
* 可以用下面的例子来演示:
* ```
* val flow = flowOf(1, 2, 3).onEach { delay(10) }
* val flow2 = flowOf("a", "b", "c", "d").onEach { delay(15) }
* flow.zip(flow2) { i, s -> i.toString() + s }.collect {
* println(it) // Will print "1a 2b 3c"
* }
* ```
*
* ### 缓冲
*
* 上游流在同一协程中按顺序收集,而不进行任何缓冲
* [other]流被并发收集,就像使用' buffer(0) '一样。参见[buffer]操作符中的文档
* 为解释。您可以根据需要使用对[buffer]操作符的额外调用,以获得更多并发性。
*/
public fun <T1, T2, R> Flow<T1>.zip(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = zipImpl(this, other, transform)
zip代码示例 :
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val numFlow : Flow<Int> = (1..5).asFlow()
val strFlow : Flow<String> = flowOf("One", "Two", "Three")
// 合并两个 Flow 流
val zipFlow : Flow<String> = numFlow.zip(other=strFlow,transform={num:Int,str:String->
val result:String="$num:$str"
result
})
zipFlow.collect(collector = {
Log.e(TAG, "收集Flow异步流冷流中的一个个元素 $it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
* 收集Flow异步流冷流中的一个个元素 1:One --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素 2:Two --------流的协程所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素 3:Three --------流的协程所在的上下文 : main
* */
}
}
2、新组合流的元素收集间隔与被组合流元素发射间隔的联系
假如两个 Flow 流的 元素发射 不同步 , 则 先发射的元素 , 需要等待对应顺序的 后发射的元素到来 ;
在下面的代码中 , numFlow 的发射元素间隔为 100ms , strFlow 发射元素间隔为 1000ms , 则 numFlow 元素收集到之后 , 需要等待 strFlow 元素收集 , 也就是 二者合并后的间隔以 慢的为准 , 合并后的流 发射间隔为 1000ms ;
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
val fastSentFlow :Flow<Int> = (1..5)
.asFlow()
.onEach(action={
// 发射元素 ( 产生事件 ) 时挂起 100ms
delay(100)
Log.e(TAG,"Flow 异步流冷流fastSentFlow发射元素值it=$it -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
})
val slowSentFlow :Flow<String> = flowOf("One", "Two", "Three", "Four", "Five")
.onEach(action={
// 发射元素 ( 产生事件 ) 时挂起 1000ms
delay(1000)
Log.e(TAG,"Flow 异步流冷流slowSentFlow发射元素值it=$it -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
})
// 合并两个 Flow 流
val zipFlow : Flow<String> = fastSentFlow.zip(other=slowSentFlow,transform={fastSentValue:Int,slowSentValue:String->
val result:String="$fastSentValue:$slowSentValue"
result
})
zipFlow.collect(collector = {
Log.e(TAG, "收集Flow异步流冷流zipFlow中的一个个元素 $it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
* Flow 异步流冷流fastSentFlow发射元素值it=1 -------- mAsFlow构建器所在的上下文 : main
* Flow 异步流冷流slowSentFlow发射元素值it=One -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流zipFlow中的一个个元素 1:One --------流的协程所在的上下文 : main
*
* Flow 异步流冷流fastSentFlow发射元素值it=2 -------- mAsFlow构建器所在的上下文 : main
* Flow 异步流冷流slowSentFlow发射元素值it=Two -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流zipFlow中的一个个元素 2:Two --------流的协程所在的上下文 : main
*
* Flow 异步流冷流fastSentFlow发射元素值it=3 -------- mAsFlow构建器所在的上下文 : main
* Flow 异步流冷流slowSentFlow发射元素值it=Three -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流zipFlow中的一个个元素 3:Three --------流的协程所在的上下文 : main
*
* Flow 异步流冷流fastSentFlow发射元素值it=4 -------- mAsFlow构建器所在的上下文 : main
* Flow 异步流冷流slowSentFlow发射元素值it=Four -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流zipFlow中的一个个元素 4:Four --------流的协程所在的上下文 : main
*
* Flow 异步流冷流fastSentFlow发射元素值it=5 -------- mAsFlow构建器所在的上下文 : main
* Flow 异步流冷流slowSentFlow发射元素值it=Five -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流zipFlow中的一个个元素 5:Five --------流的协程所在的上下文 : main
* */
}
}
Flow流展平
Flow 流在 接收元素 时 , 可能需要 另一个 流的元素 , 两个流之间进行 交互的操作 就是 展平 , 常见的 展平模式有 :
•连接模式 flatMapConcat : m 个元素的流 与 n 个元素的流 连接后 , 元素个数为 m x n 个 ;
•合并模式 flatMapMerge : m 个元素的流 与 n 个元素的流 合并后 , 元素个数为 n x m 个 ;
•最新展平模式 flatMapLatest : 前面的看时间间隔进行结合 , 中间的可能跳过某些元素 , 不要中间值 , 只重视最新的数据 ;前面我们所学的所有内容都是在一个flow上进行操作,而从flatMap开始,要上升到对两个flow进行操作了。flatMap的核心,就是将两个flow中的数据进行映射、合并、压平成一个flow,最后再进行输出。
1、连接模式 flatMapConcat
连接模式 flatMapConcat : m 个元素的流 与 n 个元素的流 连接后 , 元素个数为 m x n 个 ;
通过应用[transform]转换原始流发出的元素,它返回另一个流,然后连接并压平这些流。
该方法是 flatMapConcat =map(transform).flattenConcat() 的快捷方式。
请注意:尽管这个操作符flatMapConcat看起来非常熟悉,但我们不鼓励在常规的特定于应用程序的流中使用它。
最有可能的是,暂停[map]操作符中的操作就足够了,线性转换更容易推理。
flatMapConcat 函数原型 :
<T, R> Flow<T>.flatMapConcat(transform: suspend (value: T) -> Flow<R>): Flow<R>
flatMapConcat是Flow<T>的扩展函数
通过lambda表达式实现参数transform, 把Flow<T>流发射元素值作为输出回调参数it,
lambda表达式里, 获取第一个异步流firstFlow发射的元素值,
在lambda表达式里,创建第二个异步流secondFlow:Flow<String>,把第一个firstFlow发射的元素值组合/组装/拼接为第二个异步流secondFlow的发射元素值
/**
* 通过应用[transform]转换原始流发出的元素,它返回另一个流,
* 然后连接并压平这些流。
*
* 该方法是' map(transform).flattenConcat() '的快捷方式。看到[flattenConcat]。
*
* 请注意,尽管这个操作符看起来非常熟悉,但我们不鼓励在常规的特定于应用程序的流中使用它。
* 最有可能的是,暂停[map]操作符中的操作就足够了,线性转换更容易推理。
*/
@FlowPreview
public fun <T, R> Flow<T>.flatMapConcat(transform: suspend (value: T) -> Flow<R>): Flow<R> =
map(transform).flattenConcat()
代码示例 :
class FlowActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
Log.e(TAG, "============================== flatMapConcat 1 : 有问题 无法输出发射元素值 ========================================================")
/**<T, R> Flow<T>.flatMapConcat(transform: suspend (value: T) -> Flow<R>): Flow<R>
*flatMapConcat是Flow<T>的扩展函数
* 通过lambda表达式实现参数transform, 把Flow<T>流发射元素值作为输出回调参数it,
* 在lambda表达式里, 获取第一个异步流firstFlow发射的元素值,
* 在lambda表达式里,创建第二个异步流secondFlow:Flow<String>,把第一个firstFlow发射的元素值组合/组装/拼接为第二个异步流secondFlow的发射元素值
*/
runBlocking {
val firstFlow: Flow<Int> = flowOf(1, 2, 3)
val flatMapConcatFlow: Flow<String> = firstFlow.flatMapConcat(transform={ firstFlowValue:Int->
Log.e(TAG,"输出回调参数(firstFlow发射元素值):${firstFlowValue}")
var secondFlow: Flow<String> = flowOf("a$firstFlowValue", "b$firstFlowValue", "c$firstFlowValue")
secondFlow//lambda 最后一行 最为返回值
})
//todo 直接flatMapConcatFlow onEach无法输出发射元素信息日志
flatMapConcatFlow.onEach(action={
Log.e(TAG,"Flow 异步流冷流flatMapConcatFlow发射元素值 =$it -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
})
flatMapConcatFlow.collect(collector={
Log.e(TAG, "收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 $it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
* 输出回调参数(firstFlow发射元素值):1
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a1 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b1 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c1 --------流的协程所在的上下文 : main
*
* 输出回调参数(firstFlow发射元素值):2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a2 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b2 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c2 --------流的协程所在的上下文 : main
*
* 输出回调参数(firstFlow发射元素值):3
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a3 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b3 --------流的协程所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b3 --------流的协程所在的上下文 : main
* */
Log.e(TAG, "============================== flatMapConcat 2 :直接链式调用onEach就可以输出发射元素信息日志 ========================================================")
runBlocking {
val firstFlow: Flow<Int> = flowOf(1, 2, 3)
val flatMapConcatFlow: Flow<String> = firstFlow.flatMapConcat(transform={ firstFlowValue:Int->
Log.e(TAG,"输出回调参数(firstFlow发射元素值):${firstFlowValue}")
var secondFlow: Flow<String> = flowOf("a$firstFlowValue", "b$firstFlowValue", "c$firstFlowValue")
secondFlow//lambda 最后一行 最为返回值
})
//todo 直接链式调用onEach就可以输出发射元素信息日志
.onEach(action={
Log.e(TAG,"Flow 异步流冷流发射元素值 =$it -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
})
//直接链式调用
.flowOn(context=Dispatchers.IO)
flatMapConcatFlow.collect(collector={
Log.e(TAG, "收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 $it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
*输出回调参数(firstFlow发射元素值):1
*Flow 异步流冷流发射元素值 =a1 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
*收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a1 --------流的协程所在的上下文 : main
*Flow 异步流冷流发射元素值 =b1 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b1 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =c1 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c1 --------流的协程所在的上下文 : main
*
* 输出回调参数(firstFlow发射元素值):2
* Flow 异步流冷流发射元素值 =a2 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a2 --------流的协程所在的上下文 : main
*Flow 异步流冷流发射元素值 =b2 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b2 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =c2 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c2 --------流的协程所在的上下文 : main
*
* 输出回调参数(firstFlow发射元素值):3
* Flow 异步流冷流发射元素值 =a3 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a3 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =b3 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b3 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =c3 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
*收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c3 --------流的协程所在的上下文 : main
*
* */
Log.e(TAG, "============================== flatMapConcat 3 :直接链式调用onEach就可以输出发射元素信息日志 ========================================================")
runBlocking {
val firstFlow: Flow<Int> = flowOf(1, 2, 3)
val flatMapConcatFlow: Flow<String> = firstFlow.flatMapConcat(transform={ firstFlowValue:Int->
Log.e(TAG,"输出回调参数(firstFlow发射元素值):${firstFlowValue}")
var secondFlow: Flow<String> =
flowOf("a$firstFlowValue", "b$firstFlowValue", "c$firstFlowValue")
.onEach(action={ //todo 直接链式调用onEach就可以输出发射元素信息日志
Log.e(TAG,"Flow 异步流冷流发射元素值 =$it -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
})
//直接链式调用
.flowOn(context=Dispatchers.IO)
secondFlow//lambda 最后一行 最为返回值
})
flatMapConcatFlow.collect(collector={
Log.e(TAG, "收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 $it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
*输出回调参数(firstFlow发射元素值):1
*Flow 异步流冷流发射元素值 =a1 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
*收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a1 --------流的协程所在的上下文 : main
*Flow 异步流冷流发射元素值 =b1 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b1 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =c1 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c1 --------流的协程所在的上下文 : main
*
* 输出回调参数(firstFlow发射元素值):2
* Flow 异步流冷流发射元素值 =a2 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a2 --------流的协程所在的上下文 : main
*Flow 异步流冷流发射元素值 =b2 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b2 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =c2 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c2 --------流的协程所在的上下文 : main
*
* 输出回调参数(firstFlow发射元素值):3
* Flow 异步流冷流发射元素值 =a3 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a3 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =b3 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b3 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =c3 -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-2
*收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c3 --------流的协程所在的上下文 : main
*
* */
}
}
flatMapConcat应用场景:
不知道你有没有遇到过这样的情况,请求一个网络资源时需要依赖于先去请求另外一个网络资源。
比如说我们想要获取用户的数据,但是获取用户数据必须要有token授权信息才行,因此我们得先发起一个请求去获取token信息,然后再发起另一个请求去获取用户数据。
这种两个网络请求之间存在依赖关系的代码其实挺不好写的,稍微一不注意就可能会陷入嵌套地狱:
public void getUserInfo() {
sendGetTokenRequest(new Callback() {
@Override
public void result(String token) {
sendGetUserInfoRequest(token, new Callback() {
@Override
public void result(String userInfo) {
// handle with userInfo
}
});
}
});
}
可以看出来,网终请求代码由于需要开线程执行,然后在回调中获取结果,通常会嵌套得比较深。
而这个问题我们就可以借助flatMapConcat函数来解决。
假设我们将sendGetTokenRequest()函数和sendGetUserInfoRequest()函数都使用flow的写法进行改造:
fun sendGetTokenRequest(): Flow<String> = flow {
// send request to get token
emit(token)
}
fun sendGetUserInfoRequest(token: String): Flow<String> = flow {
// send request with token to get user info
emit(userInfo)
}
那么接下来就可以用flatMapConcat函数将它们串连成一条链式执行的任务了:
class FlowActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
MainScope().launch {
val serviceResultFlow:Flow<String> =
sendGetTokenRequest()
.flatMapConcat(transform = { token: String ->
val sendGetUserInfoRequest: Flow<String> = sendGetUserInfoRequest(token)
sendGetUserInfoRequest//lambda 最后一行 最为返回值
})
//todo 直接链式调用onEach就可以输出发射元素信息日志
.onEach(action={
Log.e(TAG,"Flow 异步流冷流发射元素值 =$it -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
})
//直接链式调用
.flowOn(context = Dispatchers.IO)
serviceResultFlow.collect(collector={userInfo:String->
Log.e(TAG, "收集Flow异步流冷流serviceResultFlow的元素值userInfo=$userInfo--------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/** Flow 异步流冷流发射元素值 =admin -------- mAsFlow构建器所在的上下文 : DefaultDispatcher-worker-1
* 收集Flow异步流冷流serviceResultFlow的元素值userInfo=admin--------流的协程所在的上下文 : main
*/
}
fun sendGetTokenRequest(): Flow<String> {
// send request to get token
val tokenFlow:Flow<String> = flow<String>(block={
delay(10000)
emit("123456")
})
return tokenFlow
}
fun sendGetUserInfoRequest(token: String): Flow<String> = flow<String>{
// send request with token to get user info
delay(2000)
if(token=="123456"){
emit("admin")
}
}
}
很多个flow流串连成一条链式任务,只需要不断连缀flatMapConcat即可
fun main() {
runBlocking {
flow1.flatMapConcat { flow2 }
.flatMapConcat { flow3 }
.flatMapConcat { flow4 }
.collect { userInfo ->
println(userInfo)
}
}
}
2、合并模式 flatMapMerge
合并模式 flatMapMerge : m 个元素的流 与 n 个元素的流 合并后 , 元素个数为 n x m 个 ;
flatMapMerge 函数原型 :
/**
* 通过应用[transform]转换原始流发出的元素,它返回另一个流,
* 然后合并并压平这些气流。
*
* 此操作符按顺序调用[transform],然后将结果流与[concurrency]合并
* 对并发收集流的数量的限制。
* 它是' map(transform).flattenMerge(concurrency)'的快捷方式。
* 详见[flattenMerge]。
*
* 请注意,尽管这个操作符看起来非常熟悉,但我们不鼓励在常规的特定于应用程序的流中使用它。
* 最有可能的是,暂停[map]操作符中的操作就足够了,线性转换更容易推理。
*
* ###算子融合
*
* [flowOn]、[buffer]和[produceIn] __after_此操作符的应用被融合
* 它是并发合并,因此只有一个正确配置的通道用于执行合并逻辑。
*
* @param并发控制运行中的流的数量,最多收集[concurrency]个流
* 同时。默认情况下,它等于[DEFAULT_CONCURRENCY]。
*/
@FlowPreview
public fun <T, R> Flow<T>.flatMapMerge(
concurrency: Int = DEFAULT_CONCURRENCY,
transform: suspend (value: T) -> Flow<R>
): Flow<R> =
map(transform).flattenMerge(concurrency)
flatMapConcat与flatMapMerge的区别
这两个函数最主要的区别其实就在字面上。
concat是连接的意思,连接一定会保证数据是按照原有的顺序连接起来的,即使上游发射的数据被delay不同长短时间,也不会改变下游接收数据的原有顺序
merge是合并的意思。而合并则只保证将数据合并到一起,并不会保证顺序。flatMapMerge函数的内部是启用并发来进行数据处理的,它不会保证最终结果的顺序。
上游发射的数据被delay不同长短时间,哪条发射的数据被delay的时间更短,下游则会更先接收这条数据
代码示例 :
class FlowActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
flowOf(300, 200, 100)
.flatMapMerge(transform={firstFlowValue:Int->
flow(block={
delay(firstFlowValue.toLong())
emit("a$firstFlowValue")
emit("b$firstFlowValue")
emit("c$firstFlowValue")
})
})
// 直接链式调用onEach就可以输出发射元素信息日志
.onEach(action={
Log.e(TAG,"Flow 异步流冷流发射元素值 =$it -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
})
//直接链式调用
.collect(collector={
Log.e(TAG, "收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 $it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
* Flow 异步流冷流发射元素值 =a100 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a100 --------流的协程所在的上下文 : main
Flow 异步流冷流发射元素值 =b100 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b100 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =c100 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c100 --------流的协程所在的上下文 : main
*
* Flow 异步流冷流发射元素值 =a200 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a200 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =b200 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b200 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =c200 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c200 --------流的协程所在的上下文 : main
*
* Flow 异步流冷流发射元素值 =a300 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 a300 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =b300 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 b300 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =c300 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流flatMapConcatFlow中的一个个组合后的元素 c300 --------流的协程所在的上下文 : main
*
* */
}
}
3、 flatMapLatest
把两个flow合并、压平成一个flow。
flow1中的数据传递到flow2中会立刻进行处理,但如果flow1中的下一个数据要发送了,而flow2中上一个数据还没处理完,则flow2会直接将上一个还没处理完的剩余数据逻辑取消舍弃掉,开始处理最新的下一个数据。
flatMapLatest 函数原型 :
/**
* 返回一个流,每当原始流发出一个值时,该流切换到[transform]函数生成的新流。
* 当原始流产生一个新值时,由' transform '块产生的前一个流将被取消。
*
* 例如,以下流程:
* ```
* flow {
* emit("a")
* delay(100)
* emit("b")
* }.flatMapLatest { value ->
* flow {
* emit(value)
* delay(200)
* emit(value + "_last")
* }
* }
* ```
* produces `a b b_last`
*
* 该操作符默认为[buffered][buffer],其输出缓冲区的大小可以通过应用后续的[buffer]操作符来改变。
*/
@ExperimentalCoroutinesApi
public inline fun <T, R> Flow<T>.flatMapLatest(@BuilderInference crossinline transform: suspend (value: T) -> Flow<R>): Flow<R> =
transformLatest { emitAll(transform(it)) }
代码示例 :
class FlowActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
flow<Int> {
emit(1)
delay(150)
emit(2)
delay(50)
emit(3)
}.flatMapLatest { firstFlowValue:Int->
flow <String>{
delay(100)
emit("NO:${firstFlowValue}")
}
.onEach {
Log.e(TAG,"第二个Flow 异步流冷流发射元素值 $it -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
}
}
//直接链式调用
.collect(collector={
Log.e(TAG, "收集Flow异步流冷流中的一个个组合后的元素 $it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
})
}
/**
* 第二个Flow 异步流冷流发射元素值 NO:1 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流中的一个个组合后的元素 NO:1 --------流的协程所在的上下文 : main
*
* 第二个Flow 异步流冷流发射元素值 NO:3 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流中的一个个组合后的元素 NO:3 --------流的协程所在的上下文 : main
* */
}
}
Flow 流异常处理
在 Flow 流 运行时 , 抛出异常 , 可以使用
- try{}catch(e: Exception){} 代码块 收集元素时捕获异常
- Flow#catch 函数 发射元素时捕获异常
一、收集元素异常处理
异常代码示例 :
如果收集的元素 it <= 1
, 则检查通过 ,ok,正常执行;
如果收集的元素it>1,就会报异常崩溃
class FlowActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
runBlocking {
flow<Int>{
for (i in 0..5) {
emit(i)
Log.e(TAG,"Flow 异步流冷流发射元素值 =$i -------- mAsFlow构建器所在的上下文 : ${Thread.currentThread().name}")
}
}
.collect{
Log.e(TAG, "收集Flow异步流冷流中的一个个元素 $it --------流的协程所在的上下文 : ${Thread.currentThread().name}")
// 如果 判断条件it<=1 ok,正常执行; 如果it>1,就会报异常崩溃
check(value=it<=1,lazyMessage={
"抛出异常 :收集数据元素值,必须<= 1"
})
}
}
/**
* 收集Flow异步流冷流中的一个个元素 0 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =0 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素 1 --------流的协程所在的上下文 : main
* Flow 异步流冷流发射元素值 =1 -------- mAsFlow构建器所在的上下文 : main
* 收集Flow异步流冷流中的一个个元素 2 --------流的协程所在的上下文 : main
* 程序异常崩溃
*
* */
}
}