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{}
进行构建之外,还可以使用其他的方式进行构建。
-
使用
flowOf
可以定义一组固定的值fun simple(): Flow<Int> = flowOf(1, 2, 3)
-
可以使用
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