Kotlin:Flow 全面详细指南,附带源码解析。

Flow

来了来了它终于来了,这篇本应在好几个个月前就需要发布的文章😂。一拖再拖🙄,毕竟对于flow还是的有敬畏之心的,不好好研究一下真心不敢乱写,有什么问题,欢迎指出,欢迎私信技术交流😋。那么现在就正式进入Flow的世界吧!

Flow简介

Flow是什么❓

A suspending function asynchronously returns a single value, but how can we return multiple asynchronously computed values? This is where Kotlin Flows come in.

简单来说:挂起函数可以异步处理并且返回单个值,但是如果要返回多个异步计算的值呢?这就是flow的用处了。下面从如何返回多个值开始一步一步深入了解flow

如何返回多个值?

For example 1 : list

我们可以有一个简单的函数,它返回一个包含三个数字的列表,然后使用 forEach 将它们全部打印出来:

fun simple(): List<Int> = listOf(1, 2, 3)
 
fun main() {
    simple().forEach { value -> println(value) } 
}

结果

1
2
3

For example 2 : Sequences

如果我们需要阻塞代码来计算数字(每次计算需要 100 毫秒),那么我们可以使用序列来表示数字:

fun simple(): Sequence<Int> = sequence {
    // sequence 构建
    for (i in 1..3) {
   
        //模拟计算耗时
        Thread.sleep(100)
        println("send$i")
        // yield 下一个值
        yield(i) 
    }
}

fun main() {
   
    simple().forEach {
    value -> println("receiver$value") }
    println("end")
}

结果

send1
receiver1
send2
receiver2
send3
receiver3
end

For example 3 : 异步计算并返回?引入flow

有什么办法可以异步计算多个值并且返回的吗?当然可以,我们可以使用挂起函数,使计算过程在异步线程执行,最终以list的形式返回。

举个🌰

suspend fun simple(): List<Int> {
   
	//模拟耗时操作
    delay(1000)
    return listOf(1, 2, 3)
}

fun main()= runBlocking {
   
    val job = launch {
   
        simple().forEach {
    value -> println(value) }
    }
    launch {
   
        println("other operate")
    }
    job.join()
}

结果:从结果来看,耗时操作并没有影响主线程的运行😎

other operate
1
2
3

但是,这样就够了吗?no no no !🙅‍♀️

使用list意味着我们只能一次性的返回所有的值。所以为了表示流的计算,引入了flow。就像可以使用Sequence类型用于同步计算值一样。

Flow使用

Flow 简单使用

上面介绍了flow要解决什么问题,那么我们就开始使用起来吧。

先看一个简单的🌰

fun simple(): Flow<Int> = flow {
    // flow 构建
    for (i in 1..3) {
   
        //模拟异步耗时计算
        delay(100)
        //发射值
        emit(i)
    }
}

fun main() = runBlocking<Unit> {
   
    // launch 一个协程 同时延时100毫秒打印 校验主线程是否阻塞
    launch {
   
        for (k in 1..3) {
   
            println("I'm not blocked $k")
            //主线程在这个时间段可以干别的事情
            delay(100)
        }
    }
    // collect flow value
    simple().collect {
    println(it) }
}

结果:通过线程打印 I'm not blocked证明异步计算不会阻塞主线程,计算成功之后会resume到collect里面继续执行。

I'm not blocked 1
1
I'm not blocked 2
2
I'm not blocked 3
3

通过上述代码,需要注意一下几点:🙆‍♀️

  • 使用flow代码块构建出来的类型为Flow
  • flow代码块里面允许写挂起函数。比如上面的,delay emit
  • 使用emit进行值的发射,使用collect进行值的收集

Flow 构建

除了上面使用的flow{}进行构建之外,还可以使用其他的方式进行构建。

  1. 使用flowOf可以定义一组固定的值

    fun simple(): Flow<Int> = flowOf(1, 2, 3)
    
  2. 可以使用 asFlow() 扩展函数将各种集合和序列转换为流。

    // 将list转换为flow
    listOf(1,2,3).asFlow().collect {
          value -> println(value) }
    

Flow 冷流

Flow是冷流,构建器代码在调用collect之前是不会进行调用的,对于多个调用者,都会重新走一遍构建器的代码。

废话不多说,上🌰

fun simple(): Flow<Int> = flow {
    
    println("Flow started")
    for (i in 1..3) {
   
        delay(100)
        emit(i)
    }
}

fun main() = runBlocking<Unit> {
   
    println("Calling simple function...")
    val flow = simple()
    println("Calling collect...")
    flow.collect {
    value -> println(value) } 
    println("Calling collect again...")
    flow.collect {
    value -> println(value) } 
}

结果

Calling simple function...
Calling collect...
Flow started
1
2
3
Calling collect again...
Flow started
1
2
3

每次收集流时都会开始,这就是为什么我们再次调用 collect 时会看到“Flow started”的原因。

Flow 取消

如何取消一个Flow呢?

Kotlin官方并没有提供flow取消的函数。啊 这???😕听到这个是不是还满疑惑。且听我细细道来。

Flow需要在协程里面使用,因为collect是挂起函数,另外基于冷流的特性,不调用collect构建器的代码压根不会走。所以只能是协程。那 我取消协程不就行了吗?😮。好像之前有看到过有开发者提出过,是否要给flow单独加一个取消的函数,被Jetbrains无情的拒绝了,哈哈哈哈很搞笑。下面引用Kotlin官方的一段话。

Flow adheres to the general cooperative cancellation of coroutines. As usual, flow collection can be cancelled when the flow is suspended in a cancellable suspending function (like delay).

adheres 坚持

Flow 坚持协程的一般协作取消。 像往常一样,当流在可取消的挂起函数(如延迟)中被挂起时,可以取消流收集。

这个adheres好像就像是在回复广大的开发者,你取消协程就行了😂😂😂。

好了,下面看取消的🌰

fun simple(): Flow<Int> = flow {
   
    for (i in 1..3) {
   
        delay(100)
        println("emit $i")
        emit(i)
    }
}

fun main() = runBlocking<Unit> {
   
    val job = launch {
   
        simple().collect {
    println(it) }
    }
    delay(250)
    job.cancel(CancellationException("timeout 250"))
    println("done")
}

结果:看我们只需要取消对应的协程即可,对应的flow也会被取消收集。

emit 1
1
emit 2
2
done

这里引申一点,对于timeout,官方有提供专用的操作函数,withTimeout系列。不需要我们手动delay然后继续调用取消,毕竟不是很优雅。

上述代码也可以写成如下的形式

fun simple(): Flow<Int> = flow {
   
    for (i in 1..3) {
   
        delay(100)
        println("emit $i")
        emit(i)
    }
}
fun main() = runBlocking<Unit> {
   
    withTimeoutOrNull(250) {
   
        simple().collect {
    value -> println(value) }
    }
    println("done")
}

看到了吗?直接将launch替换为withTimeoutOrNull就可以做到延时取消效果了,这里简单做一下源码分析。

源码
public suspend fun <T> withTimeoutOrNull(timeMillis: Long, block: suspend CoroutineScope.() -> T): T? {
   
    ...
    try {
   
        return suspendCoroutineUninterceptedOrReturn {
    uCont ->
            val timeoutCoroutine = TimeoutCoroutine(timeMillis, uCont)
            coroutine = timeoutCoroutine
            setupTimeout<T?, T?>(timeoutCoroutine, block)
        }
    } catch (e: TimeoutCancellationException) {
   
       ...
    }
}
private class TimeoutCoroutine<U, in T: U>(
    @JvmField val time: Long,
    uCont: Continuation<U> // unintercepted continuation
) : ScopeCoroutine<T>(uCont.context, uCont
  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pumpkin的玄学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值