参考文章
1,什么时候用flow呢?————kotlin要表示多个值
如何表示多个值?挂起函数可以异步返回单个值,但是如果要异步返回多个计算好的值,
就只能用flow了。其它方案的缺陷,集合返回了多个值,但是不是异步;序列返回了多个值,是同步。
//返回了多个值,但不是异步
fun simpleList(): List = listOf(1, 2, 3)
//返回了多个值,是同步。序列与集合的区别:集合长度是固定的,序列是不固定的。
//SequenceScope只能使用自己已有的挂起函数
fun simpleSequence(): Sequence = sequence {
for (i in 1..3) {
Thread.sleep(1000) //阻塞,线程被占用,不能干其它的事情。这里我们假装在计算
//delay(1000)//只能使用已有的挂起函数
yield(i)
}
}
//集合+挂起函数方案:返回了多个值,是异步,但是一次性返回了多个值
//我们要的是像Sequence一次给一个值
suspend fun simpleList2(): List {
delay(1000)
return listOf(1, 2, 3)
}
@Test
fun `test multiple values`() {
//simpleList().forEach { value -> println(value) }
//simpleSequence().forEach { value -> println(value) }
}
@Test
fun `test multiple values2`() = runBlocking {
simpleList2().forEach { value -> println(value) }
}
这个时候我们就可以使用flow来满足我们的要求。
//返回多个值,是异步的
fun simpleFlow() = flow {//folw构建器,会构建一个flow对象出来
for (i in 1..3) {
delay(1000) //假装在一些重要的事情
emit(i) //发射,产生一个元素用emit
}
}
main方法中搜集元素
simpleFlow().collect{value->println(value)}
这里我们的运行效果与Sequence一模一样,但Sequence是阻塞主线程的,flow是不阻塞主线程的。我们下面通过代码证明flow是不阻塞主线程的。
修改main方法中搜集元素的代码
@Test
fun `test multiple values3`() = runBlocking {
launch {//为了证明flow不是被阻塞的,我们再起一个任务
for (k in 1..3) {
println("I'm not blocked $k")
delay(1500)
}
}
simpleFlow().collect { value -> println(value) }
}
打印输出如下
I'm not blocked 1
1
I'm not blocked 2
2
I'm not blocked 3
3
运行效果与Sequence同,但后者是阻塞的,两个任务来回切换,证明flow是不阻塞的。
注:SequenceScope只能使用自己已有的挂起函数
注:1,通过flow构建器,得到Flow对象。
3,我们自己通过flow构建一个simpleFlow(名字可以随便取),前面可以去掉关键字suspend,这一点很重要,意味着我们可以在很多地方调用它。
flow的典型应用就是下载文件。UI界面点击按钮下载文件,下载文件是通过后台线程(如dispature.IO),通过emit发射数据(下载进度,异常,下载完成等)给主线程,主线程通过collect拿到数据,在UI中更新。
flow构建器重的代码直到流被收集的时候才运行,代码如下
fun simpleFlow2() = flow {
println("Flow started")
for (i in 1..3) {
delay(1000)
emit(i)
}
}
@Test
fun `test flow is cold`() = runBlocking {
val flow = simpleFlow2()//调用这个的时候并没有打印Flow started
println("Calling collect...")
flow.collect { value -> println(value) }//此时才开始打印Flow started
println("Calling collect again...")
flow.collect { value -> println(value) }
}
只有调用collect的时候构建器代码才会执行,才会发射元素,再次调用collect就会再次执行构建器代码,再次发射元素
打印输出如下
Calling collect……
Flow started
1
2
3
Calling collect again……
Flow started
1
2
3
@Test
fun `test flow continuation`() = runBlocking {
//asFlow()是IntRange的扩展属性,构建流的构建器。
(1..5).asFlow().filter {
it % 2 == 0//过滤偶数
}.map {
"string $it"
}.collect {
println("Collect $it")
}
}
注:asFlow的源码
/**
* Creates a flow that produces values from the range.
*/
public fun IntRange.asFlow():Flow=flow{
forEach{
value -> emit(value)
}
}
打印输出如下
Collect string 2
Collect string 4
@Test
fun `test flow builder`() = runBlocking {
flowOf("one","two","three")
.onEach { delay(1000) }//每隔一秒钟发射个元素
.collect { value ->
println(value)//每隔一秒钟打印输出one two three
}
(1..3).asFlow().collect { value ->
println(value)
}
}
注:collect是挂起函数,需要在协程里调用,构建流并不需要在协程里面。构建流和收集流会在同一个协程上下文里面。
我们代码运行验证下
fun simpleFlow3() = flow {
println("Flow started ${Thread.currentThread().name}")
for (i in 1..3) {
delay(1000)
emit(i)
}
}
//这种方法无法解决上下文保存
fun simpleFlow4() = flow {
withContext(Dispatchers.IO) {//withContext切换,后台线程发射元素
println("Flow started ${Thread.currentThread().name}")
for (i in 1..3) {
delay(1000)
emit(i)
}
}
}
@Test
fun `test flow context`() = runBlocking {
simpleFlow3()//都在主线程
.collect { value -> println("Collected $value ${Thread.currentThread().name}") }
simpleFlow4()//这里会报错,
.collect { value -> println("Collected $value ${Thread.currentThread().name}") }
}
看下运行结果
Flow started main @coroutine#1
Collected 1 main @coroutine#1
Collected 2 main @coroutine#1
Collected 3 main @coroutine#1
Flow started DefaultDispatcher-worker-1 @coroutine#1
红色异常省略
建流和收集流会在同一个协程上下文里面,但这很不实用,比如我们需要后台下载文件,并更新UI。如果要打破这种特性,withContext不行,要使用flowOn操作符来打破上下文保存的特性
fun simpleFlow5() = flow {
println("Flow started ${Thread.currentThread().name}")
for (i in 1..3) {
delay(1000)
emit(i)
}
}.flowOn(Dispatchers.Default)
@Test
fun `test flow on`() = runBlocking {
simpleFlow5()
.collect { value -> println("Collected $value ${Thread.currentThread().name}") }
}
代码运行如下
Flow started DefaultDispatcher-worker-1 @coroutine#2
Collected 1 main @coroutine#1
Collected 2 main @coroutine#1
Collected 3 main @coroutine#1
//事件源,三个事件源源不断发送过来
fun events() = (1..3)
.asFlow()
.onEach { delay(100) }
.flowOn(Dispatchers.Default)
处理
events().onEach{
event ->println("Event:$event ${Thread.currentThread.name}")
}
onEach是个过渡操作符,此时没有末端操作符,不会发送。我们加上collect末端操作符
events().onEach{
event ->println("Event:$event ${Thread.currentThread.name}")
}.collect{};
虽然输出了,但是我们想收集处理在一个指定的协程里面,这时候可以使用launchIn
fun `test flow launch`() = runBlocking {
val job = events()
.onEach { event -> println("Event: $event ${Thread.currentThread().name}") }
//.collect { }
//launchIn需要一个作用域,在作用域中指定协程上下文
.launchIn(CoroutineScope(Dispatchers.IO))
.join()//调用join才会等待子协成执行完。
}
launchIn返回的是一个Job对象。
看下打印输出:
Event:1 DefaultDispatcher-worker-3 @coroutine#2
Event:1 DefaultDispatcher-worker-3 @coroutine#2
Event:1 DefaultDispatcher-worker-3 @coroutine#2
因为有隐式调用,this就是主线程,所以在主线程中调用也可以这么写,
.launchIn(this)
流采用与协程同样的协作取消(在协程中启动流,协程取消了,流也就取消了),像往常一样,流的收集可以是当流在一个可取消的挂起函数(如delay)中挂起的时候取消。
我们代码看个例子,流在超时的时候取消
fun simpleFlow6() = flow {
for (i in 1..3) {
delay(1000)
emit(i)
println("Emitting $i")
}
}
@Test
fun `test cancel flow`() = runBlocking {
//2500则超时,超时取消协程,协程被取消了,流自然也被取消了
withTimeoutOrNull(2500) {
simpleFlow6().collect { value -> println(value) }
}
println("Done")
}
我们看下打印输出
1
Emitting 1
2
Emitting 2
Done
fun simpleFlow7() = flow {
for (i in 1..5) {
emit(i)//这里会检测ensureActive,发完3发送4的时候这里检测到active为false。就不会继续发送了。
println("Emitting $i")
}
}
@Test
fun `test cancel flow check`() = runBlocking {
simpleFlow7().collect { value ->
println(value)
if (value == 3) cancel()//执行cancel的时候active为false
}
打印输出如下
1
Emitting 1
2
Emitting 2
3
Emitting 3
报红色的异常,
可以发现4和5没有发出来。
下面这种情况则不会取消流
(1..5).asFlow().collect { value ->
println(value)
if (value == 3) cancel()
}
程序打印输出如下
1
2
3
4
5
红色异常
可以发现没有取消成功。
如果需要取消,必须明确检测是否取消,需增加cancellable(会影响性能)
(1..5).asFlow().cancellable().collect { value ->
println(value)
if (value == 3) cancel()
}
打印输出如下
1
2
3
红色异常
背压解决方案:1,降低生产效率;2,提高消费效率
背压解决方案一 buffer
fun simpleFlow8() = flow {
for (i in 1..3) {
delay(100)//生产这个元素需要100
emit(i)
println("Emitting $i ${Thread.currentThread().name}")
}
}
@Test
fun `test flow back pressure`() = runBlocking {
val time = measureTimeMillis {
simpleFlow8()
.collect { value ->
delay(300) //处理这个元素消耗300ms,生产效率小于消费效率
println("Collected $value ${Thread.currentThread().name}")
}
}
println("Collected in $time ms")//总计耗时1200
}
打印输出如下
Collected 1 main @coroutine#1
Emitting 1 main @coroutine#1
Collected 2 main @coroutine#1
Emitting 2 main @coroutine#1
Collected 3 main @coroutine#1
Emitting 3 main @coroutine#1
Collected in 1226 ms
性能优化下,提升下运行效率。这里使用缓存做性能优化。
都在子线程
@Test
fun `test flow back pressure`() = runBlocking {
val time = measureTimeMillis {
simpleFlow8()
.buffer(50)//指定50个缓存大小。
.collect { value ->
delay(300) //处理这个元素消耗300ms
println("Collected $value ${Thread.currentThread().name}")
}
}
println("Collected in $time ms")
}
打印输出如下
Emitting 1 main @coroutine#2
Emitting 2 main @coroutine#2
Emitting 3 main @coroutine#2
Collected 1 main @coroutine#1
Collected 2 main @coroutine#1
Collected 3 main @coroutine#1
Collected in 1069 ms
可以发现一次性发送出123,然后分开三次去收集。时间消耗缩短了。
前面的代码都在主线程,我们可以并行发送(切换线程),
@Test
fun `test flow back pressure`() = runBlocking {
val time = measureTimeMillis {
flowOn(Dispathcers.Default)
.collect { value ->
delay(300) //处理这个元素消耗300ms
println("Collected $value ${Thread.currentThread().name}")
}
}
println("Collected in $time ms")/
}
发送在后台线程,搜集在主线程
打印输出如下
Emitting 1 DefaultDispatcher-worker-1 @coroutine#2
Emitting 2 DefaultDispatcher-worker-1 @coroutine#2
Emitting 3 DefaultDispatcher-worker-1 @coroutine#2
Collected 1 main @coroutine#1
Collected 2 main @coroutine#1
Collected 3 main @coroutine#1
Collected in 1068 ms
背压解决方案二 conflate合并
conflate并不去处理每个值,而是处理最新的值
fun simpleFlow8() = flow {
for (i in 1..3) {
delay(100)//生产这个元素需要100
emit(i)
println("Emitting $i ${Thread.currentThread().name}")
}
}
@Test
fun `test flow back pressure`() = runBlocking {
val time = measureTimeMillis {
simpleFlow8()
.conflate()
.collect { value ->
delay(300) //处理这个元素消耗300ms
println("Collected $value ${Thread.currentThread().name}")
}
}
println("Collected in $time ms")
}
运行结果如下
Emitting 1 main @coroutine#2
Emitting 2 main @coroutine#2
Emitting 3 main @coroutine#2
Collected 1 main @coroutine#1
Collected 3 main @coroutine#1
collected in 770 ms
跳过了中间值
背压解决:collectLatest()取消并重新发射最后一个值
fun simpleFlow8() = flow {
for (i in 1..3) {
delay(100)//生产这个元素需要100
emit(i)
println("Emitting $i ${Thread.currentThread().name}")
}
}
@Test
fun `test flow back pressure`() = runBlocking {
val time = measureTimeMillis {
simpleFlow8()
.collectLast { value ->
delay(300) //处理这个元素消耗300ms
println("Collected $value ${Thread.currentThread().name}")
}
}
println("Collected in $time ms")
}
打印输出如下
Emitting 1 main @coroutine#2
Emitting 2 main @coroutine#2
Emitting 3 main @coroutine#2
Collected 3 main @coroutine#5
Collected in 785 ms
一 过渡操作符之转换操作符
suspend fun performRequest(request: Int): String {
delay(1000)
return "response $request"
}
@Test
fun `test transform flow operator`() = runBlocking {
(1..3).asFlow()
.map { request -> performRequest(request) }
.collect { value -> println(value) }
打印输出如下
response 1
response 2
response 3
transform 可以进行任意次数的变换
//更复杂的变换,
(1..3).asFlow()
.transform { request ->
emit("Making request $request")
emit(performRequest(request))
}.collect { value -> println(value) }
}
打印输出如下
Making request 1
response 1
Making request 2
response 2
Making request 3
response 3
第二 过渡操作符之限长操作符
fun numbers() = flow {
try {
emit(1)
emit(2)
println("This line will not execute")//不会打印输出,因为只取2个元素
emit(3)
} finally {
println("Finally in numbers")
}
}
@Test
fun `test limit length operator`() = runBlocking {
//只取两个元素
numbers().take(2).collect { value -> println(value) }
}
看下打印输出
1
2
Finally in numbers
@Test
fun `test terminal operator`() = runBlocking {
val sum = (1..5).asFlow()
.map { it * it }
.reduce { a, b -> a + b }//变成平方后累加
println(sum)
}
打印输出如下
55
其它的自己试一试,不在讲解
@Test
fun `test zip`() = runBlocking {
val numbs = (1..3).asFlow()
val strs = flowOf("One", "Two", "Three")
numbs.zip(strs) { a, b -> "$a -> $b" }.collect { println(it) }
}
打印输出如下
1->One
2->Two
3->Three
加延迟
@Test
fun `test zip2`() = runBlocking {
val numbs = (1..3).asFlow().onEach { delay(300) }
val strs = flowOf("One", "Two", "Three").onEach { delay(400) }
val startTime = System.currentTimeMillis()
numbs.zip(strs) { a, b -> "$a -> $b" }.collect {
println("$it at ${System.currentTimeMillis() - startTime} ms from start")
}
}
打印输出如下
1->One at 449 ms from start
2->Two at 850 ms from start
3->Three at 1254 ms from start
每次间隔400,不是700
fun requestFlow(i: Int) = flow {
emit("$i: First")
delay(500)
emit("$i: Second")
}
@Test
fun `test flatMapConcat`() = runBlocking {
//Flow>
val startTime = System.currentTimeMillis()
(1..3).asFlow()
.onEach { delay(100) }
.flatMapConcat { requestFlow(it) }
.collect { println("$it at ${System.currentTimeMillis() - startTime} ms from start") }
}
//打印输出
1:First at 135 ms from start
1:Second at 636 ms from start
2:First at 737 ms from start
3:Second at 1237 ms from start
3:First at 1337 ms from start
3:Second at 1838 ms from start
@Test
fun `test flatMapMerge`() = runBlocking {
//Flow>
val startTime = System.currentTimeMillis()
(1..3).asFlow()
.onEach { delay(100) }
//.map { requestFlow(it) }
.flatMapMerge { requestFlow(it) }
.collect { println("$it at ${System.currentTimeMillis() - startTime} ms from start") }
}
//打印输出
1:First at 174 ms from start
2:First at 264 ms from start
3:First at 366 ms from start
1:Second at 673 ms from start
2:Second at 765 ms from start
3:Second at 868 ms from start
@Test
fun `test flatMapLatest`() = runBlocking {
//Flow>
val startTime = System.currentTimeMillis()
(1..3).asFlow()
.onEach { delay(100) }
//.map { requestFlow(it) }
.flatMapLatest { requestFlow(it) }
.collect { println("$it at ${System.currentTimeMillis() - startTime} ms from start") }
}
}
//打印输出
1:First at 170 ms from start
2:First at 369 ms from start
3:First at 470 ms from start
3:Second at 972 ms from start
fun simpleFlow() = flow {
for (i in 1..3) {
println("Emitting $i")
emit(i)
}
}
@Test
fun `test flow exception`() = runBlocking {
simpleFlow().collect { value ->
println(value)
//抛一个异常,"Collected $value"是异常信息
check(value <= 1) { "Collected $value" }
}
}
代码运行如下
Emitting 1
1
Emitting 2
2
java.lang.IlleagleStateException:Collected 2
下面是红色异常信息(略)
捕获该异常
fun simpleFlow() = flow {
for (i in 1..3) {
println("Emitting $i")
emit(i)
}
}
@Test
fun `test flow exception`() = runBlocking {
try {
simpleFlow().collect { value ->
println(value)
//抛一个异常,"Collected $value"是异常信息
check(value <= 1) { "Collected $value" }
}
} catch (e: Throwable) {
println("Caught $e")
}
}
代码运行如下
Emitting 1
1
Emitting 2
2
Caught java.lang.IllegalStateException:Collected 2
上面的异常都是收集的时候抛出的异常,下面是构建时候抛出的异常
flow {
emit(1)
throw ArithmeticException("Div 0")
}
.flowOn(Dispatchers.IO)
.collect { println(it) }
运行直接抛出红色异常信息,没有收集到任何信息。下面我们捕获该异常
flow {
emit(1)
throw ArithmeticException("Div 0")
}.catch { e: Throwable -> println("Caught $e") }
.flowOn(Dispatchers.IO)
.collect { println(it) }
打印输出如下,
1
Caught java.lang.ArithmeticException:Div 0
我们通过catch函数捕获了上游的异常。
我们捕获到了异常,是可以在异常中再次恢复。代码如下。
flow {
throw ArithmeticException("Div 0")
emit(1)
}.catch { e: Throwable ->
println("Caught $e")
emit(10)//在异常中恢复
}.flowOn(Dispatchers.IO).collect { println(it) }
}
打印输出
Caught java.lang.ArithmeticException:Div 0
10
包括正常完成和异常结束
使用finally
fun simpleFlow2() = (1..3).asFlow()
@Test
fun `test flow complete in finally`() = runBlocking {
try {
simpleFlow2().collect { println(it) }
} finally {
println("Done")
}
}
打印输出如下
1
2
3
Done
使用函数
simpleFlow2()
.onCompletion { println("Done") }
.collect { println(it) }
执行完后会调用onCompletion。
打印输出如下
1
2
3
Done
onCompletion相比finally的优势在于发生异常时会,获取到异常信息,但不会捕获
fun simpleFlow3() = flow {
emit(1)
throw RuntimeException()
}
simpleFlow3()
.onCompletion { exception ->
if (exception != null) println("Flow completed exceptionally")
}
.collect { println(it) }
打印输出如下
1
Flow completed exceptionally
红色异常信息
如果想捕获异常
simpleFlow3()
.onCompletion { exception ->
if (exception != null) println("Flow completed exceptionally")
}
.catch { exception -> println("Caught $exception") }
.collect { println(it) }
打印输出如下,不在有红色异常信息
1
Flow completed exceptionally
Caught java.lang.RuntimeException
onCompletion与获取(不是捕获)下游异常,代码如下
simpleFlow2()
.onCompletion { exception ->
if (exception != null) println("Flow completed exceptionally")
}
.collect { value ->
println(value)
//抛一个异常
check(value <= 1) { "Collected $value" }
}
打印输出如下
1
2
Flow completed exceptionally
红色异常信息
如果要捕捉下游异常就需要try catch了。这里就不再展示