Koltin Flow 基础详解

605 阅读8分钟

Koltin Flow 基础详解

kotlinx-coroutines-core 1.4.2版本出来了,没有了之前的实验方法警告,终于可以可以愉快的玩耍了,现在准备把有关flow的相关资料整理汇总一下

1.概览

Flow基础详解

2.Kotlin Flow 介绍

A cold asynchronous data stream that sequentially emits values and completes normally or with an exception。

意思是:按顺序发出值并正常完成或异常完成的Cold异步数据流。

与rxjava作用类似,可能会在以后的开发中逐步代替rxjava,使整个开发生态更加趋向一体化

4.Flow的创建

  • Empty Flow
emptyFlow<String>()
  • 通过flowOf函数
flowOf(1, 2, 3)
// 1, 2, 3
flowOf(listOf(1,2,3))
// [1, 2, 3]
  • Iterable调用asFlow函数
listOf(1, 2, 3).asFlow()
// 1, 2, 3
  • 无参但是有返回值的函数**(() -> T)**调用asFlow函数
fun flowBuilderFunction(): Int {
    return 10
}

::flowBuilderFunction.asFlow()

// 10
  • 无参但是有返回值的挂起函数**(() -> T)**调用asFlow函数
suspend fun flowBuilderFunction(): Int {
    return 10
}

::flowBuilderFunction.asFlow()

// 10
  • Array调用asFlow函数
LongRange(1, 5).asFlow().collect { value -> println(value) }

5.Flow操作符

Delay相关的操作符

  • debounce

    特性:

    1. 如果两个相邻的值生产出来的时间间隔超过了[timeout]毫秒,就忽过滤掉前一个值 最后一个值不受影响,总是会被释放emit。 [timeout]可以传毫秒,也可以传Duration
        flow {
            emit(1)
            delay(3000)
            emit(2)
            delay(1000)
            emit(3)
            delay(1000)
            emit(4)
        }.debounce(2000)
    
        // 结果:1 4 
        // 解释:
        //  2和1的间隔大于2000,1被释放
        //  3和2的间隔小于2000, 2被忽略
        //  4和3的间隔小于2000, 3被忽略
        //  4是最后一个值不受timeout值的影响,         4被释放
    
    
    
    flow {
        emit(1)
        delay(3000)
        emit(2)
        delay(1000)
        emit(3)
        delay(1000)
        emit(4)
    }.debounce(2000.milliseconds)
    
    // 结果:1 4 
    
    应用:可用于搜索框的反复输入内容筛选
    
    
    
    
    

Distinct相关的操作符

  • distinctUntilChanged

    1.如果生产的值和上个发送的值相同,值就会被过滤掉

      flow {
          emit(1)
          emit(1)
          emit(2)
          emit(2)
          emit(3)
          emit(4)
      }.distinctUntilChanged()
    
      // 结果:1 2 3 4
      // 解释:
      // 第一个1被释放
      // 第二个1由于和第一个1相同,被过滤掉
      // 第一个2被释放
      // 第二个2由于和第一个2相同,被过滤掉
      // 第一个3被释放
      // 第一个4被释放
    
    1. 可以传参(old: T, new: T) -> Boolean,进行自定义的比较
    private class Person(val age: Int, val name: String)
    
    flow {
        emit(Person(20, "张三"))
        emit(Person(21, "李四"))
        emit(Person(21, "王五"))
        emit(Person(22, "赵六"))
    }.distinctUntilChanged{old, new -> old.age == new.age }
    .collect{ value -> println(value.name) }
        
    // 结果:张三 李四 赵六
    // 解释:本例子定义如果年龄相同就认为是相同的值,所以王五被过滤掉了
    
    1. 可以用distinctUntilChangedBy转换成年龄进行对比
    flow {
        emit(Person(20, "张三"))
        emit(Person(21, "李四"))
        emit(Person(21, "王五"))
        emit(Person(22, "赵六"))
    }.distinctUntilChangedBy { person -> person.age }
    
    // 结果:张三 李四 赵六
    

Emitters相关的操作符

  • transform

    对每个值进行转换

    flow {
        emit(1)
        emit(2)
        emit(3)
        emit(4)
    }.transform {
        if (it % 2 == 0) {
            emit(it * it)
        }
    }
    
    // 结果:4 16
    // 解释:
    // 1 不是偶数,被忽略
    // 2 是偶数,2的平方4
    // 3 不是偶数,被忽略
    // 4 是偶数,4的平方16
    
  • onStart

    第一个值被释放之前被执行

        flow {
            emit(1)
            emit(2)
            emit(3)
            emit(4)
        }.onStart { emit(1000) }
    
        // 结果:1000 1 2 3 4
        // 解释:
        // 第一个值1被释放的时候调用了emit(10        00), 所以1000在1之前被释放
    
  • onCompletion

    最后一个值释放完成之后被执行

      flow {
          emit(1)
          emit(2)
          emit(3)
          emit(4)
      }.onCompletion { emit(1000) }
    
      // 结果:1 2 3 4 1000
      // 解释:
      // 第一个值4被释放的时候调用了emit(100        0), 所以1000在4之后被释放
    

Limit相关的操作符

  • drop

    忽略最开始的[count]个值

      flow {
          emit(1)
          emit(2)
          emit(3)
          emit(4)
      }.drop(2)
    
      // 结果:3 4
      // 解释:
      // 最开始释放的两个值(1,2)被忽略了
    
  • dropWhile

    判断第一个值如果满足(T) -> Boolean这个条件就忽略

      flow {
          emit(1)
          emit(2)
          emit(3)
          emit(4)
      }.dropWhile {
          it % 2 == 0
      }
    
      // 结果:1 2 3 4
      // 解释:
      // 第一个值不是偶数,所以1被释放
    
      flow {
          emit(1)
          emit(2)
          emit(3)
          emit(4)
      }.dropWhile {
          it % 2 != 0
      }
    
      // 结果:2 3 4
      // 解释:
      // 第一个值是偶数,所以1被忽略
    
  • take

    只释放前面[count]个值

      flow {
          emit(1)
          emit(2)
          emit(3)
          emit(4)
      }.take(2)
    
      // 结果:1 2
      // 解释:
      // 前面两个值被释放
    
  • takeWhile

    判断第一个值如果满足(T) -> Boolean这个条件就释放

      flow {
          emit(1)
          emit(2)
          emit(3)
          emit(4)
      }.takeWhile { it%2 != 0 }
    
      // 结果:1
      // 解释:
      // 第一个值满足是奇数条件
    
      flow {
          emit(1)
          emit(2)
          emit(3)
          emit(4)
      }.takeWhile { it%2 == 0 }
    
      // 结果:无
      // 解释:
      // 第一个值不满足是奇数条件
    
    

CoroutineContext相关的操作符

  • flowOn

    可以切换CoroutineContext 说明:flowOn只影响该运算符之前的CoroutineContext,对它之后的CoroutineContext没有任何影响

  • buffer

    将flow的多个任务分配到不同的协程中去执行,加快执行的速度。

  • conflate

    如果值的生产速度大于值的消耗速度,就忽略掉中间未来得及处理的值,只处理最新的值。

    val flow1 = flow {
        delay(2000)
        emit(1)
        delay(2000)
        emit(2)
        delay(2000)
        emit(3)
        delay(2000)
        emit(4)
    }.conflate()

    flow1.collect { value ->
        println(value)
        delay(5000)
    }

    // 结果: 1 3 4
    // 解释:
    // 2000毫秒后生产了1这个值,交由collect        执行,花费了5000毫秒,当1这个值执行co        llect完成后已经经过了7000毫秒。
    // 这7000毫秒中,生产了2,但是collect还        没执行完成又生产了3,所以7000毫秒以后        会直接执行3的collect方法,忽略了2这        个值
    // collect执行完3后,还有一个4,继续执        行。

Flatten相关的操作符

  • flatMapConcat
  将原始的Flow<T>通过[transform]转换成Flow<Flow<T>>,然后将Flow<Flow<T>>释放的Flow<T>其中释放的值一个个释放。
  
  flow {
      delay(1000)
      emit(1)
      delay(1000)
      emit(2)
      delay(1000)
      emit(3)
      delay(1000)
      emit(4)
  }.flatMapConcat {
      flow {
          emit("$it 产生第一个flow值")
          delay(2500)
          emit("$it 产生第二个flow值")
      }
  }.collect { value ->
      println(value)
  }
  
  // 结果
  // I/System.out: 1 产生第一个flow值
  // I/System.out: 1 产生第二个flow值
  // I/System.out: 2 产生第一个flow值
  // I/System.out: 2 产生第二个flow值
  // I/System.out: 3 产生第一个flow值
  // I/System.out: 3 产生第二个flow值
  // I/System.out: 4 产生第一个flow值
  // I/System.out: 4 产生第二个flow值
  
  // 解释:
  // 原始Flow<Int>通过flatMapConcat被转换成Flow<Flow<Int>>
  // 原始Flow<Int>首先释放1,接着Flow<Flow<Int>> 就会释放 1产生第一个flow值 和 1产生第二个flow值 两个值
  // Flow<Int>释放2,...
  // Flow<Int>释放3,...
  // Flow<Int>释放4,...
  
  • flattenConcat

    和flatMapConcat类似,只是少了一步Map操作。

    flow {
        delay(1000)
        emit(flow {
            emit("1 产生第一个flow值")
            delay(2000)
            emit("1 产生第二个flow值") })
        delay(1000)
        emit(flow {
            emit("2 产生第一个flow值")
            delay(2000)
            emit("3 产生第二个flow值") })
        delay(1000)
        emit(flow {
            emit("3 产生第一个flow值")
            delay(2000)
            emit("3 产生第二个flow值") })
        delay(1000)
        emit(flow {
            emit("4 产生第一个flow值")
            delay(2500)
            emit("4 产生第二个flow值") })
        }.flattenConcat()
        
    // 结果
    // I/System.out: 1 产生第一个flow值
    // I/System.out: 1 产生第二个flow值
    // I/System.out: 2 产生第一个flow值
    // I/System.out: 2 产生第二个flow值
    // I/System.out: 3 产生第一个flow值
    // I/System.out: 3 产生第二个flow值
    // I/System.out: 4 产生第一个flow值
    // I/System.out: 4 产生第二个flow值
    
  • flatMapMerge

    将原始的Flow通过[transform]转换成Flow<Flow>,然后将Flow<Flow>释放的Flow其中释放的值一个个释放。 它与flatMapConcat的区别是:Flow<Flow>释放的Flow其中释放的值没有顺序性,谁先产生谁先释放。

  flow {
      delay(1000)
      emit(1)
      delay(1000)
      emit(2)
      delay(1000)
      emit(3)
      delay(1000)
      emit(4)
  }.flatMapMerge {
      flow {
          emit("$it 产生第一个flow值")
          delay(2500)
          emit("$it 产生第二个flow值")
      }
  }.collect { value ->
      println(value)
  }
    
  • merge

    将Iterable<Flow>合并成一个Flow

 val flow1 = listOf(
     flow {
         emit(1)
         delay(500)
         emit(2)
     },
     flow {
         emit(3)
         delay(500)
         emit(4)
     },
     flow {
         emit(5)
         delay(500)
         emit(6)
     }
 )
 flow1.merge().collect { value -> println("$value") }
 
 // 结果: 1 3 5 2 4 6
 // 解释:
 // 按Iterable的顺序和耗时顺序依次释放值
  • transformLatest

    原始flow会触发transformLatest转换后的flow, 当原始flow有新的值释放后,transformLatest转换后的flow会被取消,接着触发新的转换后的flow

  • flatMapLatest

    和transformLatest类似, 原始flow会触发transformLatest转换后的flow, 当原始flow有新的值释放后,transformLatest转换后的flow会被取消,接着触发新的转换后的flow

    区别:flatMapLatest的transform转换成的是Flow, transformLatest的transform转换成的是Unit

  • mapLatest

    和transformLatest类似, 原始flow会触发transformLatest转换后的flow, 当原始flow有新的值释放后,transformLatest转换后的flow会被取消,接着触发新的转换后的flow

    区别:mapLatest的transform转换成的是T,flatMapLatest的transform转换成的是Flow,transformLatest的transform转换成的是Unit

Transform相关的操作符

  • filter

    通过predicate进行过滤,满足条件则被释放

    flow {
        emit(1)
        emit(2)
        emit(3)
        emit(4)
    }.filter { it % 2 == 0 }
    
    // 结果: 2 4
    // 解释:
    // 2和4满足it % 2 == 0,被释放
    
  • filterNot

    通过predicate进行过滤,不满足条件则被释放

    flow {
        emit(1)
        emit(2)
        emit(3)
        emit(4)
    }.filterNot { it % 2 == 0 }
    
    // 结果: 1 3
    // 解释:
    // 1和3不满足it % 2 == 0,被释放
    
  • filterIsInstance

    如果是某个数据类型则被释放

    flow {
        emit(1)
        emit("2")
        emit("3")
        emit(4)
    }.filterIsInstance<String>()
    
    // 结果: "2" "3"
    // 解释:
    // "2" "3"是String类型,被释放
    
  • filterNotNull

    如果数据是非空,则被释放

    flow {
        emit(1)
        emit("2")
        emit("3")
        emit(null)
    }.filterNotNull()
    
    // 结果: 1 "2" "3"
    
  • map

    将一个值转换成另外一个值

    flow {
        emit(1)
        emit(2)
        emit(3)
        emit(4)
    }.map { it * it }
    
    // 结果: 1 4 9 16
    // 解释:
    // 将1,2,3,4转换成对应的平方数
    
  • mapNotNull

    将一个非空值转换成另外一个值

  • withIndex

    将值封装成IndexedValue对象

    flow {
        emit(1)
        emit(2)
        emit(3)
        emit(4)
    }.withIndex()
    
    // 结果:
    // I/System.out: IndexedValue(index=0, value=1)
    // I/System.out: IndexedValue(index=1, value=2)
    // I/System.out: IndexedValue(index=2, value=3)
    // I/System.out: IndexedValue(index=3, value=4)
    
  • onEach

    每个值释放的时候可以执行的一段代码

  • scan

    有一个初始值,然后每个值都和初始值进行运算,然后这个值作为后一个值的初始值

    flow {
        emit(1)
        emit(2)
        emit(3)
        emit(4)
    }.scan(100) { acc, value ->
        acc * value
    }
    
    // 结果: 100 100 200 600 2400
    // 解释:
    // 初始值 100
    // 1  100 * 1 = 100
    // 2  100 * 2 = 200
    // 3  200 * 3 = 600
    // 4  600 * 4 = 2400
    
  • runningReduce

    和scan类似,但是没有初始值,最开始是它本身

    flow {
        emit(1)
        emit(2)
        emit(3)
        emit(4)
    }.runningReduce { acc, value ->
        acc * value
    }
    
    // 结果: 1 2 6 24
    // 解释:
    // 1  1
    // 2  1 * 2 = 2
    // 3  2 * 3 = 6
    // 4  6 * 4 = 24
    

合并的操作符

  • zip

    将两个Flow在回调函数中进行处理返回一个新的值 R 当两个flow的长度不等时只发送最短长度的事件

    val nums = (1..4).asFlow() 
    val strs = flowOf("one", "two", "three") 
    nums.zip(strs) { a, b -> "$a -> $b" }
        .collect { println(it) }
    
    // 结果:
    1 -> one
    2 -> two
    3 -> three
    
  • combine

    任意一个flow释放值且都有释放值后会调用combine后的代码块,且值为每个flow的最新值。 和zip的区别: 组合两个流,在经过第一次发射以后,任意方有新数据来的时候就可以发射,另一方有可能是已经发射过的数据

    val flow1 = flowOf(1, 2, 3, 4).onEach { delay(10) }
    val flow2 = flowOf("a", "b", "c", "d").onEach { delay(20) }
    
    flow1.combine(flow2) { first, second ->
        "$first$second"
    }.collect { println("$it") }
    
    // 结果:1a 2a 2b 3b 4b 4c 4d
    
    // 解释:
    // 开始 --- flow1 释放 1,flow2 释放 a, 释放1a
    // 10毫秒 --- flow1 释放 2,释放2a
    // 20毫秒 --- flow2 释放 b,此时释放2b
    // 30毫秒 --- flow1 释放 3,此时释放3b
    // 40毫秒 --- flow1 释放 4,此时释放4b
    // 40毫秒 --- flow2 释放 c,此时释放4c
    // 60毫秒 --- flow2 释放 d,此时释放4d
    

retry相关操作符

  • retry
  public fun <T> Flow<T>.retry( retries: Long = Long.MAX_VALUE, // 重试次数 predicate: suspend (cause: Throwable) -> Boolean = { true } ): Flow<T>
  • retryWhen
  public fun <T> Flow<T>.retryWhen(
    predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean
  ): Flow<T>

末端操作符

  • Collect相关的末端操作符

    • collect

      接收值

    • launchIn

      scope.launch { flow.collect() }的缩写, 代表在某个协程上下文环境中去接收释放的值

      val flow1 = flow {
          delay(1000)
          emit(1)
          delay(1000)
          emit(2)
          delay(1000)
          emit(3)
          delay(1000)
          emit(4)
      }
      
      flow1.onEach { println("$it") }
          .launchIn(GlobalScope)
      
      // 结果:1 2 3 4
      
    • collectIndexed

      和withIndex对应的,接收封装的IndexedValue

      val flow1 = flow {
          emit(1)
          emit(2)
          emit(3)
          emit(4)
      }.withIndex()
      
      flow1.collectIndexed { index, value ->
          println("index = $index, value = $value")
      }
      
      // 结果:
      // I/System.out: index = 0, value = IndexedValue(index=0, value=1)
      // I/System.out: index = 1, value = IndexedValue(index=1, value=2)
      // I/System.out: index = 2, value = IndexedValue(index=2, value=3)
      // I/System.out: index = 3, value = IndexedValue(index=3, value=4)
      
    • collectLatest

      collectLatest与collect的区别是,如果有新的值释放,上一个值的操作如果没执行完则将会被取消

      val flow1 = flow {
          emit(1)
          delay(1000)
          emit(2)
          delay(1000)
          emit(3)
          delay(2000)
          emit(4)
      }
      
      flow1.collectLatest {
          println("正在计算收到的值 $it")
          delay(1500)
          println("收到的值 $it")
      }
      
      // 结果:
      // I/System.out: 正在计算收到的值 1
      // I/System.out: 正在计算收到的值 2
      // I/System.out: 正在计算收到的值 3
      // I/System.out: 收到的值 3
      // I/System.out: 正在计算收到的值 4
      // I/System.out: 收到的值 4
      
      // 解释:
      // 1间隔1000毫秒后释放2,2间隔1000毫秒后释放3,这间隔小于需要接收的时间1500毫秒,所以当2和3 到来后,之前的操作被取消了。
      // 3和4 之间的间隔够长能够等待执行完毕,4是最后一个值也能执行
      
  • Collection相关的末端操作符

    • toList

      将释放的值转换成List

      flow {
          emit(1)
          delay(1000)
          emit(2)
          delay(1000)
          emit(3)
          delay(2000)
          emit(4)
      }
      
      println(flow1.toList())
      
      // 结果:[1, 2, 3, 4]
      
    • toSet

      将释放的值转换成Set

      flow {
          emit(1)
          delay(1000)
          emit(2)
          delay(1000)
          emit(3)
          delay(2000)
          emit(4)
      }
      
      println(flow1.toSet())
      
      // 结果:[1, 2, 3, 4]
      
      
  • Count相关的末端操作符

    • count

      1.计算释放值的个数

        val flow1 = flow {
            emit(1)
            delay(1000)
            emit(2)
            delay(1000)
            emit(3)
            delay(2000)
            emit(4)
        }
        
        println(flow1.count())
                
        // 结果:4
        
        2.计算满足某一条件的释放值的个数
        val flow1 = flow {
            emit(1)
            delay(1000)
            emit(2)
            delay(1000)
            emit(3)
            delay(2000)
            emit(4)
        }
        
        println(flow1.count { it % 2 == 0 })
                
        // 结果:2
        // 解释:
        // 偶数有2个值 2 4
        ```
        
        
        
        
    
    
  • Reduce相关的末端操作符

    • reduce

      和runningReduce类似,但是只计算最后的结果。

      val flow1 = flow {
          emit(1)
          emit(2)
          emit(3)
          emit(4)
      }
      println(flow1.reduce { acc, value -> acc * value })
      
      // 结果:24
      // 解释:计算最后的结果,1 * 2 * 3 * 4 = 24
      
    • fold

      和scan类似,有一个初始值,但是只计算最后的结果。

      val flow1 = flow {
          emit(1)
          emit(2)
          emit(3)
          emit(4)
      }
      println(flow1.fold(100) { acc, value -> acc * value })
      
      // 结果:2400
      // 解释:计算最后的结果,100 * 1 * 2 * 3 * 4 = 2400
      
    • single

      只接收一个值的Flow 注意:多于1个或者没有值都会报错

       val flow1 = flow {
          emit(1)
      }
      println(flow1.single())
      
      // 结果:1
      
    • singleOrNull

      接收一个值的Flow或者一个空值的Flow

    • first/firstOrNull

      1. 接收释放的第一个值/接收第一个值或者空值
        val flow1 = flow {
            emit(1)
            emit(2)
            emit(3)
            emit(4)
        }
        println(flow1.first())
        
        // 结果:1
        ```
    
        2. 接收第一个满足某个条件的值
        
        val flow1 = flow {
            emit(1)
            emit(2)
            emit(3)
            emit(4)
        }
        println(flow1.first { it % 2 == 0})
        
        // 结果:2
        ```
        
        
    

Flow的错误异常处理

  • 可以通过 try catch 捕获错误异常

    try {
        flow {
           for (i in 1..3) {
            emit(i)
         }
        }.collect {
            println("接收值 $it")
            check(it <= 1) { "$it 大于1"  }
        }
    } catch (e: Throwable) {
        println("收到了异常: $e")
    }
    
    // 结果:
    // I/System.out: 接收值 1
    // I/System.out: 接收值 2
    // I/System.out: 收到了异常: java.lang.IllegalStateException: 2 大于1
    
    // 解释:
    // 收到2的时候就抛出了异常,让后flow被取消,异常被捕获
    
  • 通过catch函数

    catch函数能够捕获之前产生的异常,之后的异常无法捕获。

    flow {
         for (i in 1..3) {
            emit(i)
         }
        }.map {
            check(it <= 1) { "$it 大于1" }
            it
        }
        .catch { e -> println("Caught $e") }
        .collect()
    
    // 结果:
    // Caught java.lang.IllegalStateException: 2 大于1
    

Flow的取消

  • CoroutineScope.cancel

    GlobalScope.launch {
        val flow1 = flow {
            for(i in 1..4){
              emit(i)
            }
        }
        flow1.collect { value ->
            println("$value")
            if (value >= 3) {
                cancel()
            }
        }
    }
            
     // 结果:1 2 3       
    
  • 流取消检测

    在协程处于繁忙循环的情况下,必须明确检测是否取消。 可以添加 .onEach { currentCoroutineContext().ensureActive() }, 但是这里提供了一个现成的 cancellable 操作符来执行此操作:

    (1..5).asFlow().cancellable().collect { value -> 
            if (value == 3) cancel()  
            println(value)
        }