本文基于协程官方文档讲解,具体可查看here
一、 Flow
1.1、在讲flow前,我们先看Sequence
fun printMsg(msg:String){
println("${Thread.currentThread().name} "+msg)
}
fun simple() = sequence<Int> {
for (i in 1..3) {
Thread.sleep(300)
yield(i) //yield下一个值
}
}
fun main() {
simple().forEach { value -> printMsg("${value}") }
}
Sequence这里的yield并不是我们之前说的yield(同时并发多个协程,可以让其他协程先执行),这里的yield为正在构建的Iterator生成一个值,并挂起直到请求下一个值。 sequence的遍历需要搭配yield。
1.2、Flow的基本使用
flow的话,需要搭配emit使用。
fun simple() = flow<Int> {
for (i in 1..3) {
delay(300)
emit(i) //emit下一个值
}
}
fun main() = runBlocking {
simple().collect { value->
printMsg("${value}")
}
}
Flow是冷流,当collect时会触发,类似于sequence的末端操作触发
1.3、 flow响应取消
fun simple() = flow<Int> {
for (i in 1..4) {
Thread.sleep(100)
emit(i) //emit下一个值
}
}
fun main() = runBlocking {
withTimeoutOrNull(250){ //超时取消
simple().collect { value-> printMsg("${value}") }
}
printMsg("Done")
}
flow伴随flow所在的协程取消,它就自动取消了。后面也会介绍flow的主动取消。
1.4、flow的builder操作
fun main() = runBlocking {
(1..3).asFlow().collect { value -> printMsg("${value}") }
printMsg("Done")
}
区间或list都可以使用asFlow转成flow,为啥这里没有使用emit,是因为区间的值是固定的,无需使用emit。
1.5、中间的flow操作
fun main() = runBlocking {
val list = listOf(1, 2, 3, 4)
list.asFlow().map { value -> value + 1 }
.collect { value -> printMsg("${value}") }
printMsg("----------------")
list.asFlow().transform { value -> emit(value + 1) }
.collect { value -> printMsg("${value}") }
}
flow本身就支持map,跟Rxjava没区别,这里的transform很有用,可以在其block里面去emit值,改变collect消费的数据,但是在map的block中没法用emit发射新数据,大部分情况下,可以用transform去取代map。
问题: list本身就有map操作,为啥还要转成flow来做呢?
fun main() = runBlocking {
val list = listOf(1, 2, 3, 4)
list.asFlow().map { value ->
printMsg("flow map $value")
value + 1
}.collect { value ->
printMsg("${value}") }
printMsg("----------------")
list.map { value ->
printMsg("list map $value")
value + 1
}.forEach { value ->
printMsg("${value}") }
}
flow的map效率会高些,为啥,
因为flow没有产生中间集合,list的map会有中间集合产生,所以建议转成flow,早些年是建议用sequence来做,因sequence的map也不会产生中间集合。
1.6、size限制操作使用take操作符
fun main() = runBlocking {
(1..3).asFlow()
.take(2)
.collect { value ->
printMsg("${value}")
}
printMsg("-----------")
}
1.7、flow的末端操作 reduce操作符
fun main() = runBlocking {
val sum = (1..5).asFlow().map { it * it }
.reduce { a, b ->
printMsg("${a} ${b}")
a + b //执行累加
}
printMsg("$sum")
}
1.8、flow的context
fun main() = runBlocking {
launch(Dispatchers.Default) {
flow{
for(i in 1..3){
emit(i)
printMsg("${i} emit")
}
}.filter {
printMsg("${it} filter")
it % 2 == 0
}.map {
printMsg("${it} map")
it * it
}.collect {
printMsg("${it} collect")
}
}
printMsg("main ")
}
我们称emit发射所在的block为上游,下面的filter、map、collect都称为下游,现在上下游的执行都在同一个线程里面。如想让上游发射在一个线程,下游在另外一个线程。我们可以使用flowOn来指定上游发射所在的线程。
fun main() = runBlocking {
launch(Dispatchers.Default) { // 对应rxjava的observeOn
flow{
for(i in 1..3){
emit(i)
printMsg("${i} emit")
}
}.flowOn(Dispatchers.IO) // 对应rxjava的subscribeOn
.filter {
printMsg("${it} filter")
it % 2 == 0
}.map {
printMsg("${it} map")
it * it
}.collect {
printMsg("${it} collect")
}
}
printMsg("main ")
}
flow的flowOn就类似于Rxjava的subscribeOn, launch指定的调度器就相当于Rxjava的observeOn。
1.9、flow的buffer操作符可节省时间
当我们上游发射快,下游消费慢的时候,可以使用buffer来处理,可节省时间。
先看没加buffer的情况:👇
fun main() = runBlocking {
launch {
val time= measureTimeMillis {
flow{
for(i in 1..3){
delay(300)
emit(i)
}
}.collect {
delay(2000)
printMsg("${it}")
}
}
printMsg("耗时 ${time}")
}
printMsg("main")
}
fun main() = runBlocking {
launch {
val time = measureTimeMillis {
flow {
for (i in 1..3) {
delay(300)
emit(i)
}
}.buffer() //添加buffer的情况
.collect {
delay(2000)
printMsg("${it}")
}
}
printMsg("耗时 ${time}")
}
printMsg("main")
}
只是少了两次发射的时间而已。
1.10、flow的conflation
conflation合并之意,会跳过中间的值,仅仅发送最近的值.
fun main() = runBlocking {
launch {
val time = measureTimeMillis {
flow {
for (i in 1..5) {
delay(300)
emit(i)
}
}.conflate()
.collect {
delay(2000)
printMsg("${it}")
}
}
printMsg("耗时 ${time}")
}
printMsg("main")
}
中间的2,3,4都丢了,仅仅发射最近的值1和5。
1.11、flow的collectLatest
collectLatest并不仅仅只是消费最新的值,而且还能中断之前的collect操作。
fun main() = runBlocking {
launch {
val time = measureTimeMillis {
flow {
for (i in 1..5) {
delay(300)
emit(i)
}
}.collectLatest {
delay(2000)
printMsg("${it}")
}
}
printMsg("耗时 ${time}")
}
printMsg("main")
}
我们在collect里面多添加一行打印:
fun main() = runBlocking {
launch {
val time = measureTimeMillis {
flow {
for (i in 1..5) {
delay(300)
emit(i)
}
}.collectLatest {
printMsg("${it} before") //添加一行这个
delay(2000)
printMsg("${it} after")
}
}
printMsg("耗时 ${time}")
}
printMsg("main")
}
前面消费1,2,3,4的collect中的delay全部中断了,只有5成功了。
1.12、flow的Zip和combine (合并操作符)
fun main() = runBlocking {
val num = (1..3).asFlow().onEach { delay(200) }
val strs = flowOf("one", "two", "three").onEach { delay(300) }
num.zip(strs) { a, b -> "${a} ${b}" }.collect { //使用zip
printMsg("${it}")
}
}
fun main() = runBlocking {
val num = (1..3).asFlow().onEach { delay(200) }
val strs = flowOf("one", "two", "three").onEach { delay(300) }
num.combine(strs) { a, b -> "${a} ${b}" }.collect { //使用combine
printMsg("${it}")
}
}
zip跟combine用法这里是一样的,可能zip更符合我们合并的意图。
1.13、3种模式的flatten操作
flatten其实就是压平的操作,多个数组或者list打平成一个。
1.13.1、 flatMapConcat
fun main() = runBlocking {
(1..3).asFlow().flatMapConcat { it->
flow{ //这里会有3个flow
emit("$it first" )
delay(500L)
emit("$it second" )
}
}.collect{ it->
printMsg("$it")
}
printMsg("Done")
}
1.13.2、 flatMapMerge
fun main() = runBlocking {
(1..3).asFlow().flatMapMerge { it->
flow{
emit("$it first" )
delay(500L)
emit("$it second" )
}
}.collect{ it->
printMsg("$it")
}
printMsg("Done")
}
1.13.3、 flatMapLatest
只能完整的收到最近的数据。
fun main() = runBlocking {
(1..3).asFlow().flatMapLatest{ it->
flow{
emit("$it first" )
delay(500L)
emit("$it second" )
}
}.collect{ it->
printMsg("$it")
}
printMsg("Done")
}
1.14、flow的异常
可以在flow外层直接使用try catch捕捉。
fun main() = runBlocking {
try {
(1..3).asFlow()
.collect { it ->
printMsg("$it")
if (it > 1) {
throw IllegalArgumentException("")
}
}
}catch (e:Throwable){
printMsg("catch $e")
}
}
也可以使用catch操作捕捉。
fun main() = runBlocking {
flow {
for (i in 1..3) {
emit(i)
if (i > 1) {
throw IllegalArgumentException("xxxxx")
}
}
}.catch { e -> emit(0) } //catch
.collect { it ->
printMsg("$it")
}
}
注意
这里的catch操作只能捕捉上游发射异常,不能捕捉下游消费的异常。
fun main() = runBlocking {
flow {
for (i in 1..3) {
emit(i)
}
}.catch { e -> emit(0) }
.collect { it ->
if (it > 1) {
throw IllegalArgumentException("xxxxx")
}
printMsg("$it")
}
}
catch来捕获emit的异常,至于collect的异常还得外层的try catch捕捉。
1.15、flow的完成
try finally和onCompletion都是可以捕捉完成时机。
1.15.1、try finally
fun main() = runBlocking {
try {
flow {
for (i in 1..3) {
emit(i)
}
}.collect { it ->
printMsg("$it")
}
} finally {
printMsg("Done")
}
}
1.15.2、onCompletion
fun main() = runBlocking {
flow {
for (i in 1..3) {
printMsg("emit $i")
emit(i)
}
}.onCompletion { printMsg("onComplete") }
.collect { it ->
printMsg("$it")
}
}
onCompletion :可以带参数,也可以不带,带参数的话,可以判断是否是异常的(不论上游还是下游,只有有异常,都是非null的)
上游发射抛异常的情形:👇
fun main() = runBlocking {
flow {
for (i in 1..3) {
printMsg("emit $i")
emit(i)
throw IllegalArgumentException("xxxxx")
}
}.onCompletion { cause -> if (cause != null) printMsg("${cause.message}") }
.onCompletion { printMsg("onComplete") }
.catch { it -> printMsg("catch exception ${it.message} ") }
.collect { it ->
printMsg("$it")
}
}
下游消费抛异常的情形:👇
fun main() = runBlocking {
flow {
for (i in 1..3) {
printMsg("emit $i")
emit(i)
}
}.onCompletion { cause -> if (cause != null) printMsg("${cause.message}") }
.onCompletion { printMsg("onComplete") }
.catch { it -> printMsg("catch exception ${it.message} ") }
.collect { it ->
throw IllegalArgumentException("xxxxx")
printMsg("$it")
}
}
1.16、flow的启动
flow的启动触发除了collect以外,还有launchIn.
fun main() = runBlocking {
flow {
for (i in 1..3) {
printMsg("emit $i")
emit(i)
}
}.onEach { delay(100L) }
.onEach { event -> printMsg("$event") }
.launchIn(this) //触发flow的启动
printMsg("main")
}
1.17、flow的主动取消
需设置cancellable() + 操作cancel(),同时flow所在的协程也会取消。
fun main() = runBlocking {
launch {
flow {
for (i in 1..3) {
emit(i)
}
}.cancellable()
.collect { event ->
if (event == 2) cancel()
printMsg("$event ${coroutineContext[Job]?.isActive}")
}
}
printMsg("main")
}