Kotlin Flow 三 StateFlow 和 SharedFlow

StateFlow

StateFlow 和 LiveData 差不多,都是可觀察的數據容器。在 StateFlow 中任何數據的發送,它的每一個接收器都能接收到。在 StateFlow 和 SharedFlow 中收集器也可以被稱為訂閱者,不過這個訂閱者會掛起當前協程,而且永遠不會結束。

private val state = MutableStateFlow(1)

suspend fun simpleStateFlow() {
    coroutineScope {
        launch {
            delay(1000)
            state.collect {
                println("before state value $it")
            }
        }
        launch {
            for (i in 1..100) {
                state.emit(i)
                delay(100)
            }
        }

        launch {
            state.collect {
                println("state value $it")
            }
        }
    }
}

需要注意的是 collect 是一個掛起函數,所以一旦調用 collect 協程就會被掛起,所以上述的例子中在一個協程中發送數據,在兩個協程中接收數據。

LiveData 不同的在于, LiveData 不需要初始值,但 StateFlow 需要。

LiveData 會與 Activity 綁定,當 View 進入 STOPED 狀態時, LiveData.observer() 會自動取消注冊,而從 StateFlow 或任意其他數據流收集數據的操作并不會停止。如需實現相同的行為,需要從 Lifecycle.repeatOnLifecycle 塊收集數據流。

StateFlow熱流,并不是冷流。并且 StateFlowcollect 收不到調用之前發射的數據。

val state = MutableStateFlow(1)
coroutineScope {
    launch {
        for (i in 0..10) {
            state.emit(i)
            delay(1000)
        }
    }
                                        
    launch {
        delay(2000)
        state.collect {
            println("receive state $it")
        }
    }
}

可以看到最終的結果是:

receive state 2
receive state 3
receive state 4
receive state 5
receive state 6
receive state 7
receive state 8
receive state 9
receive state 10

因為在接受之前 delay 了 2s,所以最后是從 2 開始接收的。

把普通的 Flow 轉化成 StateFlow

val flow = flow {
    for (i in 0..4) {
        emit(i)
        delay(100)
    }
}
coroutineScope {
    val stateFlow = flow.stateIn(this)
    launch {
        stateFlow.collect {
            println("receive flow.stateIn value $it")
        }
    }
}

我們同樣可以像 LiveData 一樣直接獲取它的值。

stateFlow.value

StateFlow 分為 StateFlowMutableStateFlow 。就像 LiveDataMutableLiveData 一樣。 StateFlow 只能接收數據,不能發送數據,而 MutableStateFlow 即可以發送也可以接收。

private suspend fun simpleStateFlowAndMutableStateFlow() {
    val mutableStateFlow = MutableStateFlow(1)
    coroutineScope {
        launch {
            collectData(mutableStateFlow.asStateFlow())
        }
        launch {
            (1..10).forEach {
                delay(100)
                mutableStateFlow.emit(it)
            }
        }
    }
}

如上代碼所述,可以將 MutableStateFlow 通過 asStateFlow 轉換成 StateFlow

StateFlow 中給我們提供了一個協程安全的并發修改 StateFlow 中的值的方法 compareAndSet 。該方法能夠保證原子的修改 StateFlow 的值。該方法是通過 CAS 來修改值。

public fun compareAndSet(expect: T, update: T): Boolean

將當前的值和期待的值進行比較,如果相等則更新當前的值,并返回 true,如果不相等則返回 false。這里的比較并修改是原子性的。

SharedFlow

SharedFlowStateFlow 相比,他有緩沖區區,并可以定義緩沖區的溢出規則,已經可以定義給一個新的接收器發送多少數據的緩存值。

SharedFlow 同樣有與之對應的 MutableSharedFlowMutableSharedFlow 的參數如下:

  • replay 給一個新的訂閱者發送的緩沖區的數量。
  • extraBufferCapacity 除了 replay 的數量之外的緩沖區的大小。
  • onBufferOverflow 緩沖區溢出規則
    • SUSPEND 掛起
    • DROP_OLDEST 移除舊的值
    • DROP_LATEST 移除新的值

SharedFlow 的緩沖區大于是 replay + extraBufferCapacity 。

注意相比于 MutableStateFlowMutableSharedFlow 不需要初始值。

suspend fun simpleSharedFlow() {
    val sharedFlow = MutableSharedFlow<Int>(
        replay = 5,
        extraBufferCapacity = 3,
    )
    coroutineScope {
        launch {
            sharedFlow.collect {
                println("collect1 received shared flow $it")
            }
        }
        launch {
            (1..10).forEach {
                sharedFlow.emit(it)
                delay(100)
            }
        }
        // wait a minute
        delay(1000)
        launch {
            sharedFlow.collect {
                println("collect2 received shared flow $it")
            }
        }
    }
}

同樣的,我們可以把普通的 Flow 轉換成 SharedFlow。

suspend fun simpleConvertToSharedFlow(started: SharingStarted) {
    var start = 0L
    // create normal flow
    val flow = (1..10).asFlow()
        .onStart { start = currTime() }
        .onEach {
            println("Emit $it ${currTime() - start}ms")
            delay(100)
        }
    // convert to shared flow
    // need coroutine scope
    coroutineScope {
        val sharedFlow = flow.shareIn(this, started, replay = 2)
        delay(400)
        launch {
            println("current time ")
            sharedFlow.collect {
                println("received convert shared flow $it at ${currTime() - start}ms")
            }
        }
    }
}

這里的轉換有些復雜,可以看到我們通過 shareIn 可以將普通的 flow 轉換成 SharedFlow 。可以看到 sharedIn 有三個參數:

  • CoroutineScope - sharing 的協程的作用域。
  • SharingStarted - 啟動模式
    • Eagerly 迫切的,渴望的,在轉換完成后立即開始 sharing 數據,當上游的數據超過 replay 的時候,前面的數據就會被丟棄,相當于 DROP_OLDEST
    • Lazily 當有第一個訂閱者(調用 collect)的時候開始發射數據。
    • WhileSubscribed 當第一個訂閱者出現的時候立即開始,當最后一個訂閱者消失的時立即停止(默認情況下),replay 數量的緩存值將永遠保留(默認情況下)。這是一個函數,可以通過參數來控制當最后一個訂閱者消失時的行為,以及緩存的有效期。
      • stopTimeoutMillis - 配置最后一個訂閱者消失后 sharing flow 停止的延時。
      • replayExpirationMillis - 配置 sharing flow 協程的停止和重置緩沖區之間的間隔,單位是毫秒,默認值為 Long.MAX_VALUE 緩存永遠都不重置,0 表示立即重置緩存。比較難懂可以看看下面的例子。
  • replay 當訂閱的時候回復的數量。

如果上面的函數中傳遞的是 Eagerly ,其輸出如下:

Emit 1 2ms
Emit 2 109ms
Emit 3 213ms
Emit 4 313ms
current time 
received convert shared flow 2 at 412ms
received convert shared flow 3 at 412ms
Emit 5 413ms
received convert shared flow 4 at 414ms
Emit 6 518ms
received convert shared flow 5 at 519ms
Emit 7 619ms
received convert shared flow 6 at 619ms
Emit 8 720ms
received convert shared flow 7 at 720ms
Emit 9 822ms
received convert shared flow 8 at 823ms
Emit 10 926ms
received convert shared flow 9 at 926ms
received convert shared flow 10 at 1027ms

如果傳入的是 Lazily ,其輸入如下:

current time 
Emit 1 2ms
Emit 2 105ms
received convert shared flow 1 at 106ms
Emit 3 209ms
received convert shared flow 2 at 209ms
Emit 4 313ms
received convert shared flow 3 at 313ms
Emit 5 415ms
received convert shared flow 4 at 415ms
Emit 6 518ms
received convert shared flow 5 at 518ms
Emit 7 622ms
received convert shared flow 6 at 622ms
Emit 8 725ms
received convert shared flow 7 at 725ms
Emit 9 826ms
received convert shared flow 8 at 826ms
Emit 10 932ms
received convert shared flow 9 at 932ms
received convert shared flow 10 at 1032ms

很明顯能夠看出兩者的區別。

下面看看 WhileSubscribed ,這種方式非常靈活。

fun currTime() = System.currentTimeMillis()

suspend fun simpleConvertToSharedFlow(started: SharingStarted) {
    var start = 0L
    // create normal flow
    val flow = (1..10).asFlow()
        .onStart { start = currTime() }
        .onEach {
            println("Emit $it ${currTime() - start}ms")
            delay(100)
        }
    // convert to shared flow
    // need coroutine scope
    coroutineScope {
        val sharedFlow = flow.shareIn(this, started, replay = 2)
        val job = launch {
            println("current time ")
            sharedFlow.collect {
                println("received convert shared flow $it at ${currTime() - start}ms")
            }
        }

        launch {
            delay(1000L)
            job.cancel()
            delay(110L)
            sharedFlow.collect {
                println("received again shared flow $it")
            }
            println("shared flow has stop")
        }
    }
}

@OptIn(ExperimentalTime::class)
suspend fun main() {
//    simpleSharedFlow()
    simpleConvertToSharedFlow(
        SharingStarted.WhileSubscribed(
            stopTimeout = 100L.toDuration(DurationUnit.MILLISECONDS),
            replayExpiration = 200L.toDuration(DurationUnit.MILLISECONDS)
        )
    )
}

這里配置當最后一個訂閱者消失時 delay 100ms 后停止 sharing flow,在 sharing flow 停止后 200ms 后讓緩存失效。這里可以通過調整 job.cancel 后的 delay 函數的時長來看看效果。當時間為 110ms 時,會重新接受到緩存 9 和 10,并重新開始 sharing flow,如果參數調整為 320ms 時,緩存會失效,會直接重新開始 sharing flow。

110ms 的結果:

current time 
Emit 1 1ms
Emit 2 107ms
received convert shared flow 1 at 108ms
Emit 3 211ms
received convert shared flow 2 at 211ms
Emit 4 315ms
received convert shared flow 3 at 315ms
Emit 5 417ms
received convert shared flow 4 at 417ms
Emit 6 521ms
received convert shared flow 5 at 521ms
Emit 7 623ms
received convert shared flow 6 at 624ms
Emit 8 727ms
received convert shared flow 7 at 727ms
Emit 9 829ms
received convert shared flow 8 at 829ms
Emit 10 933ms
received convert shared flow 9 at 933ms
received again shared flow 9
received again shared flow 10
Emit 1 0ms
Emit 2 105ms
received again shared flow 1
Emit 3 210ms
received again shared flow 2
Emit 4 314ms
received again shared flow 3
Emit 5 415ms
received again shared flow 4
Emit 6 519ms
received again shared flow 5
Emit 7 620ms
received again shared flow 6
Emit 8 721ms
received again shared flow 7
Emit 9 826ms
received again shared flow 8
Emit 10 927ms
received again shared flow 9
received again shared flow 10

320ms 的結果:

current time 
Emit 1 1ms
Emit 2 106ms
received convert shared flow 1 at 106ms
Emit 3 210ms
received convert shared flow 2 at 210ms
Emit 4 314ms
received convert shared flow 3 at 314ms
Emit 5 414ms
received convert shared flow 4 at 414ms
Emit 6 517ms
received convert shared flow 5 at 517ms
Emit 7 623ms
received convert shared flow 6 at 623ms
Emit 8 726ms
received convert shared flow 7 at 727ms
Emit 9 827ms
received convert shared flow 8 at 827ms
Emit 10 931ms
received convert shared flow 9 at 931ms
Emit 1 0ms
Emit 2 105ms
received again shared flow 1
Emit 3 209ms
received again shared flow 2
Emit 4 315ms
received again shared flow 3
Emit 5 418ms
received again shared flow 4
Emit 6 523ms
received again shared flow 5
Emit 7 627ms
received again shared flow 6
Emit 8 732ms
received again shared flow 7
Emit 9 833ms
received again shared flow 8
Emit 10 937ms
received again shared flow 9
received again shared flow 10

我們看下面這段源碼就會很快明白:

override fun command(subscriptionCount: StateFlow<Int>): Flow<SharingCommand> = subscriptionCount
    .transformLatest { count ->
        if (count > 0) {
            emit(SharingCommand.START)
        } else {
            delay(stopTimeout)
            if (replayExpiration > 0) {
                emit(SharingCommand.STOP)
                delay(replayExpiration)
            }
            emit(SharingCommand.STOP_AND_RESET_REPLAY_CACHE)
        }
    }
    .dropWhile { it != SharingCommand.START } // don't emit any STOP/RESET_BUFFER to start with, only START
    .distinctUntilChanged() // just in case somebody forgets it, don't leak our multiple sending of START
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容