SharedFlow
SharedFlow是一個hot stream. sharedflow有以下特點:
- 沒有默認值
- 可以保持舊值
- emit會掛起直到所有的訂閱者處理完成
public fun <T> MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
)
replay: 重新發送給新訂閱者之前值的個數
extraBufferCapacity:除了replay緩衝區個數之外的緩衝區的值。當有剩餘空間的時候emit就不會掛起
onBufferOverflow:當extraBufferCapacity溢出時的處理。有下面三種處理方式:
public enum class BufferOverflow {
/**
* Suspend on buffer overflow.
*/
SUSPEND,
/**
* Drop **the oldest** value in the buffer on overflow, add the new value to the buffer, do not suspend.
*/
DROP_OLDEST,
/**
* Drop **the latest** value that is being added to the buffer right now on buffer overflow
* (so that buffer contents stay the same), do not suspend.
*/
DROP_LATEST
}
使用demo來測試這幾個參數的作用:
1.測試replay的作用
fun `sharedflow Reply = 1`()= runBlocking{
var sharedflow = MutableSharedFlow<Int>(replay = 1)
GlobalScope.launch {
sharedflow.emit(111)
sharedflow.emit(222)
}
val job1 = GlobalScope.launch {
delay(5000)
sharedflow.collect{
println("first job : $it")
}
}
val job2 = GlobalScope.launch {
delay(10000)
sharedflow.collect{
println("second job : $it")
}
}
job1.join()
job2.join()
}
結果:
first job : 222
second job : 222
上面的例子可以看出,設置了replay爲1(默認爲0),job1和job2最後打印的都是“222”
2.測試extraBufferCapacity的作用
fun `sharedflow emit suspend`() = runBlocking{
var sharedflow = MutableSharedFlow<Int>()
val job1 = GlobalScope.launch {
sharedflow.collect{
//println("${gettime()}:first job: $it")
}
}
val job2 = GlobalScope.launch {
sharedflow.collect{
delay(5000)
//println("${gettime()}:second job: $it")
}
}
val job3 = GlobalScope.launch {
for (i in 1..5){
delay(500)
println("${gettime()}:start--------emit $i")
sharedflow.emit(i)
println("${gettime()}:end--------emit $i")
}
}
job1.join()
job2.join()
job3.join()
}
結果:
第一個emit沒有掛起
11:20:46:699:start--------emit 1
11:20:46:704:end--------emit 1
第二個emit掛起了4s左右
11:20:47:206:start--------emit 2
11:20:51:711:end--------emit 2
第三個emit掛起了6s左右
11:20:52:217:start--------emit 3
11:20:56:716:end--------emit 3
第四個emit掛起了4s左右
11:20:57:219:start--------emit 4
11:21:1:717:end--------emit 4
第五個emit掛起了6s左右
11:21:2:218:start--------emit 5
11:21:6:720:end--------emit 5
代碼中job2在collect時候delay了5s,從emit的結果看,從emit 2開始從start emit到end emit中間大概執行了4s多的時間。extraBufferCapacity默認爲0
下面再測試一下,將extraBufferCapacity設置爲2:
var sharedflow = MutableSharedFlow<Int>(extraBufferCapacity = 2)
測試結果:
emit沒有掛起
11:24:47:843:start--------emit 1
11:24:47:848:end--------emit 1
emit沒有掛起
11:24:48:354:start--------emit 2
11:24:48:356:end--------emit 2
emit沒有掛起
11:24:48:860:start--------emit 3
11:24:48:861:end--------emit 3
emit掛起3s多
11:24:49:365:start--------emit 4
11:24:52:852:end--------emit 4
emit掛起6s多
11:24:53:355:start--------emit 5
11:24:57:857:end--------emit 5
從結果看emit1 到 emit3 的emit沒有耗費太多的時間,從emit4開始中間分別耗費了3s,6s。從demo的測試來看當緩衝區滿了之後執行了emit執行了掛起操作。
3.測試onBufferOverflow值對sharedflow的影響,修改onBufferOverflow = BufferOverflow.DROP_OLDEST,這個意思是丟棄最先的值
fun `sharedflow emit suspend`() = runBlocking{
var sharedflow = MutableSharedFlow<Int>(replay = 1,extraBufferCapacity = 0,onBufferOverflow = BufferOverflow.DROP_OLDEST)
val job1 = GlobalScope.launch {
sharedflow.collect{
println("${gettime()}:first job: $it")
}
}
val job2 = GlobalScope.launch {
sharedflow.collect{
delay(5000)
println("${gettime()}:second job: $it")
}
}
val job3 = GlobalScope.launch {
for (i in 1..5){
delay(500)
//println("${gettime()}:start--------emit $i")
sharedflow.emit(i)
// println("${gettime()}:end--------emit $i")
}
}
job1.join()
job2.join()
job3.join()
}
運行結果:
1:20:26:627:first job: 1
1:20:27:129:first job: 2
1:20:27:637:first job: 3
1:20:28:140:first job: 4
1:20:28:643:first job: 5
1:20:31:632:second job: 1
1:20:36:639:second job: 5
從上面運行結果看job1執行完成了,job2只輸出了第一個值和最後一個值。job2將2,3,4幾個值都丟失了。
onBufferOverflow != BufferOverflow.SUSPEND時,replay和extraBufferCapacity不能同時爲0(默認值),否則運行時會出現如下錯誤:
replay or extraBufferCapacity must be positive with non-default onBufferOverflow strategy DROP_OLDEST
java.lang.IllegalArgumentException: replay or extraBufferCapacity must be positive with non-default onBufferOverflow strategy DROP_OLDEST
at kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow(SharedFlow.kt:246)
at com.example.flowdemo.ExampleUnitTest$sharedflow emit suspend$1.invokeSuspend(ExampleUnitTest.kt:165)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.example.flowdemo.ExampleUnitTest.sharedflow emit suspend(ExampleUnitTest.kt:164)
將onBufferOverflow 的值改爲DROP_LATEST
var sharedflow = MutableSharedFlow<Int>(replay = 1,extraBufferCapacity = 0,onBufferOverflow = BufferOverflow.DROP_LATEST)
運行結果:
1:36:44:978:first job: 1
1:36:45:481:first job: 2
1:36:49:983:second job: 1
1:36:54:989:second job: 2
從結果看只輸出去了前兩個值,後面的值全部丟掉了。
4.sharedflow是不防抖的,即如果連續放入相同的值,那麼每個值collect都會響應一次。
fun `sharedflow shake`()= runBlocking{
val sharedflow = MutableSharedFlow<String>()
val job1 = GlobalScope.launch {
sharedflow.collect {
println("job---$it")
}
}
val job2 = GlobalScope.launch {
for (i in 1..5){
delay(100)
sharedflow.emit("hello laworks")
}
}
job1.join()
job2.join()
}
運行結果
job---hello laworks
job---hello laworks
job---hello laworks
job---hello laworks
job---hello laworks
這個和stateflow做一個對比
@Test
fun `stateflow shake`()= runBlocking{
val stateflow = MutableStateFlow("state init")
val job1 = GlobalScope.launch {
stateflow.collect {
println("job---$it")
}
}
val job2 = GlobalScope.launch {
for (i in 1..5){
delay(100)
stateflow.emit("I'am stateflow")
}
}
job1.join()
job2.join()
}
運行結果:
job---state init
job---I'am stateflow
從上面對比的結果可以看出stateflow是防抖的。
stateFlow
sharedflow是不防抖的,但是stateflow是防抖的,從上面的例子可以看出,下面是源碼的分析:
源碼如下:
synchronized(this) {
val oldState = _state.value
if (expectedState != null && oldState != expectedState) return false // CAS support
/*********return if equal*********/
if (oldState == newState) return true // Don't do anything if value is not changing, but CAS -> true
_state.value = newState
curSequence = sequence
if (curSequence and 1 == 0) { // even sequence means quiescent state flow (no ongoing update)
curSequence++ // make it odd
sequence = curSequence
} else {
// update is already in process, notify it, and return
sequence = curSequence + 2 // change sequence to notify, keep it odd
return true // updated
}
curSlots = slots // read current reference to collectors under lock
}
從源碼上可以看出來,當值沒有變化的時候就直接return了,不會做任何的處理。這也就是爲什麼stateflow是防抖的。
下面看看關於stateflow值丟失的問題:
@Test
fun `stateflow suspend`() = runBlocking {
val stateflow = MutableStateFlow(0)
val job1 = GlobalScope.launch {
stateflow.collect{
println("job1----$it")
}
}
val job2 = GlobalScope.launch {
stateflow.collect{
delay(5000)
println("job2----$it")
}
}
val job3 = GlobalScope.launch {
for(i in 1..5){
//delay(500)
println("${gettime()}:start--------emit $i")
stateflow.emit(i)
println("${gettime()}:end--------emit $i")
}
}
job1.join()
job2.join()
job3.join()
}
輸入的結果如下:
10:3:3:681:start--------emit 1
job1----0
10:3:3:684:end--------emit 1
job1----1
10:3:3:684:start--------emit 2
10:3:3:684:end--------emit 2
10:3:3:684:start--------emit 3
10:3:3:684:end--------emit 3
10:3:3:684:start--------emit 4
10:3:3:684:end--------emit 4
10:3:3:684:start--------emit 5
10:3:3:685:end--------emit 5
job1----5
job2----0
job2----5
結果看job1分別輸出了0,1,5. job2分別輸出了0和5. 並且emit很快就完成了,這是爲什麼?爲什麼job1沒有輸出所有的值,job2的延遲也沒有像sharedflow一樣將emit掛起?
首先說stateflow沒有buffer的概念,它只能存一個值,所以在有值的時候就會去更新。再看看collect的實現:
public 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)
})
從上面的方法可以看出flow的collect方法會繼續調用帶參數的collect方法,參數裏面的emit方法就是我們外面的實現。
在看看帶參數的collect在stateflow裏面的實現:
override suspend fun collect(collector: FlowCollector<T>) {
val slot = allocateSlot()
try {
if (collector is SubscribedFlowCollector) collector.onSubscription()
val collectorJob = currentCoroutineContext()[Job]
var oldState: Any? = null // previously emitted T!! | NULL (null -- nothing emitted yet)
// The loop is arranged so that it starts delivering current value without waiting first
while (true) {
....
if (oldState == null || oldState != newState) {
//emit is the user action
collector.emit(NULL.unbox(newState))
oldState = newState
}
....
}
} finally {
freeSlot(slot)
}
}
從代碼中可以看出:
1.這個方法裏面是一個無限循環
2.這個裏面在不斷的取值,並和之前的值做對比,如果一樣的話那麼就不會再調用emit方法了。
在這個方法裏面假設collector.emit(NULL.unbox(newState))
執行的時間很長,那麼就有可能出現stateflow的值變化之後沒有及時取的情況。