本文立足讲解flow的典型流程实现,示例代码如下,不清楚需要了解Flow的基本调用流程
调用示例
- 测试代码
@Test
fun testColdFeature() = runBlocking{
flow<Int>{
println("flow#emit lamda: emit 1")
emit(1)
println("flow#emit lamda: emit 2")
emit(2)
}.collect{
println("Collector#collect: ${it}")
}
}
- 输出结果
flow#emit lamda: emit 1
SafeCollector#emit#1
Collector#collect: 1
flow#emit lamda: emit 2
SafeCollector#emit#2
Collector#collect: 2
以上代码调用collect时,每有一个emit动作,就会触发一次collect lamda的打印操作,本文将会剖析整个调用流程
Flow核心流程实现
核心流程
interface Flow<out T> {
suspend fun collect(collector: FlowCollector<T>)
}
Flow所有的核心流程其实都是围绕Flow接口进行,Flow的概念可以用生产消费来描述,生产负责生产数据,比如emit,消费负责消费数据,以上接口中调用collect其实就是触发开始消费数据,更具体的是数据最终消费的时候会发送给FlowCollector进行处理
interface FlowCollector<in T> {
suspend fun emit(value: T)
}
FlowCollector本来理解为数据收集,但是偏偏是有一个emit方法,这不是发送数据的吗?请往下看:
suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
collect(object : FlowCollector<T> {
override suspend fun emit(value: T) = action(value)
})
这就是我们平常处理数据的lamda定义,先调用collect方法使用Flow的实现类开始消费数据,这时候就是触发生产流程调解emit方法将value带过来,同时以上“emit(value: T) = action(value)”带过来的value值就直接指向给lamda block进行消费了。
因此在整个生产消费模型中,好比你妹子(Flow)饿了大吼了一声说:“断粮了,快去赚钱?”(collect),作为暖男的你(FlowCollector)立马出动全力生产,你每卖完一个烧饼递钱给妹子(emit),妹子就可以直接下单买面膜了action(value)。因此看起来的顺序就为:
flow#collect -> produce#emit -> FlowCollector#emit -> collect#cosume
在这基础上更进一步理解,Flow和FlowCollector都是可以进行更进一步的套接,以此丰富更多的功能扩展
具体生产模式实现
为了理解生产模式的代码的实现流程,这里将核心原码进行了一个分离,以下用调用栈的顺序简单梳理,首先先上图,调用关系从外到里:
1.触发消费数据
flow<Int>{
println("flow#emit lamda: emit 1")
emit(1)
println("flow#emit lamda: emit 2")
emit(2)
}.collect{
println("Collector#collect: ${it}")
}
消费数据的扩展方法如下,本质上是调用Flow中的collect方法
suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
collect(object : FlowCollector<T> {
override suspend fun emit(value: T) = action(value)
})
action是最终消费数据的lamda函数,上一级FlowCollector的emit触发,那么谁来调用emit
2.包装数据实现
class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : Flow<T> {
override suspend fun collect(collector: FlowCollector<T>) {
SafeCollector(collector).block()
}
}
在步骤1中Flow的典型实现类是SafeFlow,它其实是对SafeCollector的装载,上一级调用后转到了SafeCollector的lamda方法
3.数据的生产
flow<Int>{
println("flow#emit lamda: emit 1")
emit(1)
println("flow#emit lamda: emit 2")
emit(2)
}
class SafeCollector<T>(private val collector: FlowCollector<T>) : FlowCollector<T> {
override suspend fun emit(value: T) {
println("SafeCollector#emit#${value}")
collector.emit(value)
}
}
数据的生产通过emit方法,也就是调用到SafeCollector中的emit,这个emit方法中最终又一直调用构造参数中的“collector.emit(value)”,由于一直调用到步骤1中collect中的lamda方法,因为这个lamda也被包装成了一个FlowCollector。
职责总结
由以上的流程,Flow和Collector关系也就清晰了,Flow只有一个collect方法,负责发号令和传递Collector,而Collector对应收集的功能,负责收集和传递数据。
同时,Flow和Collector都是可以通过装饰模式像不断穿衣服一样不断套接上样报的功能属性,这个后面会继续讲解。
轻松理解各种功能的扩展
Map完成数据类型的转换
@Test
fun flowMapCase() = runBlocking {
flow {
emit(1)
println("emit: 1")
}.map {
it * it
}.collect {
println("collect: $it")
}
}
以上map完成了数据类型的转换(乘方操作),至于这是怎么做到的?
public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value ->
return@transform emit(transform(value))
}
从上面看到看到map的实现是通过在Flow的其它上再穿上一个Flow的衣服,调用时就层层脱下来处理就好。
flowOn完成线程的转换
@Test
fun flowOnIOCase() = runBlocking {
flow {
println("${Thread.currentThread().name}:emit: 1")
emit(1)
}.flowOn(Dispatchers.IO)
.collect {
println("${Thread.currentThread().name}:collect# $it")
}
}
输出:
DefaultDispatcher-worker-1 @coroutine#2:emit: 1
main @coroutine#1:collect# 1
说明emit的线程在IO线程,原因是因为“flowOn(Dispatchers.IO)”是在原来的Flow时套接一个转线程的操作:
public fun <T> Flow<T>.flowOn(context: CoroutineContext): Flow<T> {
checkFlowContext(context)
return when {
context == EmptyCoroutineContext -> this
this is ChannelFlow -> update(context = context)
else -> ChannelFlowOperatorImpl(this, context = context)
}
}