持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
简易使用
声明 flow:
fun dataSet() = flow {
for (i in 1..3) {
emit(i)
}
}
复制代码
监听获取数据:
GlobalScope.launch {
dataSet().collect { value ->
println(value)
}
}
复制代码
日志输出:
I/System.out: value:1
I/System.out: value:2
I/System.out: value:3
复制代码
好了,正文开始。
flowOn
更改流发射的上下文。
fun dataSet() = flow {
println("threadName: ${Thread.currentThread().name}")
emit(1)
}.flowOn(Dispatchers.IO)
复制代码
日志输出:
I/System.out: threadName: DefaultDispatcher-worker-2
复制代码
若将 Dispatchers.IO
改为 Dispatchers.Main
。
则输出的结果为:
I/System.out: threadName: main
复制代码
launchIn
更改收集流的协程作用域。
GlobalScope.launch {
dataSet().onEach { value ->
println("threadName: ${Thread.currentThread().name}")
println("value: $value")
}.launchIn(CoroutineScope(Dispatchers.Main))
}
复制代码
日志输出:
I/System.out: threadName: main
I/System.out: value: 1
复制代码
不过这里有一点需要注意,就是 launchIn
和 collect
是不能共存的,若使用了 launchIn
,那么获取数据则需要使用 onEach
。
而 onEach
和 launchIn
并不是要固定使用的,onEach
也可以和 collect
配套使用,一般在 onEach
中进行一些间隔处理,例如:
GlobalScope.launch {
dataSet().onEach {
delay(100)
}.collect {
println("value: $it")
}
}
复制代码
cancellable
让 flow 标记为是可以取消的。
默认的情况下,flow 的执行是不可以取消的,即使自动调用了 cancel()
,🌰:
(1..3).asFlow().collect { value ->
if (value == 2) cancel()
println("value: $value")
}
复制代码
日志输出:
I/System.out: value: 1
I/System.out: value: 2
I/System.out: value: 3
复制代码
加上 cancellable
就可以取消了:
(1..3).asFlow().cancellable().collect { value ->
if (value == 2) cancel()
println("value: $value")
}
复制代码
I/System.out: value: 1
I/System.out: value: 2
复制代码
buffer
缓存所提交的数据。
flow 的执行流程,就生产者和消费者一样,上游产出,下游消费,但是,在默认的情况下,都是生产一个,消费一个,再生产一个,再消费一个,这样进行的。若是生产和消费的速度都很快的话,那倒没有什么问题,但是,若生产速度大于消费速度,就会导致生产效率被强制降低了,因为生产者会一直等到消费者消费完再生产,而这时,我们可以考虑使用 buffer 操作符,可以设置缓存的生成数,就像生产出的商品可以临时存储在仓库一样,并不会阻塞生产者的运行。
下面我们看下使用 buffer 前的效果:
fun dataSet() = flow {
(1..3).forEach {
delay(100)
println(Thread.currentThread().name+" flow")
emit(it)
}
}
复制代码
GlobalScope.launch {
val time = measureTimeMillis {
dataSet().collect { value ->
delay(1000)
println(Thread.currentThread().name+" collect")
println("value: $value")
}
}
println("总耗时:$time")
}
复制代码
日志输出:
I/System.out: DefaultDispatcher-worker-1 flow
I/System.out: DefaultDispatcher-worker-1 collect
I/System.out: value: 1
I/System.out: DefaultDispatcher-worker-1 flow
I/System.out: DefaultDispatcher-worker-1 collect
I/System.out: value: 2
I/System.out: DefaultDispatcher-worker-1 flow
I/System.out: DefaultDispatcher-worker-1 collect
I/System.out: value: 3
I/System.out: 总耗时:4776
复制代码
确实可以看出,是生产一个,然后消费一个,总耗时为:4776
加上 buffer 后:
GlobalScope.launch {
val time = measureTimeMillis {
dataSet().buffer(10).collect { value ->
delay(1000)
println(Thread.currentThread().name+" collect")
println("value: $value")
}
}
println("总耗时:$time")
}
复制代码
I/System.out: DefaultDispatcher-worker-1 flow
I/System.out: DefaultDispatcher-worker-2 flow
I/System.out: DefaultDispatcher-worker-2 collect
I/System.out: value: 1
I/System.out: DefaultDispatcher-worker-2 flow
I/System.out: DefaultDispatcher-worker-2 collect
I/System.out: value: 2
I/System.out: DefaultDispatcher-worker-2 collect
I/System.out: value: 3
I/System.out: 总耗时:3763
复制代码
总耗时为:3763,比 4776 少了不少,并且可以看出,它还会自动切换线程进行执行。
conflate
合并发射内容。其实,它算是两个功能的合成,即一个是 buffer 效果,另外一个是合并效果。buffer 消息就不多解释了,刚介绍完,而合并效果就是,例如生产者生产了 A、B、C 三个货品到仓库中,消费者去拿的时候,之后拿首位和尾位,忽略中间的,所以,消费者实际消费只有 A、C,B 就被丢弃了。
我们可以看看具体的实例效果:
fun dataSet() = flow {
(1..3).forEach {
println("flow value: $it")
emit(it)
}
}
复制代码
GlobalScope.launch {
dataSet().conflate().collect { value ->
delay(1000)
println("collect value: $value")
}
}
复制代码
日志输出:
I/System.out: flow value: 1
I/System.out: flow value: 2
I/System.out: flow value: 3
I/System.out: collect value: 1
I/System.out: collect value: 3
复制代码
collectLatest
collectLatest 的效果跟 conflate 类似,只不过改为获取最后一个数据。
GlobalScope.launch {
dataSet().collectLatest { value ->
delay(1000)
println("collect value: $value")
}
}
复制代码
I/System.out: flow value: 1
I/System.out: flow value: 2
I/System.out: flow value: 3
I/System.out: collect value: 3
复制代码
map
用于将原有的数据进行转换,再进行 collect 操作。
fun dataSet() = flow {
(1..3).forEach {
emit(it)
}
}
复制代码
GlobalScope.launch {
dataSet().map { request -> "map$request"}.collect { value ->
println("value: $value")
}
}
复制代码
日志输出:
I/System.out: value: map1
I/System.out: value: map2
I/System.out: value: map3
复制代码
transform
map 操作符只能对于数据进行转换,无法对数据进行增删改,而 transform 则可以实现该功能。
fun dataSet() = flow {
(1..3).forEach {
emit(it)
}
}
复制代码
GlobalScope.launch {
dataSet().transform { data ->
when(data){
1 -> {
emit("data1")
emit("data11")
}
2 -> {}
3 -> emit("data3")
}
}.collect {
println("value: $it")
}
}
复制代码
- when 为 1 时,是增的情况
- when 为 2 时,是删的情况
- when 为 3 时,是改的情况
take
限定收集流的长度,也就是限制只收集多少个。
fun dataSet() = flow {
(1..3).forEach {
emit(it)
}
}
复制代码
GlobalScope.launch {
dataSet().take(2).collect {
println("value: $it")
}
}
复制代码
输出:
I/System.out: value: 1
I/System.out: value: 2
复制代码
toList
将数据转换为 List。
toSet
将数据转换为 Set。
first
获取首位的值:
fun dataSet() = flow {
(1..3).forEach {
emit(it)
}
}
复制代码
GlobalScope.launch {
println("value: ${dataSet().first()}")
}
复制代码
日志输出:
I/System.out: value: 1
复制代码
single
用于确保 flow 输出值唯一。若只有一个值,则可以正常执行,若输出的值不止只有一个的时候,就会抛出异常:
值不唯一:
GlobalScope.launch {
try {
println("value: ${(1..3).asFlow().single()}")
}catch (e: Exception){
println("Exception: $e")
}
}
复制代码
日志输出:
I/System.out: Exception: java.lang.IllegalArgumentException: Flow has more than one element
复制代码
这里有一点要注意的,若不对该异常进行 try catch 的话,会引发整个 App 的崩溃,是真正抛出的异常。
值唯一:
GlobalScope.launch {
try {
println("value: ${(1..1).asFlow().single()}")
}catch (e: Exception){
println("Exception: $e")
}
}
复制代码
日志输出:
I/System.out: value: 1
复制代码
reduce
对于 flow 里面的数据进行两个两个处理。例如 flow 有三个值,分别为 A、B、C,那么,会先处理 A 和 B,然后把 A he B 的结果再与 C 进行处理。
为了方便大家理解,这里使用两个🌰:
val sum = (1..3).asFlow().reduce { accumulator, value -> accumulator + value}
println("sum: $sum")
val content = mutableListOf("one","two", "three").asFlow().reduce { accumulator, value -> "$accumulator $value" }
println("content: $content")
复制代码
日志输出为:
I/System.out: sum: 6
I/System.out: content: one two three
复制代码
在例一中
- 最开始 accumulator 为 1,value 为 2,结果为 1+2=3
- 而后 accumulator 为 3,value 为 3,结果为 3+3=6
fold
给定一个值,然后对 flow 的第一个值进行处理,然后把处理的结果再与第二值进行处理,以此类推。这里要注意的是,处理结果的类型需要与给定的值的类型保持一致。
em...太难懂了,还是看代码吧:
val sum = (1..3).asFlow().fold(3) { accumulator, value -> accumulator * value}
println("sum: $sum")
val content = (1..3).asFlow().fold("data") { accumulator, value -> "$accumulator$value"}
println("content: $content")
复制代码
日志输出:
I/System.out: sum: 18
I/System.out: content: data123
复制代码
在例一中
- 最开始 accumulator 为 3,value 为 1,结果为 3*1=3
- 而后 accumulator 为 3,value 为 2,结果为 3*2=6
- 而后 accumulator 为 6,value 为 3,结果为 6*3=18
zip
将两个 flow 进行合并,把两两合并的结果再交由 collect 进行处理:
GlobalScope.launch {
val stringFlow = mutableListOf("one", "two", "three").asFlow()
val numFlow = (1..3).asFlow()
stringFlow.zip(numFlow) { string, num ->
"$string:$num"
}.collect {
println("value -> $it")
}
}
复制代码
I/System.out: value -> one:1
I/System.out: value -> two:2
I/System.out: value -> three:3
复制代码
不过,这里有一点需要注意的是,假如两个流的长度不一致,那就以最短的为准:
GlobalScope.launch {
val stringFlow = mutableListOf("one", "two").asFlow() // 长度为 2
val numFlow = (1..3).asFlow() // 长度为 3
stringFlow.zip(numFlow) { string, num ->
"$string:$num"
}.collect {
println("value -> $it")
}
}
复制代码
I/System.out: value -> one:1
I/System.out: value -> two:2
复制代码
另外,还有一点,在这里,若两个流产生元素的速度不一致的时候,会等待两个流都产生一个元素后再进行处理:
GlobalScope.launch {
val stringFlow = mutableListOf("one", "two", "three").asFlow().onEach { delay(1000) }
val numFlow = (1..3).asFlow().onEach { delay(1500) }
stringFlow.zip(numFlow) { string, num ->
"$string:$num"
}.collect {
println("value -> $it")
}
}
复制代码
I/System.out: value -> one:1
I/System.out: value -> two:2
I/System.out: value -> three:3
复制代码
Combine
Combine 的功能跟 zip 十分相似,但是有一点不同,就是当流 A 产生一个元素 a 的时候,若流 B 还未产生新的元素 b,但之前有产生过元素 c,这时会先让 a 和 c 进行匹配,然后等 b 产生后,a 会和 b 再次进行匹配。同样,我们可以通过代码更方便去理解:
GlobalScope.launch {
val stringFlow = mutableListOf("one", "two", "three").asFlow().onEach { delay(1000) }
val numFlow = (1..3).asFlow().onEach { delay(1500) }
stringFlow.combine(numFlow) { string, num ->
"$string:$num"
}.collect {
println("value -> $it")
}
}
复制代码
I/System.out: value -> one:1
I/System.out: value -> two:1
I/System.out: value -> two:2
I/System.out: value -> three:2
I/System.out: value -> three:3
复制代码
flatMapConcat
map 的操作符是让元素 A 改为元素 B,而 flatMapConcat 的操作符是让元素 A 改为流 B。
GlobalScope.launch {
(1..3).asFlow().onEach { delay(100) }.flatMapConcat{ num ->
flow {
emit("num1: $num")
delay(200)
emit("num2: $num")
}
}.collect {
println("value -> $it")
}
}
复制代码
日志输出:
I/System.out: value -> num1: 1
I/System.out: value -> num2: 1
I/System.out: value -> num1: 2
I/System.out: value -> num2: 2
I/System.out: value -> num1: 3
I/System.out: value -> num2: 3
复制代码
flatMapMerge
flatMapMerge 与 flatMapConcat 相似,不同的是,flatMapConcat 会等待自己每个元素执行完 flatMapConcat 的转换,才会进行下一个元素执行,而 flatMapMerge 不一样,它不会等待元素执行完,而是直接开始下一个元素执行 flatMapMerge 的转换。所以,就会看到输出的值有点乱的原因。
说白了,其实就是 元素 A 在执行 flatMapMerge 的转换,还未执行完,元素 B 也在执行 flatMapMerge 的转换。
GlobalScope.launch {
(1..3).asFlow().onEach { delay(100) }.flatMapMerge{ num ->
flow {
emit("num1: $num")
delay(200)
emit("num2: $num")
}
}.collect {
println("value -> $it")
}
}
复制代码
I/System.out: value -> num1: 1
I/System.out: value -> num1: 2
I/System.out: value -> num2: 1
I/System.out: value -> num1: 3
I/System.out: value -> num2: 2
I/System.out: value -> num2: 3
复制代码
flatMapLatest
flatMapLatest 又与 flatMapMerge 类似,不同的是,当有新元素的 emit 的时候,旧元素的 emit 就会被丢弃:
GlobalScope.launch {
(1..3).asFlow().flatMapLatest{ num ->
flow {
emit("num1: $num")
delay(200)
emit("num2: $num")
}
}.collect {
println("value -> $it")
}
}
复制代码
I/System.out: value -> num1: 1
I/System.out: value -> num1: 2
I/System.out: value -> num1: 3
I/System.out: value -> num2: 3
复制代码
catch
捕获流产生的异常。
GlobalScope.launch {
flow {
throw NullPointerException()
emit(1)
}.catch { e ->
println("已捕获异常 -> $e")
}.collect { value ->
println("value -> $value")
}
}
复制代码
I/System.out: 已捕获异常 -> java.lang.NullPointerException
复制代码
当然,也是可以使用 try catch 进行捕获的:
GlobalScope.launch {
val flow = flow {
throw NullPointerException()
emit(1)
}
try {
flow.collect { value ->
println("value -> $value")
}
} catch (e: Exception) {
println("已捕获异常 -> $e")
}
}
复制代码
I/System.out: 已捕获异常 -> java.lang.NullPointerException
复制代码
onCompletion
用于流执行完成的回调。
GlobalScope.launch {
flow {
emit(1)
}.onCompletion {
println("流执行完成了")
}.collect { value ->
println("value -> $value")
}
}
复制代码
I/System.out: value -> 1
I/System.out: 流执行完成了
复制代码
当然,也是可以使用 try finally 进行实现的:
GlobalScope.launch {
val flow = flow {
emit(1)
}
try {
flow.collect { value ->
println("value -> $value")
}
} finally {
println("流执行完成了")
}
}
}
复制代码
I/System.out: value -> 1
I/System.out: 流执行完成了
复制代码