概述
可以利用中间运算符在不使用值的情况下修改数据流。中间运算符可以接连应用,形成链式运算,在数据项被发送到数据流时延迟执行。
其中操作符还分为三大类:
- 创建操作符
- 中间操作符
- 末端操作符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m20UOOOF-1670402776711)(/Users/apple/Downloads/job_files/flow/Xnip2022-11-20_15-33-21.jpg )]
创建操作符
flow
val f = flow {
emit(1)
emitAll(flowOf(1, 2, 3))
}
// output
1
1
2
3
创建 Flow
的基本方法. 使用 emit
发射单个值使用 emitAll
发射一个流 ,类似 list.addAll(list)
flowOf
快速创建 flow
, 类似 listOf()
val f2 = flowOf("A", "B", "C")
// output
A
B
C
asFlow
将其他数据转换成 普通的flow
,一般是集合向Flow
的转换
kotlin 标准库中提供了基本数据类型相关扩展方法,其中 Map
类型不能直接使用asFlow()
需要转换成其支持的类型,再使用 asFlow()
.
fun testAsFlow3() {
// Range 类型
val flow1 = IntRange(1, 100).asFlow()
val flow2 = LongRange(1L, 1000L).asFlow()
// Array 类型
val flow3 = intArrayOf(1, 2, 3).asFlow()
val flow4 = longArrayOf(1L, 2L, 3L).asFlow()
val flow5 = arrayOf("A", "B", "C").asFlow()
// Sequence 类型
val flow6 = sequenceOf("X", "Y", "Z").asFlow()
// Iterable 类型
val flow7 = listOf(1, 2, 3).asFlow()
val flow8 = setOf("E", "G", "C").asFlow()
// Iterator 类型
val flow9 = iterator<Int> {}.asFlow()
// Map 类型
val map = hashMapOf(Pair("A", 1), Pair("B", 2))
val flow10 = map.asIterable().asFlow()
val flow11 = map.asSequence().asFlow()
val flow12 = map.iterator().asFlow()
}
callbackFlow
将回调方法改造成 flow
, 类似 suspendCoroutine
例如我们有以下,利用 RequestManager
和 RequestCallBack
模拟数据加载情况,将回调改造成支持 Flow
interface RequestCallBack {
fun onStart()
fun onSuccessFul(value: Int)
fun onError(e: Throwable)
fun onCompleted()
}
object RequestManager {
private val callBacks = mutableListOf<RequestCallBack>()
fun addRequestCallBack(callBack: RequestCallBack) {
callBacks.add(callBack)
}
fun removeRequestCallBack(callBack: RequestCallBack) {
callBacks.remove(callBack)
}
fun request() {
callBacks.forEach {
it.onStart()
}
try {
val value = Random.nextInt()
if (value % 2 == 0) {
throw IllegalStateException("value % 2 != 0")
}
callBacks.forEach {
it.onSuccessFul(value)
}
} catch (e: Exception) {
callBacks.forEach {
it.onError(e)
}
}
callBacks.forEach {
it.onCompleted()
}
}
}
利用callbackFlow
函数,改造如下:
fun testCallBackFlow(): Flow<Int> = callbackFlow {
val callback = object : RequestCallBack {
override fun onStart() {
}
override fun onSuccessFul(value: Int) {
trySend(value) // 将数据发送到 flow中
}
override fun onError(e: Throwable) {
close(e) // 抛出异常关闭
close() // 正常关闭, 不抛出异常
}
override fun onCompleted() {
}
}
RequestManager.addRequestCallBack(callback)
RequestManager.request()
// 关闭之后回调
awaitClose {
RequestManager.removeRequestCallBack(callback)
}
}
- 调用
send()
或trySend()
将数据发送至Flow
中 - 在回调结束调用
close()
关闭管道、其中close()
传入异常将末端收集不到出错前数据,如果不传则可以正常收集到. awitClose
扩展方法在Flow
管道close()
之后调用,可以在此处做数据回收等工作。
与 flow
构建器不同,callbackFlow
允许通过 send
函数从不同 CoroutineContext
发出值
在协程内部,callbackFlow
会使用通道,它在概念上与阻塞队列非常相似。通道都有容量配置,限定了可缓冲元素数的上限。在 callbackFlow
中所创建通道的默认容量为 64 个元素。当您尝试向完整通道添加新元素时,send
会将数据提供方挂起,直到新元素有空间为止.
emptyFlow
val f3 = emptyFlow<Int>()
返回一个空流。
channelFlow
在一般的flow
在构造代码块中不允许切换线程,ChannelFlow
则允许内部切换线程
val f4 = channelFlow<Int> {
send(1)
withContext(Dispatchers.IO) {
send(2)
}
withContext(Dispatchers.Default) {
send(3)
}
}
// output
1
2
3
变换操作符
map
将发出的值 进行变换,lambda
的返回值为最终发送的值
val flowMap = flow {
emit(true)
}.map {
return@map it.toString()
}
mapLatest
转换原始流数据,当原始流发出新值时,将取消对先前值的转换块的计算。
val flowMapLatest = flow {
emit("a")
delay(100)
emit("b")
}.mapLatest { value ->
println("Started computing $value")
delay(200)
"Computed $value"
}
// output
Started computing a
Started computing b
Computed b
mapNotNull
对数据流控制,仅发送 map
后不为空的值
val flowMapNoNull = flow {
emitAll(flowOf("A", "B"))
}.mapNotNull {
if (it == "B") {
it
} else {
null
}
}
// output
B
transform
对发出的值 进行变换 。区别于map
, transform
的接收者是FlowCollector
,因此它非常灵活,可以变换、跳过它或多次发送。
val flowTransform = flow {
emitAll(flowOf("A", "B", "c", "d"))
}.transform { value ->
if (value == "A" || value == "B") {
emit("transform :${value.lowercase()}")
} else {
emit("value ${value.uppercase()}")
}
}
// output
transform :a
transform :b
value C
value D
transformLatest
类比 mapLatest
,当有新值发送时如果上个变换还没结束,会先取消掉
val flowTransformLatest = flow {
emit("a")
delay(100)
emit("b")
}.transformLatest { value ->
emit(value)
delay(200)
emit(value + "_last")
}
// output
a
b
b_last
transformWhile
将转换函数应用于给定流的每个值,同时此函数返回 true
。 transformWhile
的接收者是 FlowCollector
,因此 transformWhile
是一个灵活的函数,可以转换发射的元素、跳过它或多次发射它。此运算符概括了 takeWhile
并可用作其他运算符的构建块。例如,下载进度消息流可以在下载完成时完成,但会发出最后一条消息(与 takeWhile 不同)
val flowTransformWhite = flow {
for (i in 1..4) {
emit(i)
}
}.transformWhile { value ->
if (value > 2) {
false
} else {
emit("value $value")
true
}
}
// output
value 1
value 2
asStateFlow
将 MutableStateFlow
转换为 StateFlow
,就是变成不可变的。常用在对外暴露属性时使用
private val _flowAsStateFlow = MutableStateFlow(UIState.INITIAL)
val flowAsStateFlow = _flowAsStateFlow.asStateFlow()
asSharedFlow
将 MutableSharedFlow
转换为 SharedFlow
,就是变成不可变的。常用在对外暴露属性时使用
private val _asSharedFlow = MutableSharedFlow<UIState>()
val asSharedFlow = _asSharedFlow.asSharedFlow()
receiveAsFlow
将Channel
转换为Flow
,可以有多个观察者,但不是多播,可能会轮流收到值。
private val _receiveAsFlow = Channel<UIState>()
val receiveAsFlow = _receiveAsFlow.receiveAsFlow()
consumeAsFlow
将Channel
转换为Flow
,但不能多个观察者(会crash)
private val _consumeAsFlow = Channel<UIState>()
val consumeAsFlow = _consumeAsFlow.consumeAsFlow()
withIndex
将结果包装成IndexedValue
类型
public data class IndexedValue<out T>(public val index: Int, public val value: T)
val withIndex = flow {
emit("A")
emit("B")
}.withIndex()
// output
IndexedValue(index=0, value=A)
IndexedValue(index=1, value=B)
scan
和 fold
相似,区别是fold
返回的是最终结果,scan
返回的是个flow
,会把初始值和每一步的操作结果发送出去。
val scan = flowOf(1, 2, 3)
.scan(emptyList<Int>()) { acc, value ->
acc + value
}
// output
[]
[1]
[1, 2]
[1, 2, 3]
produceIn
转换为 ReceiveChannel
, 不常用。
注: Channel 内部有 ReceiveChannel 和 SendChannel之分,看名字就是一个发送,一个接收。
val produceIn = flowOf(1, 2, 3).produceIn(viewModelScope)
// output
1
2
3
runningFold
区别于 fold
,就是返回一个新流,将每步的结果发射出去。
其实上述 scan
操作符内部也是调用的 runningFold
val runningFold = flowOf(1, 2, 3)
.runningFold(emptyList<Int>()) { acc, value ->
acc + value
}
// output
[]
[1]
[1, 2]
[1, 2, 3]
runningReduce
区别于 reduce
,就是返回一个新流,将每步的结果发射出去。
val runningReduce = flowOf(1, 2, 3, 4)
.runningReduce { acc, value ->
acc + value
}
// output
1
3
6
10
过滤操作符
filter
筛选出符合条件的值
val filter = flowOf("A", "B", "C")
.filter {
it != "B"
}
// output
A
C
filterInstance
筛选对应类型的值
val filterIsInstance = flowOf("A", 1, false)
.filterIsInstance<String>()
// output
A
filterNot
筛选不符合条件相反的值,相当于filter
取反
val filterNot = flowOf("A", "B", "C")
.filterNot {
it == "A"
}
// output
B
C
filterNotNull
筛选不为空的值
val filterNotNull = flowOf("A", null, "C")
.filterNotNull()
// output
A
C
drop
作用是 丢弃掉前 n
个的值 , 入参count
为int
类型
val drop = flowOf("A", "B", "C")
.drop(2)
// output
C
dropWhile
这个操作符有点特别,和 filter
不同! 它是找到第一个不满足条件的,返回其和其之后的值。
如果首项就不满足条件,则是全部返回。
val dropWhile = flowOf("A", "B", "C")
.dropWhile {
it != "B"
}
// output
take
返回前 n 个 元素
val take = flowOf("A", "B", "C")
.take(2)
// output
A
B
takeWhile
也是找第一个不满足条件的项,但是取其之前的值 ,和dropWhile
相反。
如果第一项就不满足,则为空流
// ①
val takeWhile = flowOf("A", "B", "C")
.takeWhile {
it != "B"
}
// output
A
// ②
val takeWhile2 = flowOf("A", "B", "C")
.takeWhile {
it == "B"
}.onEmpty {
emit("empty")
}
// output
empty
debounce
防抖节流 ,指定时间内的值只接收最新的一个,其他的过滤掉。
val debounce = flow {
emit(1)
delay(90)
emit(2)
delay(90)
emit(3)
delay(1010)
emit(4)
delay(1010)
emit(5)
}.debounce(1000)
// output
3
4
5
sample
采样. 给定一个时间周期,仅获取周期内最新发出的值
val sample = flow {
repeat(10) {
emit(it)
delay(110)
}
}.sample(200)
// output
1
3
5
7
9
distinctUntilChangedBy
去重操作符,判断连续的两个值是否重复,可以选择是否丢弃重复值。
val distinctUntilChangedBy = flow {
emit(User(1, "Tom"))
emit(User(3, "Tom"))
emit(User(3, "Tom"))
emit(User(4, "Jan"))
}.distinctUntilChangedBy {
it.name
}
// output
User(id=1, name=Tom)
User(id=4, name=Jan)
distinctUntilChanged
过滤用,distinctUntilChangedBy
的简化调用 。连续两个值一样,则跳过发送
val distinctUntilChanged = flow {
emit(User(1, "Tom"))
emit(User(1, "Tom"))
emit(User(4, "Jan"))
emit(User(3, "Tom"))
}.distinctUntilChanged()
// output
User(id=1, name=Tom)
User(id=4, name=Jan)
User(id=3, name=Tom)
组合操作符
combine
组合每个流最新发出的值。
private val combine1 = flowOf(1, 2)
.onEach {
delay(10)
}
private val combine2 = flowOf("A", "B", "C")
.onEach {
delay(15)
}
val combine = combine1.combine(combine2) { i: Int, s: String ->
i.toString() + s
}
// output
1A
2A
2B
2C
combineTransform
合并两个流,相当于 combine
+ transform
private val combineTransform1 = flowOf(1, 2)
private val combineTransform2 = flowOf("A", "B", "C")
val combineTransform = combineTransform1.combineTransform(combineTransform2) { number, text ->
emit("$number : $text")
}
// output
1 : A
2 : A
2 : B
2 : C
merge
合并多个流成 一个流。
private val merge1 = flowOf(1, 2).onEach {
delay(10)
}
private val merge2 = flowOf("A", "B", "C").onEach {
delay(15)
}
val merge = merge(merge1, merge2).map {
it.toString()
// output
1
A
2
B
C
flattenConcat
以顺序方式将给定的流展开为单个流 ,是Flow<Flow<T>>
的扩展函数
val flattenConcat = flow {
emit(flowOf(1, 2))
emit(flowOf(3, 4))
}.flattenConcat()
// output
1
2
3
4
flattenMerge
作用和 flattenConcat
一样,但是可以设置并发收集流的数量。
参数 concurrency
: Int
,当其 == 1时,效果和 flattenConcat
一样,大于 1 时,则是并发收集。
val flattenMerge = flow {
emit(flowOf(1, 2).flowOn(Dispatchers.IO))
emit(flowOf(3, 4).flowOn(Dispatchers.Default))
emit(flowOf(5, 6))
}.flattenMerge(2)
// output
3
4
5
6
1
2
flatMapContact
这是一个组合操作符,相当于 map
+ flattenConcat
, 通过 map
转成一个流,在通过 flattenConcat
展开合并成一个流
val flattenMapConcat = flowOf(1, 2, 3)
.flatMapConcat {
flowOf("$it : map")
}
// output
1 : map
2 : map
3 : map
flatMapLatest
和其他 带 Latest
的操作符 一样,如果下个值来了,上变换还没结束,就取消掉。内部实现: transformLatest
+ emitAll
val flattenMapLatest = flow {
emit("a")
delay(100)
emit("b")
}.flatMapLatest { value ->
flow {
emit(value)
delay(200)
emit(value + "_last")
}
}
// output
a
b
b_last
flatMapMerge
也是组合操作符,简化使用。 map
+ flattenMerge
。 因此也是有
concurrency
这样一个参数,来限制并发数。
可以并发处理数据,效率更高,所以不保证顺序
val flatMapMerge = flowOf(300, 200, 100)
.flatMapMerge {
flow {
delay(it.toLong())
emit("A:$it")
emit("B:$it")
}
}
// output
A:100
B:100
A:200
B:200
A:300
B:300
zip
对两个流进行组合,分别从二者取值,一旦一个流结束了,那整个过程就结束了。
并行处理。
private val flowZip1 = flowOf(1, 2, 3)
private val flowZip2 = flowOf("A", "B")
val flowZip = flowZip1.zip(flowZip2) { t1, t2 ->
t1.toString() + t2
}
// output
1A
2B
功能性操作符
接收的的时候判断 协程是否被取消 ,如果已取消,则抛出异常
cancellable
val job= flowOf(1,3,5,7).cancellable().onEach { value->
print(value)
} .launchIn(lifecycleScope)
//取消
job.cancel()
catch
对上游异常进行捕获 ,对下游无影响
上游 指的是 此操作符之前的流 , 下游 指的是此操作符之后的流
val catch = flow {
emit("A")
throw IllegalStateException()
}.catch {
if (it is IllegalStateException) {
emit("error")
}
}
// output
A
error
retryWhen
有条件的进行重试 ,lambda
中有两个参数: 一个是 异常原因,一个是当前重试的 index
(从0开始).
lambda
的返回值 为 Boolean
,true
则继续重试 ,false
则结束重试
val retryWhen = flow {
emit("Request")
throw IOException("network error")
}.retryWhen { cause, attempt ->
if (attempt > 5) {
return@retryWhen false
}
cause is IOException
}.catch {
if (it is IOException) {
emit("Request Error")
}
}
// output
Request
//...7
Request Error
在重试超过5次之后会将异常抛出
retry
重试机制 ,当流发生异常时可以重新执行。retryWhen
的简化版。
retries: Long
= Long.MAX_VALUE 指定重试次数,以及控制是否继续重试.(默认为true)
val retry = flow {
emit("Request")
throw IOException("network error")
}.retry(2) {
it is IOException
}.catch {
emit("Request Error")
}
// output
Request
//...2
Request Error
buffer
buffer
用于解决流速不均匀,指的是 Flow
上游发送数据的速度 和 Flow
下游处理数据的速度不匹配,从而可能引发的一系列问题
我们先看没有使用 buffer
的例子:
flow {
emit(1)
delay(1000)
emit(2)
delay(1000)
emit(3)
}.onEach {
println("$it is ready")
}.collect {
delay(1000)
println("$it is complete")
}
// output
1 is ready
1 is complete
2 is ready
2 is complete
3 is ready
3 is complete
默认情况下,collect
函数和flow
函数会运行在同一个协程当中,因此collect
函数中的代码没有执行完,flow
函数中的代码也会被挂起等待。
也就是说,我们在collect函数中处理数据需要花费1秒,flow函数同样就要等待1秒。collect函数处理完成数据之后,flow函数恢复运行,发现又要等待1秒,这样2秒钟就过去了才能发送下一条数据。
flow {
emit(1)
delay(1000)
emit(2)
delay(1000)
emit(3)
}.onEach {
println("$it is ready")
}.buffer()
.collect {
delay(1000)
println("$it is complete")
}
// output
1 is ready
2 is ready
1 is complete
3 is ready
2 is complete
3 is complete
buffer
函数会让 flow
函数和collect
函数运行在不同的协程当中,这样flow
中的数据发送就不会受collect
函数的影响了。
conflate
仅保留最新值, 内部就是 buffer(CONFLATED)
val conflate = flow {
repeat(30) {
delay(100)
emit(it)
}
}.conflate()
.onEach {
delay(1000)
}
// output
0
9
19
29
输出结果不固定,仅保留最新的值
flowOn
指定上游操作的执行线程 。 想要切换执行线程 就用它!
flow.map { ... } // Will be executed in IO
. flowOn (Dispatchers.IO) // This one takes precedence
. collect{ ... }
回调操作符
onStart
在上游流开始之前被调用。 可以发出额外元素,也可以处理其他事情,比如发埋点
val onStart = flow {
emit("A")
}.onStart {
emit("onStart")
}
// output
onStart
A
onCompletion
在流取消或者结束时调用。可以执行发送元素,发埋点等操作
val onCompletion = flowOf("A", "B")
.onCompletion {
emit("Done")
}
// output
A
B
Done
onEach
在上游向下游发出元素之前调用
val onEach = flow {
emit("A")
emit("B")
}.onEach {
println("emit start: $it")
}
// output
emit start: A
A
emit start: B
B
onEmpty
当流完成却没有发出任何元素时回调。 可以用来兜底.。
val onEmpty = emptyFlow<Int>().onEmpty {
emit(1)
emit(2)
}
// output
1
2
onSubscription
SharedFlow
专属操作符 (StateFlow
是SharedFlow
的一种特殊实现)
在建立订阅之后 回调。 和 onStart
有些区别 ,SharedFlow
是热流,因此如果在onStart
里发送值,则下游可能接收不到。
val onSubscription = MutableSharedFlow<String>().onSubscription {
emit("subscription")
}
// output
subscription
末端操作符
collect
触发flow
的运行。通常的监听方式
launch{
flowOf(1, 2).collect{ value->
print(value)
}
}
// output
1
2
collectIndexed
带下标的 收集操作
flowOf("A", "B", "C")
.collectIndexed { index, value ->
println("$index , $value")
}
// output
0 , A
1 , B
2 , C
collectLatest
与 collect
的区别是 ,有新值发出时,如果此时上个收集尚未完成,则会取消掉上个值的收集操作
只想要最新的数据,中间值可以丢弃时可以使用此方式
flow {
emit(1)
delay(50)
emit(2)
}.collectLatest { value ->
println("Collecting $value")
delay(100) // Emulate work
println("$value collected")
}
// output
Collecting 1
Collecting 2
2 collected
toCollection
将结果添加到集合
val list = mutableListOf<Int>()
flow {
emit(1)
emit(2)
}.toCollection(list)
println(list)
// output
[1, 2]
toList
将结果转换为 List
flow {
emit("A")
emit("B")
}.toList().forEach { v ->
println(v)
}
toSet
将结果转换为 Set
val set: Set<Int> = flow {
emit(1)
emit(2)
}.toSet()
println(set)
launchIn
直接触发流的执行,不设置 action
,入参为coroutineScope
一般不会直接调用,会搭配别的操作符一起使用,如onEach
,onCompletion
。返回值是Job
val launchIn = flow {
emit("A")
}.onEach {
println(it)
}.onCompletion {
emit("onCompletion")
}.launchIn(viewModelScope)
// output
A
last
返回流 发出 的最后一个值 ,如果为空会抛异常
val v = flowOf("A", "B")
.last()
rintln(v)
// output
B
lastOrNull
返回流 发出 的最后一个值 ,可以为空
val v2 = emptyFlow<Int>()
.lastOrNull()
println(v2)
// output
null
first
返回流 发出 的第一个值 ,如果为空会抛异常
val v = flowOf("A", "B")
.first()
println(v)
// output
A
firstOrNull
返回流 发出 的第一个值 ,可以为空
val v2 = emptyFlow<Int>()
.firstOrNull()
println(v2)
// output
null
single
接收流发送的第一个值 ,区别于first(),如果为空或者发了不止一个值,则都会报错
val v = flowOf("A")
.single() // ok
println(v)
// output
A
val v2 = flowOf("A", "B")
.single() // error
println(v2)
singleOrNull
接收流发送的第一个值 ,可以为空 ,发出多值的话除第一个,后面均被置为null
val v3 = emptyFlow<Int>()
.singleOrNull()
println(v3)
// output
null
count
返回流发送值的个数。 类似 list.size()
val v = flowOf("A", "B")
.count()
println(v)
// output
2
val v2 = flow {
emit(1)
emit("A")
}.count { value ->
value is String
}
println(v2)
// output
1
fold
从初始值开始 执行遍历,并将结果作为下个执行的 参数。
val v = flowOf("A", "B")
.fold("Z") { initial, value ->
initial + value
}
println(v)
// output
ZAB
reduce
和fold
运行逻辑一致,只不过无初始值
val v = flowOf(2, 4)
.reduce { accumulator, value ->
accumulator * value
}
println(v)
// output
8
Android 平台操作符
asLiveData
将 Flow
转换成 LiveData
,用于更新数据
val asLiveData = flow {
emit("A")
emit("B")
}.asLiveData()
// 观察
viewModel.asLiveData.observe(this) {
// ignore
}
asFlow
将 LiveData
转换成 Flow
private val userLiveData: LiveData<User> = DataRepository.getUserLiveData()
val userFlow = userLiveData.asFlow()
.map {}
.flowOn(Dispatchers.IO)
.catch {
}
冷流和热流转换
shareIn
将普通flow
转化为 SharedFlow
, 其有三个参数
- scope:
CoroutineScope
开始共享的协程范围 - started:
SharingStarted
控制何时开始和停止共享的策略 - replay: Int = 0 发给 新的订阅者 的旧值数量
其中 started
有一些可选项:
Eagerly
: 共享立即开始,永不停止Lazily
: 当第一个订阅者出现时, 永不停止WhileSubscribed
: 在第一个订阅者出现时开始共享,在最后一个订阅者消失时立即停止(默认情况下),永久保留重播缓存(默认情况下)
WhileSubscribed
具有以下可选参数:
stopTimeoutMillis
— 配置最后一个订阅者消失到协程停止共享之间的延迟(以毫秒为单位)。 默认为零(立即停止)。replayExpirationMillis
- 共享的协程从停止到重新激活,这期间缓存的时效
val shareIn = flowOf(1, 2, 3)
.shareIn(viewModelScope, SharingStarted.Eagerly, 10)
stateIn
将普通flow
转化为 StateFlow
。 其有三个参数:
- scope - 开始共享的协程范围
- started - 控制何时开始和停止共享的策略
- initialValue - 状态流的初始值
val stateIn = flowOf(1)
.stateIn(viewModelScope, SharingStarted.Lazily, 1)
参考
https://developer.android.com/kotlin/flow?hl=zh-cn
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/