在进入StateFlow和SharedFlow学习之前,咱们先回顾下Flow的基本常识,在之前的文章中有介绍过,Flow是一种冷流,只需在搜集者开端搜集数据的时分,它才会去发射数据,而今天介绍的StateFlow和SharedFlow却是一种热流,只需有数据它们就会发射并不会在意有没有搜集者。

【Flow】图文详解Kotlin中SharedFlow和StateFlow

文章内容较长,请咱们慢慢食用

Kotlin】Flow流常识梳理

【Kotlin】Kotlin中Flow操作符解析大全

先来看看它们三者的对应联系,如下图所示:

【Flow】图文详解Kotlin中SharedFlow和StateFlow

从上面的联系图能够看出,SharedFlow和StateFlow都是Flow的子类,而且StateFlow也是SharedFlow的直接子类,能够了解为状况流也是特殊的一种同享流,那么咱们就先从SharedFlow下手,先把它的运用和原理弄清楚之后,那么StateFlow就很轻松的了解了。

SharedFlow

在日常开发中,一般运用MutableSharedFlow()办法创立一个SharedFlow目标

【Flow】图文详解Kotlin中SharedFlow和StateFlow

此办法有三个参数,咱们先看下这三个参数界说和效果:

  • replay:回放参数,此参数的效果便是在搜集时,能够先搜集到现已发射过的某些数量值,默以为0
  • extraBufferCapacity:额定的缓冲容量,此参数能够界说除了指定回放数量以外的可缓冲数,默以为0
  • onBufferOverflow:处理缓冲的战略,这个战略配置的是发射数据时处理缓冲区数据溢出的方式,BufferOverFlow是一个枚举类,内部有三种方式分别为SUSPEND、DROP_OLDEST和DROP_LATEST,这儿先简略介绍下三种方式的大约意思
    • SUSPEND表明当缓冲区数据满了的时分,会将发射事情挂起
    • DROP_OLDEST表明当缓存区数据满了的时分,再次发送数据时会放弃缓冲区最旧的数据
    • DROP_LATEST表明当缓存区数据满了的时分,再次发送数据时会放弃缓存区最新的数据

这儿先大致了解下这三种方式,后边会经过代码示例具体解说下。下面咱们先最简略运用下SharedFlow,参数都采用默许的值,看看效果怎么。

默许参数的同享流

fun main(): Unit = runBlocking {
    sampleShared()
}
private suspend fun sampleShared() {
    val sharedFlow = MutableSharedFlow<Int>()
    GlobalScope.launch {
        for (i in 0..10) {
            println("shareFlow emit: $i")
            sharedFlow.emit(i)
            delay(100L)
        }
    }
    GlobalScope.launch {
        sharedFlow.collect {
            println("sharedFlow collect: $it")
        }
    }
    delay(2000L)
}

上面运用MutableSharedFlow()办法创立了一个SharedFlow目标,然后开启两个协程,分别用于发送数据和接纳数据互不干扰,发送11次数据,每次延时100ms,终究的delay(2000L)是为了能让上面协程完整履行完。终究咱们看下运转后的效果

log:
shareFlow emit: 0
shareFlow emit: 1
sharedFlow collect: 1
shareFlow emit: 2
sharedFlow collect: 2
shareFlow emit: 3
sharedFlow collect: 3
shareFlow emit: 4
sharedFlow collect: 4
shareFlow emit: 5
sharedFlow collect: 5
shareFlow emit: 6
sharedFlow collect: 6
shareFlow emit: 7
sharedFlow collect: 7
shareFlow emit: 8
sharedFlow collect: 8
shareFlow emit: 9
sharedFlow collect: 9
shareFlow emit: 10
sharedFlow collect: 10

咱们对照上面的日志来剖析下SharedFlow的运转机制,emit()是从0-10一共发送了11次数据,然而搜集者却是从1开端收到数据,这是因为在发送数据0的时分,搜集者还没开端作业,只需比及发送数据1的时分,搜集者才真实的履行搜集动作,尔后就会依照发送的数据顺次搜集到数据。

从这儿就能够看出SharedFlow是一个热流,在流的榜首篇文章咱们得知Flow是一个冷流,只需在搜集者作业的时分才会发射数据,而从这儿的数据0发射状况来看,即便搜集者还没有作业的时分SharedFlow也会履行发送动作,热流不会在乎发射的时分有没有搜集者在作业。

介绍完采用默许参数的SharedFlow之后,下面咱们顺次体会下SharedFlow三个参数的效果。

带回放参数的同享流

fun main(): Unit = runBlocking {
    sampleReplayShared()
}
private suspend fun sampleReplayShared() {
    val sharedFlow = MutableSharedFlow<Int>(replay = 2)
    GlobalScope.launch {
        for (i in 0..10) {
            sharedFlow.emit(i)
            delay(100L)
        }
    }
    // 这儿延时1s,上面数据现已发送了0,1,2,3,4,5,6,7,8,9
    // 此刻搜集者还没开端作业,假如没有回放参数只能从9开端搜集
    delay(1000L)
    GlobalScope.launch {
        sharedFlow.collect {
            println("sharedFlow collect: $it")
        }
    }
    delay(2000L)
}

这次创立MutableSharedFlow的时分,传入了reply = 2的参数,履行它的回放参数为2,而且在搜集者作业之前延时1s,先让SharedFlow发送一会数据,延时过后再开端搜集作业,1s的延时现已发送了数据0-9,假如回放参数采用默许0的话,此刻搜集者只能搜集到数据10,那么运转下看看传入回放参数2的效果是怎样的呢?

log:
sharedFlow collect: 8
sharedFlow collect: 9
sharedFlow collect: 10

日志中竟然额定输出了8和9,从这也就能够看出,回放参数直接影响了搜集数据的起始方位,即便数据在搜集之前现已发送过了,搜集者仍是能够依据回放参数来搜集到搜集前发送的数据。

这儿咱们还能够经过ShareFlow.replayCache来更直观的观察到回放数据的改变,下面咱们在每次发送数据之前打印一下replayCache值(它是一个列表目标,界说在ShareFlow接口中)

GlobalScope.launch {
    for (i in 0..10) {
        sharedFlow.emit(i)
        println("shareFlow emit: $i")
        val replayCache = sharedFlow.replayCache
        println("shareFlow replayCache: ${replayCache.joinToString()}")
        delay(100L)
    }
}
log:
shareFlow emit: 0
shareFlow replayCache: 0
shareFlow emit: 1
shareFlow replayCache: 0, 1
shareFlow emit: 2
shareFlow replayCache: 1, 2
shareFlow emit: 3
shareFlow replayCache: 2, 3
shareFlow emit: 4
shareFlow replayCache: 3, 4
shareFlow emit: 5
shareFlow replayCache: 4, 5
shareFlow emit: 6
shareFlow replayCache: 5, 6
shareFlow emit: 7
shareFlow replayCache: 6, 7
shareFlow emit: 8
shareFlow replayCache: 7, 8
shareFlow emit: 9
shareFlow replayCache: 8, 9
sharedFlow collect: 8
sharedFlow collect: 9
shareFlow emit: 10
shareFlow replayCache: 9, 10
sharedFlow collect: 10

上面的日志可能有点多,可是细看输出的次序仍是比较清晰的,当发送数据0的时分,此刻回放缓存replayCache为0,发送数据1的时分回放缓存就变成了0和1,尔后的回放缓存就顺次增加,在发送完数据9的时分回放缓存为8和9,此刻搜集者开端作业,它会直接从回放缓存中取到8和9并顺畅的搜集过来,从日志中也能够看出shareFlow collect:8和shareFlow collect:9被打印出来,终究发送数据10的时分,搜集者也就立即搜集到并打印出来。

从这儿示例就能够十分清楚的了解到replayCache的效果,它便是为了让搜集者能够搜集到在自己正式作业之前现已发送的数据,下面咱们接着来看下extraBufferCapacity参数的效果和效果。

带额定缓冲容量和缓冲战略的同享流

这儿直接将extraBufferCapacity和onBufferOverflow放在一起解说,它们俩都是用来处理缓冲数据的战略参数

挂起战略

private suspend fun sampleBufferCapacityShared() {
    val sharedFlow = MutableSharedFlow<Int>(
        extraBufferCapacity = 0,
        onBufferOverflow = BufferOverflow.SUSPEND
    )
    val startTime = System.currentTimeMillis()
    GlobalScope.launch {
        sharedFlow.collect {
            val currentTimeMillis = System.currentTimeMillis()
            println("sharedFlow ${currentTimeMillis - startTime} collect: $it")
            delay(300L)
        }
    }
    GlobalScope.launch {
        for (i in 0..10) {
            val currentTimeMillis = System.currentTimeMillis()
            println("shareFlow ${currentTimeMillis - startTime} emit: $i")
            sharedFlow.emit(i)
            delay(100L)
        }
    }
    delay(5000L)
}

上面将extraBufferCapacity和onBufferOverflow分别设置为0和SUSPEND,也便是表明缓冲区大小为0,发射的时分没有缓冲区能够存储只能挂起发送事情等候搜集者处理完数据才能够进行下一次的数据发送,而且咱们在搜集数据的时分延时300ms来模拟耗时操作,而发送数据只需100ms的延时,这儿成心将搜集处的延时设置的比发送延时要长一点,这样就能够很清晰的看出ShareFlow是怎么处理积压的发射数据,下面咱们运转下代码看看实际状况是否如此。

log:
shareFlow 11 emit: 0
sharedFlow 21 collect: 0
shareFlow 122 emit: 1
sharedFlow 325 collect: 1
shareFlow 426 emit: 2
sharedFlow 627 collect: 2
shareFlow 730 emit: 3
sharedFlow 928 collect: 3
shareFlow 1033 emit: 4
sharedFlow 1229 collect: 4
shareFlow 1329 emit: 5
sharedFlow 1532 collect: 5
shareFlow 1637 emit: 6
sharedFlow 1833 collect: 6
shareFlow 1938 emit: 7
sharedFlow 2134 collect: 7
shareFlow 2239 emit: 8
sharedFlow 2437 collect: 8
shareFlow 2538 emit: 9
sharedFlow 2740 collect: 9
shareFlow 2846 emit: 10
sharedFlow 3045 collect: 10

经过日志中emit和collect打印次序能够看出,即便发送的延时低于搜集的延时,它也不会依照既定的100ms发送一次数据来正常发送,它会在搜集处延时完了之后才进行下一次的发送事情。

接着咱们将extraBufferCapacity设置为2看看效果

log:
shareFlow 11 emit: 0
sharedFlow 14 collect: 0
shareFlow 118 emit: 1
shareFlow 221 emit: 2
sharedFlow 319 collect: 1
shareFlow 322 emit: 3
shareFlow 427 emit: 4
sharedFlow 624 collect: 2
shareFlow 727 emit: 5
sharedFlow 927 collect: 3
shareFlow 1031 emit: 6
sharedFlow 1232 collect: 4
shareFlow 1336 emit: 7
sharedFlow 1532 collect: 5
shareFlow 1636 emit: 8
sharedFlow 1837 collect: 6
shareFlow 1941 emit: 9
sharedFlow 2138 collect: 7
shareFlow 2242 emit: 10
sharedFlow 2442 collect: 8
sharedFlow 2746 collect: 9
sharedFlow 3051 collect: 10

咱们将上面的日志转换成流程图来直观的感受下全体进程的运作

【Flow】图文详解Kotlin中SharedFlow和StateFlow

对照着上面的流程图,咱们一步一步的剖析下全体流程:

  • 当发送数据0的时分,此刻缓冲区是没有数据的,SharedFlow能够正常发送数据0,接纳也是正常接纳,发送完之后缓冲区不变;
  • 100ms之后又开端发送数据1,可是此刻搜集者还在300ms的延时内,没有搜集者处理数据,只能将发送的数据存到缓冲区,那么此刻缓冲区就变成数据1;
  • 200ns之后又开端发送数据2,此刻仍旧没有搜集者搜集数据,只能将数据2存到缓冲区,此刻的缓冲区现已有数据1了,咱们设置的缓冲容量为2,缓冲区新增数据2;
  • 300ms之后搜集者延时现已完毕,它能够持续接纳数据了,因为缓冲区是有数据1和2的,那么此刻优先接纳缓冲区的数据,按次序就接纳了数据1,然后SharedFlow持续开端发送数据3,此刻就会将数据3添加到缓冲区移除数据1,缓冲区数据变为2和3;
  • 400ms的时分是一个节点,此刻SharedFlow接纳处处于延时状况中,而发送处开端发送数据4,因为此刻缓冲区已满,发送处就会变成挂起状况,等候搜集处延时完毕,缓冲区数据不变
  • 500ms时,SharedFlow接纳处仍是处于延时状况,发送处也只能处于挂起状况,此刻发送和接纳都不会有任何动作;
  • 600ms时,SharedFlow接纳处延时状况完毕,开端接纳新的数据,优先接纳缓冲区数据2,数据2被接纳之后,发送处挂起状况也随之完毕,就能够将挂起的数据4给发送出来,添加到缓冲区傍边;
  • 600ms到2200ms之间的流程就省掉不说了,流程相似,下一个主要节点便是2200ms;
  • 2200ms时,SharedFlow发送处开端发送终究一个数据10,此刻接纳处是延时状况,发送处不能够及时的将数据10发送出去,只能被逼挂起;
  • 2400ms之后,SharedFlow开端接纳新的数据,它从缓冲区接纳完数据8之后,发送处也将数据10发送出来,添加到缓冲区傍边,此刻缓冲区数据变为9和10,留意到此为止,现已没有新的数据持续发送,只需等候接纳处每隔300ms接纳新数据即可
  • 2700ms之后,SharedFlow开端顺次从缓冲区接纳新的数据,直到将数据9和10接纳完毕。

上面便是整个extraBufferCapacity = 2和onBufferOverflow = SUSPEND的全体流程,总体来说便是挂起战略能够在缓冲区已满的状况下挂起发送事情,等候接纳处处理完结之后持续发送新的数据。

接下来咱们再看看onBufferOverflow = DROP_OLDEST和onBufferOverflow = DROP_LATEST的效果(缓冲区容量仍旧设置为2)

丢掉缓冲区旧数据战略

DROP_OLDEST顾名思义便是丢掉最旧的数据,那么丢掉的是哪里旧数据呢?经过代码来了解下,示例代码仍是沿用上面的,只是将onBufferOverflow修改为DROP_OLDEST,运转代码看看日志输出状况:

log:
shareFlow 12 emit: 0
sharedFlow 16 collect: 0
shareFlow 121 emit: 1
shareFlow 225 emit: 2
sharedFlow 318 collect: 1
shareFlow 326 emit: 3
shareFlow 426 emit: 4
shareFlow 528 emit: 5
sharedFlow 622 collect: 4
shareFlow 631 emit: 6
shareFlow 731 emit: 7
shareFlow 832 emit: 8
sharedFlow 922 collect: 7
shareFlow 932 emit: 9
shareFlow 1034 emit: 10
sharedFlow 1225 collect: 9
sharedFlow 1529 collect: 10

从日志中能够看出,全体消费时间只需SUSPEND战略的一半,发送处是将数据0-10都发送出来,可是搜集处只搜集到了0、1、4、7、9和10数据,中心有好几个数据没有正常接纳到,下面咱们仍是将日志转换成对应的流程图看看全体运转状况。

【Flow】图文详解Kotlin中SharedFlow和StateFlow

在了解了SUSPEND战略之后,DROP_OLDEST流程了解起来就很轻松了,下面仍是一步一步的解说下整个流程:

  • 0ms时,SharedFlow开端发送数据0,接纳处也开端接纳数据0,此刻缓冲区没有数据新增,接纳处开端履行延时操作;
  • 100ms时,SharedFlow发送数据1,此刻接纳处还处于延时状况,发送的数据1只能暂时存到缓冲区中,缓冲区数据变为1;
  • 200ms时,SharedFlow发送数据2,此刻接纳处仍旧处于延时状况,发送的数据2仍然暂存到缓冲区中,缓冲区数据变为1和2;
  • 300ms时,接纳处延时完毕,开端搜集新的数据,优先从缓冲区中获取数据,获取到数据1,此刻发送处也开端发送新的数据3,因为缓冲区数据1被接纳,数据3就能够暂存进去,缓冲区数据变为2和3;
  • 400ms时,SharedFlow发送数据4,接纳处前面接纳完数据1开端延时,只能将数据4存入缓冲区,可是此刻缓冲区现已有2和3,到达了最大的容量,因为咱们设置的是DROP_OLDEST战略,它会将缓冲区最旧的数据2丢掉,将发送的数据4存入缓冲区,缓冲区数据就变成3和4;
  • 中心逻辑共同,省掉…(主要是想偷个懒)
  • 当1000ms时,SharedFlow开端发送终究一个数据10,此刻发送处还在延时状况,就将数据10存入缓冲区,放弃掉缓冲区最旧的数据8,缓冲区数据变为9和10;
  • 1000ms之后,SharedFlow现已没有数据能够发送,现在只需求等候接纳处每隔300ms从缓冲区接纳数据即可,直到将9和10接纳完结。

从上面的流程能够看出,DROP_OLDEST在缓冲区数据已满的状况下,发送新的数据会将缓冲区最旧的那个数据丢掉,存入发送的数据,这也导致了接纳者不能完整的接纳所有发送过的数据。

丢掉缓冲区最新数据战略

DROP_LATEST的意思是丢掉最新的数据,它和DROP_OLDEST正好相反,此战略下缓冲区已满的状况下,发送新数据时缓冲区的数据是不变的,新发送的数据不会存入缓冲区中,直接丢掉。

咱们在原有代码中onBufferOverflow修改为DROP_LATEST,运转一下看看日志输出状况:

log:
shareFlow 11 emit: 0
sharedFlow 14 collect: 0
shareFlow 119 emit: 1
shareFlow 221 emit: 2
sharedFlow 316 collect: 1
shareFlow 322 emit: 3
shareFlow 426 emit: 4
shareFlow 529 emit: 5
sharedFlow 617 collect: 2
shareFlow 631 emit: 6
shareFlow 731 emit: 7
shareFlow 832 emit: 8
sharedFlow 918 collect: 3
shareFlow 934 emit: 9
shareFlow 1036 emit: 10
sharedFlow 1221 collect: 6
sharedFlow 1522 collect: 9

从collect的日志状况看下来,好像和咱们预期的效果有点收支,在运转代码之前预期的只会搜集到0、1、2、3、4、5,可是实际状况的确搜集到了0、1、2、3、6、9这五个数据,有点懵,仍是经过流程图剖析下全体运转状况吧。

【Flow】图文详解Kotlin中SharedFlow和StateFlow

  • 0ms时,SharedFlow开端发送数据0,接纳处也开端接纳数据0,此刻缓冲区没有数据新增,接纳处开端履行延时操作;
  • 100ms时,SharedFlow发送数据1,此刻接纳处还处于延时状况,发送的数据1只能暂时存到缓冲区中,缓冲区数据变为1;
  • 200ms时,SharedFlow发送数据2,此刻接纳处仍旧处于延时状况,发送的数据2仍然暂存到缓冲区中,缓冲区数据变为1和2;
  • 300ms时,接纳处延时完毕,开端搜集新的数据,优先从缓冲区中获取数据,获取到数据1,此刻发送处也开端发送新的数据3,因为缓冲区数据1被接纳,数据3就能够暂存进去,缓冲区数据变为2和3;
  • 400ms时就开端DROP_LATEST战略就开端发挥它的效果了,此刻缓冲区数据已满,发送的数据4不能够进入缓冲区替换掉数据2,只能将新数据4丢掉掉,缓冲区数据不变仍然为2和3;
  • 500ms时和前面400ms发生的事情共同,缓冲区数据仍旧不变;
  • 600ms时,搜集处开端作业从缓冲区接纳数据2,发送处也开端发送新的数据6,此刻缓冲区数据只需3,那新的数据6就能够暂存到缓冲区中,缓冲区数据变为3和6;
  • 中心省掉一段,逻辑和之前相似
  • 当到达1000ms时,发送终究一个数据10,此刻缓冲区已满数据10不能够存入到缓冲区,放弃数据10,缓冲区数据仍旧为6和9;
  • 后边的1200ms和1500ms,搜集处开端缓冲区顺次获取数据6和9,终究在1500ms接纳完毕,整个流程也就完结了。

从上面的流程能够看出,DROP_LATEST会在缓冲区已满的时分直接放弃掉即将发送的数据,这便是它处理缓冲区的战略。到这为止整个SharedFlow机制就介绍完了,它的运转机制及三个参数的效果都在上面进行了具体的介绍,咱们能够复制示例代码运转感受下它的作业流程,加深下对它的了解。下面进入StateFlow的相关常识介绍。

StateFlow

StateFlow在文章开端的地方就介绍到它是一种特殊的SharedFlow,看完上面临SharedFlow的解说之后,了解StateFlow就变得十分简略,下面直接看怎么运用StateFlow。

fun main(): Unit = runBlocking {
    sampleStateFlow()
}
private suspend fun sampleStateFlow() {
    val stateFlow = MutableStateFlow(0)
    GlobalScope.launch {
        stateFlow.collect {
            println("stateFlow collect: $it")
        }
    }
    delay(100L)
    GlobalScope.launch {
        for (i in 1..10) {
            println("stateFlow emit: $i")
            stateFlow.emit(i)
            delay(100L)
        }
    }
    delay(2000L)
}

StateFlow一般由MutableStateFlow(value)方式创立,它必须传入一个value参数,表明StateFlow的默许值,即便后续没有数据发送,也能够搜集到此默许值。

代码12行处延时100ms便是为了让搜集者能够完结默许值0的进程,然后在发射的地方每次发送完延时100ms,接下来看看日志的输出状况

stateFlow collect: 0
stateFlow emit: 1
stateFlow collect: 1
stateFlow emit: 2
stateFlow collect: 2
stateFlow emit: 3
stateFlow collect: 3
stateFlow emit: 4
stateFlow collect: 4
stateFlow emit: 5
stateFlow collect: 5
stateFlow emit: 6
stateFlow collect: 6
stateFlow emit: 7
stateFlow collect: 7
stateFlow emit: 8
stateFlow collect: 8
stateFlow emit: 9
stateFlow collect: 9
stateFlow emit: 10
stateFlow collect: 10

从上面能够看出搜集者在发送新数据之前优先拿到默许数据0,后续每次发送新的数据都会正常的接纳到,默许值便是它和SharedFlow的不同之处。

MutableStateFlow(value)终究会创立一个StateFlowImpl目标,它内部维护着_state的atomic目标,此目标的初始值便是咱们传入的value,而且每次发送新数据的时分它也会跟着改变成最新的值,即便在屏幕旋转之后,再次履行搜集动作仍是会拿到_state最新的数据。所以在开发中StateFlow适用于存储和界面相关的数据,这样在屏幕旋转或者activity意外重建的状况下也能保证界面之前的信息不会丢掉,而SharedFlow适用于弹Toast、Dialog等场景,它不需求保存状况,便是履行一次性动作。

好了,到这儿SharedFlow和StateFlow大致都介绍完了,假如你对文章有不同的见地或者疑问都能够在评论区沟通,我也会及时回复,谢谢咱们的阅览。

关于我

我是Taonce,假如觉得本文对你有所协助,帮助关注、赞或者保藏三连一下,谢谢~