开启掘金成长之旅!这是我参与「掘金日新计划 · 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()
以上就是创建冷流的方式。以下是两种特殊的流:
- ChannelFlow:前两个发送数据都是使用emit,channelFlow使用send方式。
- 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里面热流有以下几种:
- SharedFLow:共享流,它可以有多个监听者即有多个collect。默认会根据collect对象的处理情况而挂起,相同的值发送多次,监听者就会收到多次。
- 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。