Cold flow and hot flow

514 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 14 天,点击查看活动详情

首先先普及一下什么是flow,这里我们讲的是kotlin中的flow。flow其实就是针对数据的一些列的传递,类与类之间,协程和协程之间都是可以的。kotlin在的flow分为冷流和热流。

什么是冷流呢?

按照官方的说法就是只能有一个监听者,只有有监听者的时候流才会开始,要不然流是不会开始的。我们创建冷流的方式有很多种,例如下面几种:

fun createFlowDemo1()= flow<String> {
    emit("hello")
}
fun createFlowDemo2()= flowOf("hello","hi")
fun createFlowDemo3():Flow<String> = listOf("hello","hi").asFlow()

以上就是创建冷流的方式。以下是两种特殊的流:

  1. ChannelFlow:前两个发送数据都是使用emit,channelFlow使用send方式。
  2. CallbackFlow:CallbackFlow其实是在ChannelFlow的基础上做的,不同的是它必须需要实现awaitClose,要不然就会crash。

这两种流有的人把它们归为热流,但是按照定义,以及经过我的测试,我认为它们应该属于冷流,看看下面的例子:

fun testColdFlow()= runBlocking{
    val flow = createFlowDemo1()
    val job1 =  GlobalScope.launch {
        flow.collect{ str ->
            Log.i("flowtest","string:$str")
        }
    }
    val job2 =  GlobalScope.launch {
        delay(5000)
        flow.collect{ str ->
            Log.i("flowtest","string11111:$str")
        }
    }
​
    val job3 =  GlobalScope.launch {
        delay(10000)
        flow.collect{ str ->
            Log.i("flowtest","string333333:$str")
        }
    }
    job1.join()
    job2.join()
    job3.join()
}
fun createFlowDemo1()= callbackFlow {
    send("hello")
    GlobalScope.launch {
        //delay(1000)
        for (i in 0..5) {
            Log.i("flowtest","start emit:$i")
            send(i.toString())
        }
    }.join()
    awaitClose {  }
    Log.i("flowtest","flow end")
}

我创建了一个callbackFlow并send了一些数据,对同一个流对象进行三次collect,而且在collect的时间上做了控制,如果是热流的话那么job2和job3应该是不会有任何东西输出的。但是实际结果是:

I/flowtest: start emit:0
I/flowtest: start emit:1
I/flowtest: start emit:2
I/flowtest: start emit:3
I/flowtest: start emit:4
I/flowtest: string:hello
I/flowtest: string:0
I/flowtest: start emit:5
I/flowtest: string:1
I/flowtest: string:2
I/flowtest: string:3
I/flowtest: string:4
I/flowtest: string:5
I/flowtest: start emit:0
I/flowtest: string11111:hello
I/flowtest: start emit:1
I/flowtest: string11111:0
I/flowtest: start emit:2
I/flowtest: string11111:1
I/flowtest: start emit:3
I/flowtest: string11111:2
I/flowtest: string11111:3
I/flowtest: start emit:4
I/flowtest: start emit:5
I/flowtest: string11111:4
I/flowtest: string11111:5
I/flowtest: start emit:0
I/flowtest: start emit:1
I/flowtest: start emit:2
I/flowtest: start emit:3
I/flowtest: string333333:hello
I/flowtest: string333333:0
I/flowtest: string333333:1
I/flowtest: string333333:2
I/flowtest: string333333:3
I/flowtest: start emit:4
I/flowtest: start emit:5
I/flowtest: string333333:4
I/flowtest: string333333:5

流其实是进行了三次,所以我把这个归为冷流。

什么是热流?

热流不依赖于监听者,也就是说热流是当流创建的时候就已经开始发数据了,而不是只有在有监听者的时候开始。在kotlin里面热流有以下几种:

  1. SharedFLow:共享流,它可以有多个监听者即有多个collect。默认会根据collect对象的处理情况而挂起,相同的值发送多次,监听者就会收到多次。
  2. stateFlow:stateFlow和SharedFlow有很多相似的地方,它也可以有多个监听者,不同的是它需要初始化值,并且多个监听者互相不影响。相同的值发送多次不会多次通知监听者。

热流创建的方式:

var sharedflow = MutableSharedFlow<Int>()
GlobalScope.launch {
    sharedflow.emit(123)
    sharedflow.emit(456)
}
val stateflow = MutableStateFlow(0)
for(i in 1..5){
stateflow.emit(i)
}
callbackFlow {
    for(i in 1..5){
        send(i)
        withContext(Dispatchers.IO){
            send(i+1)
        }
        awaitClose {
            //unregister observer and so on.
        }
    }
}

看下面一个特殊的例子:

fun testColdFlow()= runBlocking{
    val job =  GlobalScope.launch {
        createFlowDemo1().collect{ str ->
            Log.i("flowtest","string:$str")
        }
    }
    job.join()
}
fun createFlowDemo1()= flow<String> {
    emit("hello")
    GlobalScope.launch {
        delay(1000)
        for(i in 0..5){
            emit(i.toString())
        }
    }
}

在这个代码里面”hello“是直接发出去的,后面的数字是delay了1s之后才发出去的,看看打印的结果:

I/flowtest: string:hello

只打印了hello。但是没有打印数字。如果仔细的话看看log会发现有一个错误信息:

Process: dagger2example, PID: 6483
java.lang.IllegalStateException: Flow invariant is violated:
    Emission from another coroutine is detected.
    Child of StandaloneCoroutine{Active}@1066def, expected child of StandaloneCoroutine{Active}@b15d0fc.
    FlowCollector is not thread-safe and concurrent emissions are prohibited.
    To mitigate this restriction please use 'channelFlow' builder instead of 'flow'

意思就说是在构建流的时候FlowCollector不是线程安全的,所以不能这样去构建一个普通的flow。