协程: Flow 异步流 /Flow 操作符/Flow 流异常处理

以异步方式返回多个返回值的方案:

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
    *  程序异常崩溃
    *
    * */


}

}

二、发射元素异常处理

鸣谢:

mAndroid面试题之Kotlin异步流、冷流Flow (qq.com)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值