- 场景:List 是同步一次性返回多个值,Sequence 是同步(阻塞)分开返回多个值,Suspend 是异步返回单个值,Flow可以异步返回多个值。
- 冷流:只能在创建的时候定义生产数据的代码,无法在外部通过实例更新数据。创建和中间操作只是对上一步的包装并不会执行,所以是响应式编程(也叫声明式),因此可以调用挂起函数自身却不是suspend修饰,只有消费的时候才真正生产和操作数据,所以消费都是suspend修饰得在携程中调用。
- 一些名词:构建→上游(前面的流)→当前操作→下游(后面的流)→消费。
- 有序:元素遵循先进先出原则,是一个接一个操作完,而不是统一操作再进行下一步。
- 协作取消:消费的取消只能在可取消的挂起函数挂起的时候取消(即跟随协程取消)。
- 切换线程:默认生产和消费在同一协程上下文中,消费所在的协程决定了生产所在的线程。对于生产可以通过 flowOn 改变上流执行线程而不会影响下流。对于消费可通过 launchIn 改变流执行的线程(不会影响生产中的flowOn)。
- 背压:当数据的消费速度赶不上生产速度。默认情况下生产一个消费一个是同步交替进行的不存在背压,除非使用flowOn切换线程。由于消费操作是挂起函数(协程能轻松切换线程),挂起恢复的特性会阻塞数据生产的速度不会对下流产生背压。
- 防抖:新值的内容发生变化才会消费,相同会被忽略。
Flow | Channel | SharedFlow | |
类型 | 冷流(数据只能在流内生产) | 热流(数据在流外生产然后传递给流) | |
数据产生 | 消费时才会生产数据。 | 不消费也会生产数据,旧值可以缓存在内存中。 | |
关系 | 与订阅者是一对一关系(多个订阅者彼此之间独立,数据是完整重新发送)。 |
与订阅者是一对多关系(多个订阅者轮流接收,收到的不是同一个值)。 |
与订阅者是一对多关系(多个订阅者同时接收,收到的是同一个值)。 |
关闭 | 流会自动关闭(停止订阅或者数据数据生产完)。 | 构造创建的不会自动关闭,构建器创建的会跟随协程关闭。 | 构造创建的不会自动关闭,转换的启动模式配置为WhileSubscribed会超时关闭。 |
初始值 | 无 | 有 |
用来生产数据,发送值到流中。
创建Flow的基本方法,需要手动调用 emit 发射单个值,调用 emitAll 发射另一个Flow中的值。emit 不是线程安全的,不应该并发调用(可考虑
channelFlow),不应该在内部调用withContext切换线程(可考虑flowOn操作符)。
public fun |
val flow = flow {
emit(5)
for (i in 1..3) {
emit(i)
}
emitAll(flowOf(1, 2, 3))
}
快速创建固定值集的Flow(类似 listOf() )。
public fun for (element in elements) { emit(element) } } |
val flow = flowOf(1, 2, 3)
遍历其它容器(Array、Range、Sequence、Iterable、Iterator)将元素发送到流中。也可以将普通函数或挂起函数返回的值发送到流中。
public fun Array.asFlow(): Flow public fun XXArray.asFlow(): Flow public fun XXRange.asFlow(): Flow public fun public fun public fun public fun public fun |
val flow = listOf(1, 2, 3).asFlow()
将回调API改造成Flow。底层使用的sendChannel,默认容量64满了会挂起直到消费出空位,为了避免生产被挂起可以配置为CONFLATED或者UNLIMITED。
public fun |
send () | 发送数据。 |
offer () | 允许在协程外提交。 |
sendBlocking () | 尝试用offer,失败则用runBlocking{ send() }阻塞式提交。 |
awaitClose () | Flow关闭时执行,用来释放资源(注销回调函数),未调用报错 IllegalStateException。 |
fun showWithFlow(): Flow = callbackFlow {
//1.创建Flow并发送值(实现Callback接口)
val callback = object: NetCallback {
override fun onNextValue(value: Int) {
try {
offer(num)
} catch(t: Throwable) {...}
}
override fun onError(ecxeption: Throwable) {
cancel(CancellationException("API发生错误", exception))
}
override fun onCompleted() = close()
}
//2.注册回调(传参使用,并对API进行配置操作)
getData(callback)
//3.取消协程并注销回调(用来释放API资源)
awaitClose {...}
}
返回一个空的Flow。
public fun |
val flow = emptyFlow()
支持缓冲通道,线程安全,允许不同的CorotineContext发送事件。生产和消费可以异步进行,不像flow默认生产一个消费一个是同步交替进行(使用缓存操作符就不会了)。
public fun |
val flow = channelFlow { }
执行一些操作,不会立即执行,返回的还是Flow。
onEach | public fun |
filter | public inline fun 保留符合条件的值。 |
filterNot | public inline fun 保留不符合条件的值。 |
filterNotNull | public fun 保留不为null的值。 |
filterIsInstance | public inline fun 保留对应类型的值(类型填到泛型里面)。 |
生产默认执行在消费处所在的协程指定的线程上,生产一个消费一个是同步的。指定线程后,生产和消费是异步进行。
flowOn | public fun flowOn使上游执行在指定的线程上,不会影响下游。上游还有 flowOn 的时候,只影响他们之间的那些操作。下游默认还是执行在消费所在的线程。 |
数据的消费速度赶不上生产速度。默认情况下生产一个消费一个是同步交替进行的不存在背压,emit和collect都是挂起函数会等待对方准备好,最终耗时是每个值消耗的时间总和。buffer使生产和消费并发运行提高效率,设置缓冲区大小(从0开始算的),消费来不及就先把值缓存起来等待着。对于缓存区满了有三种策略:
- BufferOverflow.SUSPEND模式:默认模式,缓冲区满了就和默认情况一样挂起(等待消费),消费一个再生产一个。
- BufferOverflow.DROP_OLDEST模式:缓冲区满了就丢弃还没消费掉的旧值,存入新值。
- BufferOverflow.DROP_LATEST模式:缓冲区满了就丢弃后来生产的新值,直到有空位了再往里存又生产的值(不是丢弃掉的那些)。
buffer | public fun 参数capacity是缓冲区大小,参数onBufferOverflow是对缓冲区满后新值的处理模式。 |
conflate | public fun 会丢弃中间值,只消费首尾值。相当于 buffer(0 , BufferOverflow.DROP_OLDEST)。 |
map 和 filter 底层就是使用的 transform。transform 能 emit 任意次数,flow 中每个值都会循环一遍 transform 中的 emit,而 map 只能对值转换。
transform transformLatest transformWhile |
public inline fun 可以 emit 任意次数,flow 中每个值都会循环一遍 transform 中的 emit。 |
public fun 只对最后一个值消费。 |
|
public fun 为true继续消费,为false后续值都丢弃。 |
|
map mapLatest mapNotNull |
public inline fun 对值进行转换后再emit。 |
public fun 只对最后一个值转换,会丢弃其它值。 |
|
public inline fun 转换会丢弃null值。 |
val flow = (1..3).asFlow()
//map只能转换元素
flow.map { "[$it-a]" }.collect { print(it) } //打印:[1-a][2-a][3-a]
//mapNotNull不处理null值
flow{
emit(1)
emit(2)
emit(null)
emit(3)
}.mapNotNull { it?.plus(1) }.collect { print("$it,") } //打印:2,3,4
//flow中每个值都会循环一遍transform中的emit
flow.transform {
emit("[$it-a]")
emit("[$it-b]")
}.collect { print(it) } //打印:[1-a][1-b][2-a][2-b][3-a][3-b]
//transform实现filter和map
flow.transform {
emit(it + 1)
}.transform {
if (it % 2 == 0) emit(it)
}.collect { print("$it,") } //打印:2,4
flow.transformLatest {
delay(1000) //此处不延迟的话会每个值都处理而不是只处理最后一个
emit(it*10)
}.collect { print("$it,") } //打印:20
flow.transformWhile {
emit(it)
it != 3
}.collect { print("$it,") } //打印:1,2
take takeWhile |
public fun 获取几个值,丢弃剩下的。 |
public fun 满足条件就获取值,只要碰到不满足条件的值就丢弃剩下全部值(即便剩下的里面有满足条件的),第一个值就不满足会全部丢弃。 |
|
drop dropWhile |
public fun 丢弃几个值,获取剩下的。 |
public fun 满足条件就丢弃值,只要碰到不满足条件的值就获取剩下全部值(即便剩下的里面有满足条件的),第一个值就不满足会全部获取。 |
val flow = (1..5).asFlow()
flow.take(3).collect { print(",$it") } //打印:1,2,3
flow.takeWhile { it > 3 }.collect { print(",$it") } //打印:无内容
flow.drop(3).collect { print(",$it") } //打印:4,5
flow.dropWhile { it == 3 }.collect { print(",$it") } //打印:1,2,3,4,5
merge | public fun 将两个Flow中的值连接起来。 |
zip | public fun 将两个Flow中同索引的值根据条件合并成一个值,最终值个数为短的的那个Flow(长的那个Flow多的值被丢弃)。 |
combine | public fun 将两个Flow中同索引的值根据条件合并成一个值,短的那个Flow最后一个值重复跟长的那个Flow剩下的值合并。 |
val flow1 = flowOf(1, 2, 3, 4, 5)
val flow2 = flowOf('a', 'b', 'c')
merge(flow1, flow2).collect { print(",$it") } //打印:1,2,3,4,5,a,b,c
merge(flow2, flow1).collect { print(",$it") } //打印:a,b,c,1,2,3,4,5
flow1.zip(flow2) { a, b -> "[$a$b]" }.collect { print(it) } //打印:[1a][2b][3c]
flow2.zip(flow1) { a, b -> "[$a$b]" }.collect { print(it) } //打印:[a1][b2][c3]
flow1.combine(flow2) { a, b -> "[$a$b]" }.collect { print(it) } //打印:[1a][2b][3c][4c][5c]
flow2.combine(flow1) { a, b -> "[$a$b]" }.collect { print(it) } //打印:[a1][b2][c3][c4][c5]
flattenConcat flattenMerge |
public fun 将Flow中的多维值都展平然后全部连接起来。 |
public fun 将Flow中的多维值都展平然后全部连接起来,可以设置并发数。 |
val flow1 = flowOf(1, 2, 3, 4, 5)
val flow2 = flowOf('a', 'b', 'c', 'd', 'e', 'f')
val flow3 = flowOf(flow1, flow2)
flow3.flattenMerge(2).collect { print(",$it") } //打印:1,2,3,4,5,a,b,c,d,e,f
flow3.flattenConcat().collect { print(",$it") } //打印:1,2,3,4,5,a,b,c,d,e,f
flatMapContact flatMapMerge flatMapLatest |
public fun 流中流,外流值执行完内流操作,才轮到下一个外流值。 |
public fun 流中流,内流值执行完外流操作,才轮到下一个内流值。 |
|
public inline fun 流中流,外流发射新值,上一个值没操作完就会被取消。 |
val flow = (1..3).asFlow()
flow.flatMapConcat {
flow {
emit("$it:前")
delay(500)
emit("$it:后")
}
}.collect { print("[$it]") } //打印:[1:前][1:后][2:前][2:后][3:前][3:后]
println()
flow.flatMapMerge {
flow {
emit("$it:前")
delay(500)
emit("$it:后")
}
}.collect { print("[$it]") } //打印:[1:前][2:前][3:前][1:后][2:后][3:后]
println()
flow.flatMapLatest {
flow {
emit("$it:前")
delay(500)
emit("$it:后")
}
}.collect { print("[$it]") } //打印:[1:前][2:前][3:前][3:后]
注意:链式调用中出现多个onStart { action }时,后出现的 action 会先执行,因为后续 onStart 构建的下游流包在了上游 onStart 的外面,并且 action 会在收集上游流数据之前执行。而这个结论却不能沿用到onCompletion { action },虽然 onCompletion 构建的下游流也包裹在上游 onCompletion 外面,但是 action 总是在收集上游流之后执行。
onStart onEmpty |
public fun 在数据生产之前调用。(可以用来做耗时操作之前的操作,例如UI展示进度条) |
public fun 在数据消费完后(参数case = null)或者出现异常时(case != null)调用。只能判断有没有发生异常,不能捕获异常。(可以用来做耗时操作结束后的操作,例如UI隐藏进度条) |
|
public fun 在flow完成却未发送任何值时调用。(可以用来发送默认值) |
retry | 当上游发生异常时可以重新执行几次。retryWhen 的简化版。 |
retryWhen | 有条件的进行重试 ,lambda 中有两个参数: 一个是 异常原因,一个是当前重试的 index (从0开始)。lambda 的返回值 为 Boolean ,true则继续重试 ,false 则结束重试。 |
(1..3).asFlow().onEach{
if(it == 3) throw Exception("发生异常")
delay(100)
println("生产:$it")
}.retry(2){
it.message = "有异常"
}.catch{ ex ->
println("捕获异常:${ex.massage}") //用it试试
}.collect{
println("消费:$it")
}
scan | public fun 会把初始值和每一步的操作结果发送出去。 |
(1..3).asFlow()
.onEach { delay(200) }
.scan(1) { acc, v -> acc + v }
.collect { print("$it,") } //打印:1,2,4,7
distinctUntilChanged distinctUntilChangedBy |
public fun 过滤用,下面一个的简化版本。连续两个值一样,则跳过发送。 |
public fun 去重操作符,判断连续的两个值是否重复,可以选择是否丢弃重复值。 |
debounce | public fun 在指定时间内只发送最新的值。 |
sample | public fun 周期性的在指定时间内只发送最新的值。 |
生命周期低于目标状态会取消上游,不影响下游,需要注意调用顺序。
flowWithLifecycle | public fun |
最终操作,会触发流的执行,返回的是结果。
collect | public suspend fun collect(collector: FlowCollector 收集Flow中的值。 |
collectIndexed | public suspend inline fun |
conflateLast | public suspend fun 只收集最后一个值。 |
launchIn | public fun 在指定的其它协程作用域中消费,而不是当前协程中。指定该协程作用域的上下文也就相当于切换了线程,记得接着调用join()。 |
flowOf(1,2,3)
.launchIn(CoroutineScope(Dispatchers.IO)) //相当于切换了线程
.join()
reduce |
public suspend fun 对值挨个进行指定运算返回最终结果(1和2计算后,结果和3运算,结果再和4运算...返回最终结果)。 |
fold | public suspend inline fun 带初始值,对值挨个进行指定运算返回最终结果(初始值和1计算后,结果和2计算,结果再和4运算...返回最终结果)。 |
runningFold | 区别于 fold ,就是返回一个新流,将每步的结果发射出去。 |
runningReduce | 区别于 reduce ,就是返回一个新流,将每步的结果发射出去。 |
val flow = (1..5).asFlow()
println(flow.reduce({ a, b -> a + b })) //打印:15
println(flow.fold(2, { a, b -> a + b })) //打印:17
toCollection toList toSet |
将结果转到到集合。 |
将结果转换为List。 | |
将结果转换为Set。 | |
asLiveData | public fun 将结果转到LiveData。 |
asStateFlow | 转为StateFlow。 |
asShareFlow | 转为SharedFlow。 |
last lastOrNull first firstOrNull single singleOrNull |
获取最后一个值,为null会抛异常 NoSuchElementException。 |
获取最后一个值,可以为null。 | |
获取第一个值,如果为空会抛异常 NoSuchElementException。 | |
获取第一个元值,可以为null。 | |
接收流发送的第一个值 ,区别于first,如果为空或者发了不止一个值,则都会报错。 | |
接收流发送的第一个值 ,可以为努力了,发出多值的话除第一个,后面均被置为null。 | |
count | 返回流发送值的个数。类似list.size,注:sharedFlow无效(无意义) |
Flow在协程中消费,因此协程的取消会一并取消Flow。Flow{ }形式的创建对 emit() 操作附加了 ensureActive() 以检测协程的取消,但出于性能原因其它构建形式和操作不会自行检查以响应,因此需要手动调用 cancellable() 检查。
cancellable( ) | public fun |
val flow = flow { repeat(5) { emit(it) } }
withTimeout(2500) { flow.collect { println(it) } }
//该构建方式会对发射值附加取消检测
flow {
repeat(5) { emit(it) }
}.collect {
if (it == 3) cancel()
}
//其它构建方式需要手动检查
(1..5).asFlow().cancellable().collect {
println(it)
if (it == 3) cancel()
}
在flow构建器或处理值的时候可能发生异常,异常会向下流动关闭每个处理步骤,如果没有调用catch,未捕获的异常会立即取消该flow,在消费时重新抛出。可以在flow内部使用 try-finally 语句,也可以在flow外部使用 try-catch 语句。在Android开发中 catch() 可以用来展示错误信息或者显示默认数据(例如空列表)。
catch | public fun 只能捕获上游的异常,上游还有catch那就是它俩捕获之间的,捕获后下游不会再捕获得到。可以在代码块中使用 throw 再次抛出、可以使用 emit 转换为值发射、或其他处理。 |
flow {
emit(1)
emit(2)
throw NullPointerException()
}.map {
it +1
}.catch {
println("catch:$it")
}.collect {
println("collect:$it")
}
//打印:
//collect:2
//collect:3
//catch:java.lang.NullPointerException
Flow | Suspend | |
作用 | 连续性的异步数据流(多个值) | 一次性的异步任务(单个值) |
场景 | 实时数据频繁刷新(点赞数、) | 无需更新数据(文章内容) |
fun updateNews() {
flow{}
.onStart { showProgressBar() }
.onCompletion { hideProgressBar() }
.onEach { view.show(it) }
.catch { view.handleError(it) }
.launchIn(viewModelScope)
}
回调
onSubion | SharedFlow 专属操作符 (StateFlow是SharedFlow 的一种特殊实现)。 |
变换
receiveAsFlow consumeAsFlow |
将Channel 转换为Flow ,可以有多个观察者,但不是多播,可能会轮流收到值。 |
将Channel 转换为Flow ,但不能多个观察者(会crash)! | |
withIndex | 将结果包装成IndexedValue类型。 |
produceIn | 转换为ReceiveChannel , 不常用。 |
组合
conbineTransform | 顾名思义 combine+ transform |