この記事は主にコールドフローとホットフローの関連実現原理を分析しますが、原理ロジックは長くて複雑です。特に、熱流 SharedFlow の関連実装原理に関しては、ロジックがより抽象的で理解しにくくなります。この記事は比較的長いです。カタログに従ってセクションに分けて読むことをお勧めします。最初に基本概念とコールド フローを見てから、ホット フローの SharedFlow と StateFlowをそれぞれ見ていきます。
この記事を読むと、次のような疑問が湧いてきます。
- 寒流と熱流とは何を指しますか?
- ビジネス開発において、コールド フローとホット フローは何に使用できるか、または解決できるでしょうか?
- コールドフローとホットフローの違いは何ですか?
- コールドフローの動作原理は何ですか?
- SharedFlow は送信するデータをどのように管理しますか?
- SharedFlow はサブスクライバーをどのように管理しますか?
- StateFlow と LiveData の違いは何ですか?
コールド フローであろうとホット フローであろうと、ビジネスに役立つテクノロジーはすべて、次のようなビジネス開発における実際的な問題を解決する必要があります。
- コルーチンとコールド フローは、レスポンシブ プログラミングの
RxJava
フレームワーク。Kotlin プロジェクトでは、コルーチンとコールド フローを使用すると、RxJava を使用するよりも多くの利点があります。 - SharedFlow は、 を置き換えるイベント バスとして使用できます
EventBus
。 LiveData
Hot Flow StateFlow は、イベント状態の更新、置換、および結合MVI
置換に使用できますMVVM
。
この記事に誤りがある場合は、時間内に修正されます。ようこそお読みください。
基本的な考え方
前の記事: Kotlin Flow の探索から、Kotlin Flow は Kotlin データ フローであり、データ フローにはプロバイダー (プロデューサー)、仲介者 (中間操作)、およびコンシューマー (消費者) を含める必要があることがわかりました。
- プロバイダー (プロデューサー): ソース データ、データ フローにデータを追加します。
- 仲介者 (中間操作): データ ストリームに送信される値を変更したり、データ ストリーム自体を変更したりできます。
- Consumer (Consumer): 結果データ。データ ストリーム内の値を消費します。
flow
.operator1()
.operator2()
.operator3()
.collect(consumer)
データ ストリームを作成するには、Kotlin 拡張関数flowOf
、、を使用できますasFlow
。flow{}
flowOf(1, 2, 3).map { it * it }.collect {}
(1..3).asFlow().map { it % 2 == 0 }.collect {}
flow<Int> {
emit(1)
emit(2)
emit(3)
}.map { it * 2 }.collect {}
上記のデータフローの作成方法では、中間操作はコンシューマー(消費者)、つまりコレクターが存在する必要があるcollect{}
場合にこれはKotlin Sequences
と同じです。
データ ストリームを作成する上記の方法に加えて、SharedFlow
と をStateFlow
。
class TestFlow {
private val _sharedFlow = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.SUSPEND
)
val sharedFlow: SharedFlow<Int> = _sharedFlow
fun testSharedFlow() {
MainScope().launch {
Log.e("Flow", "sharedFlow:emit 1")
_sharedFlow.emit(1)
Log.e("Flow", "sharedFlow:emit 2")
_sharedFlow.emit(2)
}
}
private val _stateFlow = MutableStateFlow<Int>(value = 1)
val stateFlow: SharedFlow<Int> = _stateFlow
fun testStateFlow() {
MainScope().launch {
_stateFlow.value = 1
}
}
}
と を使用してストリームを作成する場合、コレクターが存在しないか、複数のコレクターが存在する可能性があります。コレクターとは独立して存在し、コンシューマーなどによって終了されません。SharedFlow
StateFlow
collect{}
collect{}
asList
asSet
testFlow.testSharedFlow()
testFlow.testStateFlow()
控制台输出结果:
Flow com.wangjiang.example E sharedFlow:emit 1
Flow com.wangjiang.example E sharedFlow:emit 2
Flow com.wangjiang.example E stateFlow:value 1
collect{}
コレクタがない場合でも、ShareFlow と StateFlow が実行されていることがわかります。以下にコレクターを追加してcollect{}
、もう一度見てみましょう。
lifecycleScope.launch {
testFlow.sharedFlow.collect {
Log.e("Flow", "SharedFlow Collect1: value=$it")
}
}
lifecycleScope.launch {
testFlow.sharedFlow.collect {
Log.e("Flow", "SharedFlow Collect2: value=$it")
}
}
testFlow.testSharedFlow()
lifecycleScope.launch {
testFlow.stateFlow.collect {
Log.e("Flow", "StateFlow Collect1: value=$it")
}
}
lifecycleScope.launch {
testFlow.stateFlow.collect {
Log.e("Flow", "StateFlow Collect2: value=$it")
}
}
testFlow.testStateFlow()
控制台输出结果:
Flow com.wangjiang.example E StateFlow Collect1: value=1
Flow com.wangjiang.example E StateFlow Collect2: value=1
Flow com.wangjiang.example E sharedFlow:emit 1
Flow com.wangjiang.example E SharedFlow Collect1: value=1
Flow com.wangjiang.example E SharedFlow Collect2: value=1
Flow com.wangjiang.example E sharedFlow:emit 2
Flow com.wangjiang.example E SharedFlow Collect1: value=2
Flow com.wangjiang.example E SharedFlow Collect2: value=2
の場合SharedFlow
、これはイベント サブスクライバにイベントを配布し、イベントを共有するイベント バスに似ています。これはStateFlow
、イベントの最新ステータスを更新し、サブスクライバーにイベントの更新を通知する LiveData に似ています。
コールド フローとホット フローは簡単に区別できるようになりました。flowOf
、などを使用して作成されたデータ フローはコールド フローと呼ばれます。つまり、 を使用して作成されたデータ フローasFlow
は、コレクタから独立して存在することはできず、各データ フローにはコレクターが実行できるようにすることは、完全なデータ フローと呼ばれます。ヒート フローを使用して作成されたデータ フローは、コレクターから独立して存在することができ、コレクターが存在しないか、複数のコレクターが存在する可能性があります。flow{}
: Flow<T>
collect{}
collect{}
: SharedFlow<T>
: StateFlow<T>
collect{}
collect{}
冷たい流れ
フローはシーケンスのようなコールド フローであり、フローが収集されるまでフロー ビルダーのコードは実行されません。
フローはシーケンスと同じであり、ターミナル オペレーターが必要です。つまり、コレクターcollect{}
またはasList
その他asSet
の操作がある場合にのみ実行されます。
lifecycleScope.launch {
val flow = flow {
Log.e("Flow", "emit:1")
emit(1)
Log.e("Flow", "emit:2")
emit(2)
}.map {
Log.e("Flow", "map:$it")
it * it
}
flow.collect {
Log.e("Flow", "collect:$it")
}
}
控制台输出结果:
Flow com.wangjiang.example E emit:1
Flow com.wangjiang.example E map:1
Flow com.wangjiang.example E collect:1
Flow com.wangjiang.example E emit:2
Flow com.wangjiang.example E map:2
Flow com.wangjiang.example E collect:4
collect{}
を使用する場合は、データ生成を開始します: 値を出力しemit
、次に中間操作:map
変換を実行し、次にデータ消費: を実行しますcollect
。プロセス全体を通じて、データ フローは時系列順に発生します。つまり、emit:1
→ map:1
、→ 、→ではなく、 → → collect:1
、→ →です。emit:2
map:2
collect:4
emit:1
emit:2
map:1
map:2
collect:1
collect:4
例からコールド フローの実行原理を簡単に見てみましょう。
class TestFlow {
fun testColdFlow() {
MainScope().launch {
flow<Int> { emit(1) }.map { it * it }.collect {
Log.e("Flow", "testColdFlow", Throwable())
}
}
}
}
testColdFlow
メソッドを実行すると、コンソールにcollect
メソッド呼び出しスタック情報が出力されます。
Flow com.wangjiang.example E testColdFlow
java.lang.Throwable
at com.wangjiang.example.flow.TestFlow$testColdFlow$1$3.emit(TestFlow.kt:46)
at com.wangjiang.example.flow.TestFlow$testColdFlow$1$3.emit(TestFlow.kt:45)
at com.wangjiang.example.flow.TestFlow$testColdFlow$1$invokeSuspend$$inlined$map$1$2.emit(Emitters.kt:224)
at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15)
at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:87)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:66)
at com.wangjiang.example.flow.TestFlow$testColdFlow$1$1.invokeSuspend(TestFlow.kt:45)
at com.wangjiang.example.flow.TestFlow$testColdFlow$1$1.invoke(Unknown Source:14)
at com.wangjiang.example.flow.TestFlow$testColdFlow$1$1.invoke(Unknown Source:4)
at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:61)
at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:230)
at com.wangjiang.example.flow.TestFlow$testColdFlow$1$invokeSuspend$$inlined$map$1.collect(SafeCollector.common.kt:113)
at com.wangjiang.example.flow.TestFlow$testColdFlow$1.invokeSuspend(TestFlow.kt:45)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8668)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)
collect
上記のメソッド呼び出しスタック情報から、おおよその順序は→ AbstractFlow.collect
→ SafeFlow.collectSafely
→ map
→ SafeCollector.emit
→ emit
→であることがわかりますTestFlow$testColdFlow$1$3.emit
。(上記のログはソースコードに対応していません。ここでは AndroidStudio を通じて kotlin のクラス ファイルを表示できます。)
ここでは、中間操作マップの変換を理解することに焦点を当てます。
import kotlinx.coroutines.flow.unsafeTransform as transform
public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value ->
return@transform emit(transform(value))
}
internal inline fun <T, R> Flow<T>.unsafeTransform(
@BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = unsafeFlow { // Note: unsafe flow is used here, because unsafeTransform is only for internal use
collect { value ->
// kludge, without it Unit will be returned and TCE won't kick in, KT-28938
return@collect transform(value)
}
}
internal inline fun <T> unsafeFlow(@BuilderInference crossinline block: suspend FlowCollector<T>.() -> Unit): Flow<T> {
return object : Flow<T> {
override suspend fun collect(collector: FlowCollector<T>) {
collector.block()
}
}
}
上記のコード分析から、マップ プロセスは次のようになります。map
→ transform
→ unsafeTransform
→ unsafeFlow { }
→ collect {}
→ 执行上一个 flow
→ 拿到上一个 flow 的结果
→ @collect transform(value)
→ @transform emit(transform(value))
→ map变换操作结果
→ 到下一个 flow 或 消费者 collect
。
このプロセスを見ると、確かにフロー コールド フローの実行プロセスはKotin Sequence の実行プロセスの原理に似ています。
したがって、コールド フロー全体のトリガー プロセスは次のように簡単に要約できます。コンシューマー収集が中間操作をトリガーし、中間操作フィルター、マップ変換などがプロデューサーをトリガーし、プロデューサーが本番データを出力し、その後、変換のためにデータを中間オペレーションに渡し、最後に送信します。変換されたデータはコンシューマーに渡されます。これがコールドフロー実行の原理です。それと少し似ていて、下から上にトリガーされて、上から下に流れていく感覚です。
ビジネスシーン
: Flow<T>
ビジネス シナリオの場合、RxJava
これはレスポンシブ プログラミングでの使用に似ています。たとえば、次のようになります。
GlobalScope.launch(Dispatchers.Main) {
flowOf(bitmap).map { bmp ->
//在子线程中执行耗时操作,存储 bitmap 到本地
saveBitmap(bmp)
}.flowOn(Dispatchers.IO).collect { bitmapLocalPath ->
//在主线程中处理存储 bitmap 后的本地路地址
}
}
Kotlin プロジェクトでは、コルーチンとコールド フローを使用して、リアクティブ プログラミング用に RxJava を置き換えることができます。
要約する
コールド フローでは、データ プロデューサー、0 個以上の中間操作、およびデータ コンシューマーが一緒に完全なフローを構築する必要があります。実行原理はKotin Sequenceと似ており、コンシューマーコレクトやその他の端末操作がある場合、ストリームは下から上にトリガーされ始め、上から下に流れます。
熱の流れ
熱流は SharedFlow と StateFlow に分けられ、どちらもコレクターとは独立して存在します。
共有フロー
SharedFlow は、名前が示すように、ホット フローと呼ばれます。主な理由は、すべてのコレクターが発行する値を共有できること、共有方法がブロードキャストであること、そのインスタンスがコレクターの存在とは独立して存在できることです。ShredFlow を理解するには、その共有および独立した存在の意味を理解する必要があります。
SharedFlow の作成、送信、収集から以下を解析していきます。
作成
SharedFlowKt によって提供されるMutableSharedFlow
コンストラクター。
private val _sharedFlow = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.SUSPEND
)
val sharedFlow: SharedFlow<Int> = _sharedFlow
パラメータの意味:
replay
: 新しいサブスクライバがサブスクライブするときに、以前に送信された値の数が新しいサブスクライバに再送信されます (スティッキー データと同様)。extraBufferCapacity
: リプレイに加えて、キャッシュされた値の数。キャッシュ領域に値がまだある場合、発行は一時停止されません (発行が速すぎ、収集が遅すぎるため、発行データはキャッシュされます)。onBufferOverflow
:送信するデータ項目でバッファーがいっぱいになった場合の処理戦略を指定します (バッファーのサイズは、再生と extraBufferCapacity によって共同で決定されます)。デフォルトは BufferOverflow.SUSPEND ですが、(名前が示すように) BufferOverflow.DROP_LATEST または BufferOverflow.DROP_OLDEST にすることもできます。
public fun <T> MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
//.....省略
//缓存的值数量
val bufferCapacity0 = replay + extraBufferCapacity
val bufferCapacity = if (bufferCapacity0 < 0) Int.MAX_VALUE else bufferCapacity0
return SharedFlowImpl(replay, bufferCapacity, onBufferOverflow)
}
internal open class SharedFlowImpl<T>(
private val replay: Int,
private val bufferCapacity: Int,
private val onBufferOverflow: BufferOverflow
) : AbstractSharedFlow<SharedFlowSlot>(), MutableSharedFlow<T>, CancellableFlow<T>, FusibleFlow<T> {
//.....省略
}
MutableSharedFlow コンストラクターは、SharedFlowImpl
インスタンス。SharedFlowImpl クラスに関連付けられたクラスとインターフェイス間の単純な関係を見てみましょう:
上から下まで、各クラスまたはインターフェイスの役割は次のとおりです。
Flow 接口
: フローの消費操作に使用されます。つまり、サブスクライバーがpublic suspend fun collect(collector: FlowCollector<T>)
インターフェイスメソッド パラメーター コレクターはコールド フローまたはホット フローのコレクターであるため、フロー インターフェイスはFlowCollecter インターフェース。FlowCollecter 接口
: ストリームの収集に使用され、ストリームの終了操作または中間操作にすることができます。public suspend fun emit(value: T)
メソッドをパラメーター値は、データプロデューサーまたは中間操作の出力によって送信された値です。SharedFlow 接口
: フロー インターフェイスを継承し、public val replayCache: List<T>
新しいサブスクライバの (再生番号) 値キャッシュ スナップショットを表す属性を定義します。MutableSharedFlow 接口
: SharedFlow および FlowCollecter インターフェイスを継承すると、次collect(collecter: FlowCollecter<T>)
のようにすることもemit(value: T)
。CancellableFlow 接口
: 空のインターフェイスであるフロー インターフェイスを継承し、主にフローをキャンセルできること、つまり SharedFlow をキャンセルできることをマークします。CancellableFlowImpl 类
: Flow インターフェースのcollect(collector: FlowCollector<T>)
メソッド。FusibleFlow 接口
: BufferOverflow および flowOn オペレーションと連携して動作します。AbstractSharedFlow<S : AbstractSharedFlowSlot<*>> 抽象类
: サブスクライバの管理、クラスAbstractSharedFlowSlot
の受信、継承を担当します。SynchronizedObject
SynchronizedObject 类
: コルーチンは、ロックsynchronized(lock: SynchronizedObject, block: () -> T)
メソッド。AbstractSharedFlowSlot<SharedFlowImpl<*>> 抽象类
:fun allocateLocked(flow: F): Boolean
およびfun freeLocked(flow: F): Array<Continuation<Unit>?>
、それぞれサブスクライバに関連付けられた SharedFlowSlot のアプリケーションとリリースを表します。SharedFlowSlot 类
: AbstractSharedFlowSlot クラスを継承し、メソッドallocateLocked
とfreeLocked
抽象メソッドを実装し、属性var index = -1L
とをvar cont: Continuation<Unit>? = null
。ここで、index はキャッシュ配列内で処理されるデータのインデックスを示し、cont は新しいデータの送信を待機するサブスクライバーの継続を示します (ラップされたサブスクリプション)に);BufferOverflow 枚举类
: フロー内のバッファ オーバーフロー処理戦略。列挙値は、バッファがいっぱいになったときに値の送信または送信のアップストリームが一時停止されることSUSPEND
を示します列挙値DROP_OLDEST
は、オーバーフロー時にバッファ内の最も古い値が削除されることを示し、新しい値は値はキャッシュ領域に追加されます。一時停止しません。列挙値は、バッファがオーバーフローしたときにバッファに現在追加されている最新の値を削除し(バッファの内容は変更されないままになります)、一時停止しないことDROP_LATEST
を意味します。SharedFlowImpl 类
: 抽象クラスを継承し、AbstractSharedFlow<SharedFlowSlot>
インターフェイスを実装する実際の SharedFlow 実装クラスMutableSharedFlow<T>, CancellableFlow<T>, FusibleFlow<T>
。
上記の情報から、SharedFlow の作成後に提供される機能は次のように要約できます: データのemit
送信、送信時にデータ キャッシュが関与すること、バッファ オーバーフロー戦略、一時停止されるかどうかなど。collect
サブスクライブ、サブスクライブを使用します。これには、サブスクライバーの管理、データの取得、一時停止されるかどうかなどの問題が含まれます。
排出
SharedFlowImpl
このクラスはFlowCollector
、emit
emit メソッドが呼び出されたときに、インターフェイスのメソッドを実装します。
collect
現在SharedFlow および onBufferOverflow にサブスクライバが存在する場合、この呼び出しは一時停止される可能性があります。 BufferOverflow = BufferOverflow.SUSPEND;collect
現在SharedFlow を使用しているサブスクライバがいない場合、バッファは使用されません。restart != 0 の場合、最後に発行された値は単純にリプレイ キャッシュに格納され、リプレイ キャッシュ内の古い要素と置き換えられます。replay=0 の場合、最後に発行された値は破棄されます。- 放出メソッドは
suspend
変更されており、suspend
それに関連する未変更のメソッドがありますtryEmit
。 - このメソッドはスレッドセーフであり、外部同期なしで同時コルーチンから安全に呼び出すことができます。
Emit メソッドの呼び出しがハングする場合がある
まず、emit
メソッドの実装を見てください。
override suspend fun emit(value: T) {
if (tryEmit(value)) return // fast-path
emitSuspend(value)
}
Emit メソッドが一時停止されるかどうかは、主にtryEmit(value)
メソッドの。true が返された場合は、実行されませんemitSuspend(value)
。つまり、一時停止されません。それ以外の場合emitSuspend(value)
、Emit は一時停止されます。
まず、emit メソッドがハングしない例とハングする例を見てみましょう。
不会挂起,调用的是 tryEmit 方法
class TestFlow {
private val _sharedFlow = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.SUSPEND
)
val sharedFlow: SharedFlow<Int> = _sharedFlow
fun testSharedFlow() {
MainScope().launch {
Log.e("Flow", "sharedFlow:emit 1")
_sharedFlow.emit(1)
}
}
}
lifecycleScope.launch {
testFlow.sharedFlow.collect(object : FlowCollector<Int> {
override suspend fun emit(value: Int) {
Log.e("Flow", "SharedFlow Collect: value=$value", Throwable())
}
})
}
控制台输出日志:
Flow com.wangjiang.example E sharedFlow:emit 1
Flow com.wangjiang.example E SharedFlow Collect: value=1
java.lang.Throwable
at com.wangjiang.example.fragment.TestFlowFragment$initView$5$1.emit(TestFlowFragment.kt:76)
at com.wangjiang.example.fragment.TestFlowFragment$initView$5$1.emit(TestFlowFragment.kt:174)
at kotlinx.coroutines.flow.SharedFlowImpl.collect$suspendImpl(SharedFlow.kt:383)
at kotlinx.coroutines.flow.SharedFlowImpl$collect$1.invokeSuspend(Unknown Source:15)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:190)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:161)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
at kotlinx.coroutines.flow.SharedFlowImpl.tryEmit(SharedFlow.kt:400)
at kotlinx.coroutines.flow.SharedFlowImpl.emit$suspendImpl(SharedFlow.kt:405)
at kotlinx.coroutines.flow.SharedFlowImpl.emit(Unknown Source:0)
at com.wangjiang.example.flow.TestFlow$testSharedFlow$1.invokeSuspend(TestFlow.kt:20)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8668)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)
上記の MutableSharedFlow 構築メソッドを にextraBufferCapacity = 1
変更しextraBufferCapacity = 0
、その他は変更しないでください。
会挂起,调用的是 emitSuspend 方法
控制台输出日志:
Flow com.wangjiang.example E sharedFlow:emit 1
Flow com.wangjiang.example E SharedFlow Collect: value=1
java.lang.Throwable
at com.wangjiang.example.fragment.TestFlowFragment$initView$5$1.emit(TestFlowFragment.kt:76)
at com.wangjiang.example.fragment.TestFlowFragment$initView$5$1.emit(TestFlowFragment.kt:174)
at kotlinx.coroutines.flow.SharedFlowImpl.collect$suspendImpl(SharedFlow.kt:383)
at kotlinx.coroutines.flow.SharedFlowImpl$collect$1.invokeSuspend(Unknown Source:15)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:190)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:161)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
at kotlinx.coroutines.flow.SharedFlowImpl.emitSuspend(SharedFlow.kt:504)
at kotlinx.coroutines.flow.SharedFlowImpl.emit$suspendImpl(SharedFlow.kt:406)
at kotlinx.coroutines.flow.SharedFlowImpl.emit(Unknown Source:0)
at com.wangjiang.example.flow.TestFlow$testSharedFlow$1.invokeSuspend(TestFlow.kt:20)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8668)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)
上記 2 つのログの違いは次のとおりです。
emit
→suspendImpl
→tryEmit
→CancellableContinuationImpl.resumeWith
→DispatchedTaskKt.resume
→TestFlowFragment$initView$5$1.emit
emit
→suspendImpl
→emitSuspend
→CancellableContinuationImpl.resumeWith
→DispatchedTaskKt.resume
→TestFlowFragment$initView$5$1.emit
出力ログの比較から、onBufferOverflow
ポリシーBufferOverflow.SUSPEND
が の場合、キャッシュ領域extraBufferCapacity
に値がある場合、発行は一時停止されません。それ以外の場合、発行は一時停止されます。したがって、と の値がメソッドonBufferOverflow
推測できるようになりました。extraBufferCapacity
tryEmit
override fun tryEmit(value: T): Boolean {
var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
val emitted = synchronized(this) {
if (tryEmitLocked(value)) {
resumes = findSlotsToResumeLocked(resumes)
true
} else {
false
}
}
for (cont in resumes) cont?.resume(Unit)
return emitted
}
private fun tryEmitLocked(value: T): Boolean {
// Fast path without collectors -> no buffering
if (nCollectors == 0) return tryEmitNoCollectorsLocked(value) // always returns true
// With collectors we'll have to buffer
// 如果缓存已满且订阅者消费慢时,不能直接给订阅者值
if (bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex) {
when (onBufferOverflow) {
BufferOverflow.SUSPEND -> return false // will suspend
BufferOverflow.DROP_LATEST -> return true // just drop incoming
BufferOverflow.DROP_OLDEST -> {} // force enqueue & drop oldest instead
}
}
enqueueLocked(value)
bufferSize++ // value was added to buffer
// drop oldest from the buffer if it became more than bufferCapacity
if (bufferSize > bufferCapacity) dropOldestLocked()
// keep replaySize not larger that needed
if (replaySize > replay) { // increment replayIndex by one
updateBufferLocked(replayIndex + 1, minCollectorIndex, bufferEndIndex, queueEndIndex)
}
return true
}
tryEmit メソッドの戻り値は、emitted
放出された の値に依存し、さらに放出された の値もtryEmitLocked
メソッド。tryEmitLocked の戻り値が false かどうかは、次の条件によって異なります。
if (bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex) {
when (onBufferOverflow) {
BufferOverflow.SUSPEND -> return false // will suspend
BufferOverflow.DROP_LATEST -> return true // just drop incoming
BufferOverflow.DROP_OLDEST -> {} // force enqueue & drop oldest instead
}
}
フィールド: bufferSize
、bufferCapacity
、minCollectorIndex
、replayIndex
、これらはすべて SharedFlowImp のグローバル変数です。
private class SharedFlowImpl<T>(
private val replay: Int, //新订阅者订阅时,重新发送多少个之前已发出的值给新订阅者
private val bufferCapacity: Int, // replay+extraBufferCapacity,缓存容量
private val onBufferOverflow: BufferOverflow //缓存溢出策略
) : AbstractSharedFlow<SharedFlowSlot>(), MutableSharedFlow<T>, CancellableFlow<T>, FusibleFlow<T> {
/*
Logical structure of the buffer 缓存逻辑结构
buffered values
/-----------------------\
replayCache queued emitters
/----------\/----------------------\
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | 1 | 2 | 3 | 4 | 5 | 6 | E | E | E | E | E | E | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
^ ^ ^ ^
| | | |
head | head + bufferSize head + totalSize
| | |
index of the slowest | index of the fastest
possible collector | possible collector
| |
| replayIndex == new collector's index
\---------------------- /
range of possible minCollectorIndex
head == minOf(minCollectorIndex, replayIndex) // by definition
totalSize == bufferSize + queueSize // by definition
INVARIANTS:
minCollectorIndex = activeSlots.minOf { it.index } ?: (head + bufferSize)
replayIndex <= head + bufferSize
*/
// Stored state 缓存存储状态
private var buffer: Array<Any?>? = null // 缓存数组,用于保存 emit 发送的数据
private var replayIndex = 0L // 新订阅者从 replayCache 中获取数据的起始位置
private var minCollectorIndex = 0L // 当前活跃订阅者从缓存数组中获取数据时,对应的位置最小索引
private var bufferSize = 0 // 缓存数组中 buffered values 的大小
private var queueSize = 0 // 缓存数组中 queued emitters 的大小
// Computed state 缓存计算状态
private val head: Long get() = minOf(minCollectorIndex, replayIndex) // 缓存数组的起始位置
private val replaySize: Int get() = (head + bufferSize - replayIndex).toInt() // 缓存数组中 replay 的大小
private val totalSize: Int get() = bufferSize + queueSize // 缓存数组中已经缓存的数据数量
private val bufferEndIndex: Long get() = head + bufferSize// 缓存数组中 buffered values 的结尾位置的后一位索引,也就是 queued emitters 的起始位置
private val queueEndIndex: Long get() = head + bufferSize + queueSize 缓存数组中 queued emitters 的结尾位置的后一位索引
上記の SharedFlowImpl のキャッシュ ロジック構造を次のものと組み合わせたものです。
MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 1 或 0,
onBufferOverflow = BufferOverflow.SUSPEND
)
extraBufferCapacity = 1 の場合、emit メソッドを呼び出してデータを放出すると、bufferSize=0、bufferCapacity=1、minCollectorIndex=0、replayIndex=0 となるため、falsebufferSize >= bufferCapacity && minCollectorIndex <= replayIndex
となる 。
extraBufferCapacity = 0 の場合、emit メソッドを呼び出してデータを出力します。この時点では、bufferSize=0、bufferCapacity=0、minCollectorIndex=0、replayIndex=0 であるため truebufferSize >= bufferCapacity && minCollectorIndex <= replayIndex
ですBufferOverflow.SUSPEND
ます, そのため、emitSuspend が実行され、エミットが一時停止されます。
そのため、emit メソッドの呼び出しがハングする可能性があります。実は、bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex
判定条件を満たすということはバッファオーバーフローを意味するので、このときの処理方針を BufferOverflow.SUSPEND 、 BufferOverflow.DROP_LATEST 、 BufferOverflow.DROP_OLDEST のいずれにするか選択する必要があります。
キャッシュ領域
Emit によって発行された値は、バッファ配列buffer
に。
private var buffer: Array<Any?>? = null // 缓存数组,用于保存 emit 发送的数据
バッファには以下が含まれます:buffered values
とqueued emitters
。
buffered values
Emit(value) メソッドに格納される値、バッファリングされた値のサイズは に依存しbufferCapacity=replay + extraBufferCapacity
、replay と extraBufferCapacity は create にMutableSharedFlow( replay: Int = 0, extraBufferCapacity: Int = 0, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND )
渡されるため、バッファリングされた値も、replay と extraBufferCapacity の 2 つの部分に分割されます。
queued emitters
その中で、 emit(value) メソッドに格納された値はEmitter
値にラップされます。
private suspend fun emitSuspend(value: T) = suspendCancellableCoroutine<Unit> sc@{ cont ->
var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
val emitter = synchronized(this) lock@{
// recheck buffer under lock again (make sure it is really full)
if (tryEmitLocked(value)) {
cont.resume(Unit)
resumes = findSlotsToResumeLocked(resumes)
return@lock null
}
// add suspended emitter to the buffer
// 将 value 包装成 Emitter 对象存储到 buffer 中,存储位置为 queued emitters 的起始结束位置范围
Emitter(this, head + totalSize, value, cont).also {
enqueueLocked(it)
queueSize++ // added to queue of waiting emitters
// synchronous shared flow might rendezvous with waiting emitter
if (bufferCapacity == 0) resumes = findSlotsToResumeLocked(resumes)
}
}
// outside of the lock: register dispose on cancellation
emitter?.let { cont.disposeOnCancellation(it) }
// outside of the lock: resume slots if needed
for (r in resumes) r?.resume(Unit)
}
private class Emitter(
@JvmField val flow: SharedFlowImpl<*>,
@JvmField var index: Long,
@JvmField val value: Any?,
@JvmField val cont: Continuation<Unit>
) : DisposableHandle {
override fun dispose() = flow.cancelEmitter(this)
}
エミットが一時停止されている場合(バッファ領域内のバッファ値がいっぱいであるか、サイズが 0 である場合)、エミッタ値はbuffer
に。
ここで値をバッファに保存するには、次のリンクがあります。
emit
→tryEmit
→tryEmitLocked
→tryEmitNoCollectorsLocked
→enqueueLocked
emit
→tryEmit
→tryEmitLocked
→enqueueLocked
emit
→emitSuspend
→tryEmitLocked
→tryEmitNoCollectorsLocked
→enqueueLocked
emit
→emitSuspend
→tryEmit
→tryEmitLocked
→enqueueLocked
emit
→emitSuspend
→Emitter
→enqueueLocked
// enqueues item to buffer array, caller shall increment either bufferSize or queueSize
private fun enqueueLocked(item: Any?) {
val curSize = totalSize
val buffer = when (val curBuffer = buffer) {
// 创建一个大小为 2 的缓存数组
null -> growBuffer(null, 0, 2)
// 如果当前缓存数据已经存满,则扩容,扩大为原来的 2 倍
else -> if (curSize >= curBuffer.size) growBuffer(curBuffer, curSize,curBuffer.size * 2) else curBuffer
}
buffer.setBufferAt(head + curSize, item)
}
現在の SharedFlow を収集している加入者はいません
collect
現在SharedFlow にサブスクライバが存在しない場合は、値をバッファに保存し、リンクをたどってみてください。
emit
→tryEmit
→tryEmitLocked
→tryEmitNoCollectorsLocked
→enqueueLocked
emit
→emitSuspend
→tryEmitLocked
→tryEmitNoCollectorsLocked
→enqueueLocked
private fun tryEmitLocked(value: T): Boolean {
// Fast path without collectors -> no buffering
if (nCollectors == 0) return tryEmitNoCollectorsLocked(value) // always returns true
// ..... 省略
return true
}
private fun tryEmitNoCollectorsLocked(value: T): Boolean {
assert { nCollectors == 0 }
if (replay == 0) return true // no need to replay, just forget it now
enqueueLocked(value) // enqueue to replayCache
bufferSize++ // value was added to buffer
// drop oldest from the buffer if it became more than replay
if (bufferSize > replay) dropOldestLocked()
minCollectorIndex = head + bufferSize // a default value (max allowed)
return true
}
このとき、再度replay=0にするとバッファは使用されなくなります。それ以外の場合、emit(value) の値は、バッファ配列内のバッファリングされた値のreplayCacheの開始範囲と終了範囲に格納されます。
リプレイの価値
サブスクライバがcollect
現在、この時点で、replay=0、extraBufferCapacity=0 であれば、リンクをたどろうとします。
emit
→emitSuspend
→Emitter
→enqueueLocked
Emit(value) の値は Emitter オブジェクトにパックされ、バッファ配列内のキューに入れられたエミッタの開始範囲と終了範囲に格納されます。購読者がいない場合、emit(value) の値は破棄されます。
restart!=0 または extraBufferCapacity!=0 の場合、次のリンクをたどろうとします。
emit
→tryEmit
→tryEmitLocked
→enqueueLocked
emit
→emitSuspend
→tryEmit
→tryEmitLocked
→enqueueLocked
emit
→emitSuspend
→Emitter
→enqueueLocked
Emit(value) メソッドの値は、バッファー配列内のバッファーされた値またはキューに入れられたエミッターの開始および終了範囲に保管されます。バッファーされた値の開始および終了範囲に保管される場合、開始および終了の値は更新は、BufferOverflow のバッファ オーバーフロー ポリシー (BufferOverflow.SUSPEND、BufferOverflow.DROP_LATEST または BufferOverflow.DROP_OLDEST) の影響を受けます。
収集
上記のエミッションの分析によると、コレクションはバッファ領域buffer
から、バッファされた値領域から直接値値を取得することも、キューに入れられたエミッタからエミッタオブジェクトを取得して値値を逆アセンブルすることもできます。
SharedFlowImpl
このクラスFlow
はcollect
インターフェイスのメソッドを実装します。
override suspend fun collect(collector: FlowCollector<T>) {
// 分配一个 SharedFlowSlot
val slot = allocateSlot()
try {
// 如果订阅者是一个 SubscribedFlowCollector,则先告诉订阅者开始订阅
if (collector is SubscribedFlowCollector) collector.onSubscription()
// 当前订阅者所在协程
val collectorJob = currentCoroutineContext()[Job]
// 死循环
while (true) {
var newValue: Any?
// 死循环
while (true) {
// 通过分配的 slot 去从缓存区 buffer 获取值
newValue = tryTakeValue(slot) // attempt no-suspend fast path first
// 获取到值
if (newValue !== NO_VALUE) break
// 没有获取到值,订阅者所在协程会被挂起,等待 emit 发射新数据到缓存区
awaitValue(slot) // await signal that the new value is available
}
//确认订阅者所在协程是否还存活,如果不存活,会抛出 CancellationException 异常,直接到 finally
collectorJob?.ensureActive()
// 将新值给订阅者
collector.emit(newValue as T)
}
} finally {
// 订阅者不存活时,释放分配的 slot
freeSlot(slot)
}
}
購読者が購読するときの主な手順は次のとおりです。
- SharedFlowSlot を割り当てます。
val slot = allocateSlot()
- 割り当てられたスロットを通じてバッファ バッファから値を取得します:
newValue = tryTakeValue(slot)
; 値が正常に取得された場合は、次のステップに直接進みます。それ以外の場合、サブスクライバのコルーチンは一時停止され、新しいデータをバッファに送信するための Emit を待機します。awaitValue(slot)
- サブスクライバーが配置されているコルーチンがまだ生きているかどうかを確認します。生きていない場合は、
CancellationException
例外が、finally に直接進みます。collectorJob?.ensureActive()
- 新しい値をサブスクライバーに与えます。
collector.emit(newValue as T)
- サブスクライバがアクティブでない場合は、割り当てられたスロットを解放します。
freeSlot(slot)
allocateSlot
、tryTakeValue(slot)
、awaitValue
を分析しましょうfreeSlot
。
スロットの割り当て
allocateSlot
このメソッドは、 SharedFlowImpl から継承されたAbstractSharedFlow
抽象。
@Suppress("UNCHECKED_CAST")
protected var slots: Array<S?>? = null // 用于管理给订阅者分配的 slot
private set
protected var nCollectors = 0 // 还存活的订阅者数量
private set
private var nextIndex = 0 // 分配下一个 slot 对象在 slots 数组中的索引
private var _subscriptionCount: MutableStateFlow<Int>? = null // 用一个 StateFlow 来记录订阅者数量
protected fun allocateSlot(): S {
// Actually create slot under lock
var subscriptionCount: MutableStateFlow<Int>? = null
// 加锁
val slot = synchronized(this) {
// 获取一个 Array<SharedFlowSlot?> 对象
val slots = when (val curSlots = slots) {
// 新创建一个大小为 2 的 Array<SharedFlowSlot?>
null -> createSlotArray(2).also { slots = it }
// 扩容,容量扩大为原来 Array<SharedFlowSlot?> 的 2 倍
else -> if (nCollectors >= curSlots.size) {
curSlots.copyOf(2 * curSlots.size).also { slots = it }
} else {
// 直接使用当前的 Array<SharedFlowSlot?>
curSlots
}
}
// 下面为从上面的 slots 数组中获取一个 slot 对象
var index = nextIndex
var slot: S
while (true) {
slot = slots[index] ?: createSlot().also { slots[index] = it }
index++
if (index >= slots.size) index = 0
// 给 slot 的属性 index 赋值,index 的值指向的缓存区 buffer 中的 index
if ((slot as AbstractSharedFlowSlot<Any>).allocateLocked(this)) break // break when found and allocated free slot
}
nextIndex = index
// 订阅者加1
nCollectors++
subscriptionCount = _subscriptionCount // retrieve under lock if initialized
slot
}
// 订阅数量加 1
subscriptionCount?.increment(1)
return slot
}
上記のコード ロジックから、このメソッドの主な機能は次のとおりです。 SharedFlowSlot オブジェクトをサブスクライバに割り当てます。これを使用して、バッファ バッファから取得した値のインデックスを関連付けます。つまり、サブスクライバが取得する値を決定します。そして、サブスクライバーが配置されているコルーチンを一時停止し、新しい値がバッファーに送信されるのを待ちます。
SharedFlowSlot
クラスについて:
private class SharedFlowSlot : AbstractSharedFlowSlot<SharedFlowImpl<*>>() {
@JvmField
var index = -1L // 指向缓冲区 buffer 中的索引,值为 -1 表示当前 slot 已经被释放
//......省略
override fun allocateLocked(flow: SharedFlowImpl<*>): Boolean {
if (index >= 0) return false // not free
index = flow.updateNewCollectorIndexLocked()
return true
}
//......省略
}
internal fun updateNewCollectorIndexLocked(): Long {
val index = replayIndex
if (index < minCollectorIndex) minCollectorIndex = index
return index
}
サブスクライバに割り当てられたスロット オブジェクトの変数index
、バッファ バッファから取得した値の初期インデックスは、replayIndex (index=replayIndex) です。つまり、新しいサブスクライバは、replayCache の開始位置から値を取得します。
tryTakeValue
tryTakeValue
このメソッドの機能は次のとおりです。SharedFlowSlot
の。インデックスは、バッファされた値の範囲、またはバッファ バッファ内のキューに入れられたエミッタの開始位置と終了位置を指す場合があります。値が正常に取得されると、インデックスはバッファーの次の位置を指しますslot.index = index + 1
。
private fun tryTakeValue(slot: SharedFlowSlot): Any? {
var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
// 加锁
val value = synchronized(this) {
// 通过 slot ,获取指向的缓存区 buffer 中的 index
val index = tryPeekLocked(slot)
if (index < 0) {
// 没有值
NO_VALUE
} else {
// 记录一下当前 slot 的 index
val oldIndex = slot.index
// 通过上面的 index,从缓存区 buffer 中取出对应的值
val newValue = getPeekedValueLockedAt(index)
// slot 的 index 指向缓存区 buffer 中的下一位 index+1
slot.index = index + 1 // points to the next index after peeked one
// 更新缓存数组的位置,并获取缓存数组与订阅者数组中可恢复的续体
resumes = updateCollectorIndexLocked(oldIndex)
newValue
}
}
for (resume in resumes) resume?.resume(Unit)
return value
}
インデックスがバッファ内のバッファ値、またはキューに入れられたエミッタの開始位置と終了位置の範囲と一致しているかどうかを判断するには、主にtryPeekLocked
次のメソッド
// returns -1 if cannot peek value without suspension
private fun tryPeekLocked(slot: SharedFlowSlot): Long {
// slot.index 刚开始的值是 replayIndex,也就是指向 buffered values(参看上面的 updateNewCollectorIndexLocked 方法 )
val index = slot.index
// 如果 index 在 buffered values 的起始结束位置范围内,直接返回
if (index < bufferEndIndex) return index
// 下面的逻辑都是用来判断是否能在 queued emitters 取值
// index>=bufferEndIndex ,此时如果 buffered values 的容量又大于0,找不到值
if (bufferCapacity > 0) return -1L
// 此时缓存数组只有 queued emitters,不能取起始位置后面的 Emitter,所以找不到值
// 因为 head=minOf(minCollectorIndex, replayIndex)
if (index > head) return -1L
// 缓存数组大小为0 ,找不到值
if (queueSize == 0) return -1L
// 从 queued emitters 起始结束位置范围内取值
return index
}
awaitValue
tryTakeValue メソッドがNO_VALUE
値、つまり-1L
tryPeekLocked メソッドが返すとき、対応するインデックスがバッファ内に見つからないときは、次のメソッドが実行されますawaitValue
。
// 这个方法是一个挂起方法
private suspend fun awaitValue(slot: SharedFlowSlot): Unit = suspendCancellableCoroutine { cont ->
synchronized(this) lock@{
// 再此尝试获取指向的缓存区 buffer 中的 index
val index = tryPeekLocked(slot) // recheck under this lock
if (index < 0) {
// 没有找到,给 slot 对象 cont 赋值,也就是让订阅者所在协程挂起
slot.cont = cont // Ok -- suspending
} else {
// 找到,恢复协程,不需要挂起
cont.resume(Unit) // has value, no need to suspend
return@lock
}
slot.cont = cont // suspend, waiting
}
}
このメソッドの主な機能は、サブスクライバをContinuation
インターフェイス。
slot.cont はContinuation
インターフェイス。
public interface Continuation<in T> {
/**
* 关联的协程
*/
public val context: CoroutineContext
/**
* 恢复关联的协程,传递一个 successful 或 failed 结果值过去
*
*/
public fun resumeWith(result: Result<T>)
}
これは、サブスクライバーが配置されているコルーチンにcontext
関連付けられて。そのため、slot.cont の値には、関連付けられたサブスクライバーの Continuation オブジェクトが格納されます。
フリースロット
freeSlot メソッドは、allocateSlot に対応します。サブスクライバが存在しなくなったときに、freeSlot
このメソッドが実行されます。
protected fun freeSlot(slot: S) {
// 使用 StateFlow 保存订阅数量
var subscriptionCount: MutableStateFlow<Int>? = null
// 加锁
val resumes = synchronized(this) {
// 订阅者数量减1
nCollectors--
subscriptionCount = _subscriptionCount
// 如果没有订阅者,下一次在 slots 中分配 slot 对象,从索引0开始
if (nCollectors == 0) nextIndex = 0
// slot 对象的真正释放
(slot as AbstractSharedFlowSlot<Any>).freeLocked(this)
}
/*
Resume suspended coroutines.
This can happens when the subscriber that was freed was a slow one and was holding up buffer.
When this subscriber was freed, previously queued emitted can now wake up and are resumed here.
*/
for (cont in resumes) cont?.resume(Unit)
// 订阅数量减1
subscriptionCount?.increment(-1)
}
このメソッドの主な機能: 記録された加入者の数が 1 減り、スロット オブジェクトのインデックスと cont がリセットされます。つまり、インデックスはバッファの開始位置と終了位置の範囲を指さなくなります。そして、 cout はサブスクライバーの coroutine に関連付けられなくなりました。
private class SharedFlowSlot : AbstractSharedFlowSlot<SharedFlowImpl<*>>() {
@JvmField
var index = -1L // 指向缓冲区 buffer 中的索引,值为 -1 表示当前 slot 已经被释放
//......省略
@JvmField
var cont: Continuation<Unit>? = null // 用来保存等待新数据发送的订阅者的续体,当订阅者等待新值时用到
//......省略
override fun freeLocked(flow: SharedFlowImpl<*>): Array<Continuation<Unit>?> {
assert { index >= 0 }
val oldIndex = index
index = -1L
cont = null // cleanup continuation reference
return flow.updateCollectorIndexLocked(oldIndex)
}
}
freeLocked で呼び出されるflow.updateCollectorIndexLocked(oldIndex)
メソッドは、キャッシュ配列の場所を更新するために使用されます。
この時点で、SharedFlowの作成、送信、収集、分析は終了です。それは、その特性、つまり共有と独立した存在について一般的に理解しています。
ビジネスシーン
SharedFlow の作成、送信、収集の原理を理解した後は、その共有と独立した存在に基づいて、以前に使用されていた EventBus と同様に、ビジネスのイベント バスとして使用できます。以下は、SharedFlow によって実装された EventBus の簡単な例です。
イベント バスを定義します。
object EventBus {
private val events = ConcurrentHashMap<String, MutableSharedFlow<Event>>()
private fun getOrPutEventFlow(eventName: String): MutableSharedFlow<Event> {
return events[eventName] ?: MutableSharedFlow<Event>().also { events[eventName] = it }
}
fun getEventFlow(event: Class<Event>): SharedFlow<Event> {
return getOrPutEventFlow(event.simpleName).asSharedFlow()
}
suspend fun produceEvent(event: Event) {
val eventName = event::class.java.simpleName
getOrPutEventFlow(eventName).emit(event)
}
fun postEvent(event: Event, delay: Long = 0, scope: CoroutineScope = MainScope()) {
scope.launch {
delay(delay)
produceEvent(event)
}
}
}
@Keep
open class Event(val value: Int) {
}
イベントの発行とサブスクリプション:
lifecycleScope.launch {
EventBus.getEventFlow(Event::class.java).collect {
Log.e("Flow", "EventBus Collect: value=${it.value}")
}
}
EventBus.postEvent(Event(1), 0, lifecycleScope)
EventBus.postEvent(Event(2), 0)
控制台输出结果:
Flow com.example.wangjaing E EventBus Collect: value=1
Flow com.example.wangjaing E EventBus Collect: value=2
SharedFlow をイベント バスとして使用すると、次の利点があります。
- イベントは遅れて送信される可能性があります
- スティッキーイベントを定義可能
- イベントはアクティビティまたはフラグメントのライフサイクルを感知できます
- イベントは順序付けられています
要約する
ホット フロー SharedFlow では、作成後に存在し、プロデューサーがデータを発行するときにコンシューマーがデータを収集することなく独立して実行できます。プロデューサがデータを送信した後、データはキャッシュされ、新しいコンシューマと古いコンシューマの両方がデータを受信して、共有データを実現できます。
エミッション データ操作の場合、MutableSharedFlow コンストラクター パラメーターのreplay、extraBufferCapacity、onBufferOverflow 値の影響を受けます。これらのパラメーターは、エミッション操作が一時停止されるかどうかを決定します。送信されたデータはバッファ配列を使用して管理され、管理領域はバッファされた値とキューされたエミッタに分割されます。再生パラメータと extraBufferCapacity パラメータは、バッファリングされた値領域のサイズを決定します。バッファリングされた値領域がいっぱいでオーバーフローした場合、バッファオーバーフローのオーバーフロー ポリシーに従って領域が調整されます。restart=0 および extraBufferCapacity=0、または、replay!=0 および extraBufferCapacity!=0 でバッファリングされた値の領域がいっぱいの場合、放出されたデータはエミッターとしてパッケージ化され、キューに入れられたエミッター領域に保管されます。さらに、サブスクライバの数によって、送信されたデータがバッファに格納されるか破棄されるかが決まります。最後に、キャッシュに保存されたデータはすべてのサブスクライバーと共有されます。
データ収集操作の場合は、サブスクライバを管理するためのスロット Array<SharedFlowSlot?> 配列を使用します。各スロット オブジェクトはサブスクライバに対応し、スロット オブジェクトの slot.index は、サブスクライバによって収集されるデータとキャッシュ領域を関連付けます。 、slot.cont は、サブスクライバのコルーチンを SharedFlow コンテキストに関連付けます。値がslot.indexを通じてキャッシュ領域で取得できた場合、その値はサブスクライバに直接送信されます。それ以外の場合、サブスクライバは Continuation インターフェイス実装クラス オブジェクトにカプセル化され、slot.cont に格納されます。サブスクライバが配置されているコルーチンは一時停止され、バッファに値がある場合、サブスクライバ コルーチンが再開され、値が与えられます。それに。サブスクライバ コルーチンが存続しない場合、サブスクライバに関連付けられているスロット オブジェクトが解放され、slot.inext とslot.cont の値がリセットされ、キャッシュ配列の位置が再調整されます。
ステートフロー
StateFlow
また、SharedFlowをベースに実装されているため、StateFlowはSharedFlowの特別な存在として理解できます。
public interface StateFlow<out T> : SharedFlow<T> {
/**
* The current value of this state flow.
*/
public val value: T
}
StateFlow もホット フローであり、すべてのコレクタが発行する値を共有することもできますが、この値は最新の値であり、そのインスタンスはコレクタの存在とは独立して存在することもできます。
以下、StateFlowの作成、送信、収集から分析していきますが、原理はSharedFlowの作成、送信、収集と同様ですので、簡単な分析のみを記載します。
作成
SharedFlowKt によって提供されるMutableStateFlow
コンストラクター。
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
private class StateFlowImpl<T>(
initialState: Any // T | NULL
) : AbstractSharedFlow<StateFlowSlot>(), MutableStateFlow<T>, CancellableFlow<T>, FusibleFlow<T> {
private val _state = atomic(initialState)
//......省略
}
StateFlow には、アトミック オブジェクトにキャッシュされる初期値が必要です: _state = atomic(initialState)
。この値が更新されない場合、サブスクライバはサブスクライブ時にこの値を受け取ります。
排出
StateFlowImpl クラスは、EmitDataemit
とtryEmit
メソッドを実装します。
private class StateFlowImpl<T>(
initialState: Any // T | NULL
) : AbstractSharedFlow<StateFlowSlot>(), MutableStateFlow<T>, CancellableFlow<T>, FusibleFlow<T> {
// 缓存当前值原子对象
private val _state = atomic(initialState)
private var sequence = 0
public override var value: T
get() = NULL.unbox(_state.value)
//发送时,更新当前值,并缓存在 _state 中
set(value) { updateState(null, value ?: NULL) }
override fun compareAndSet(expect: T, update: T): Boolean =
updateState(expect ?: NULL, update ?: NULL)
//更新原子对象 _state 的值
private fun updateState(expectedState: Any?, newState: Any): Boolean {
var curSequence = 0
// 订阅者关联的 slots
var curSlots: Array<StateFlowSlot?>? = this.slots
synchronized(this) {
val oldState = _state.value
if (expectedState != null && oldState != expectedState) return false // CAS 操作
if (oldState == newState) return true // 如果当前值和新值相等,则不用更新,也不用通知订阅者
// 将新值更新到缓存中
_state.value = newState
curSequence = sequence
if (curSequence and 1 == 0) {
curSequence++ //
sequence = curSequence
} else {
sequence = curSequence + 2 // change sequence to notify, keep it odd
return true
}
curSlots = slots // read current reference to collectors under lock
}
//......省略
}
//......省略
//非挂起发送
override fun tryEmit(value: T): Boolean {
this.value = value
return true
}
//挂起发送
override suspend fun emit(value: T) {
this.value = value
}
}
これら 2 つのメソッドは、キャッシュ オブジェクトに_state
格納されているもので、現在の値が新しい値と等しい場合は更新されず、そうでない場合は更新され、新しい値がサブスクライバーに与えられます。
収集
上記の起動の分析によると、収集とはキャッシュ_state
から。
StateFlowImpl
このクラスFlow
はcollect
インターフェイスのメソッドを実装します。
override suspend fun collect(collector: FlowCollector<T>): Nothing {
// 分配一个 StateFlowSlot
val slot = allocateSlot()
try {
// 如果订阅者是 SubscribedFlowCollector 类型,则告诉订阅者订阅开始
if (collector is SubscribedFlowCollector) collector.onSubscription()
// 当前协程
val collectorJob = currentCoroutineContext()[Job]
// 记录一下上一个缓存值
var oldState: Any? = null
// 死循环
while (true) {
// Here the coroutine could have waited for a while to be dispatched,
// 获取当前缓存值
val newState = _state.value
// 确认订阅者所在协程是否还存活,如果不存活,会抛出 `CancellationException` 异常,直接到 finally
collectorJob?.ensureActive()
// 如果上一个缓存值为空或新值不等于上一个缓存值,则将新值给订阅者
if (oldState == null || oldState != newState) {
collector.emit(NULL.unbox(newState))
//更新记录的缓存值
oldState = newState
}
// 判断订阅者是否需要挂起
if (!slot.takePending()) {
//订阅者所在协程会被挂起,等待 emit 发射新数据到缓存
slot.awaitPending()
}
}
} finally {
// 订阅者不存活时,释放分配的 slot
freeSlot(slot)
}
}
購読者が購読するときの主な手順は次のとおりです。
- StateFlowSlot を割り当てます。
val slot = allocateSlot()
- 割り当てられたスロットを介してキャッシュ
_state
から値を取得します。val newState = _state.value
- サブスクライバーが配置されているコルーチンがまだ生きているかどうかを確認します。生きていない場合は、
CancellationException
例外が、finally に直接進みます。collectorJob?.ensureActive()
- 新しい値をサブスクライバーに与えます。
collector.emit(NULL.unbox(newState))
- サブスクライバがアクティブでない場合は、割り当てられたスロットを解放します。
freeSlot(slot)
ビジネスシーン
StateFlow の作成、送信と収集の一般原則、最新の状態を共有する特徴について理解します。ビジネスにおけるステータス更新(LiveDataの代替)に使用できます。
たとえば、サーバーからリスト データを取得し、UI にリスト データを表示します。以下はMVI (Model-View-Intent)
それを行うために:
データ層:
class FlowRepository private constructor() {
companion object {
@JvmStatic
fun newInstance(): FlowRepository = FlowRepository()
}
fun requestList(): Flow<List<ItemBean>> {
val call = ServiceGenerator
.createService(FlowListApi::class.java)
.getList()
return flow {
emit(call.execute())
}.flowOn(Dispatchers.IO).filter { it.isSuccessful }
.map {
it.body()?.data
}
.filterNotNull().catch {
emit(emptyList())
}.onEmpty {
emit(emptyList())
}
}
}
ビューモデル:
class ListViewModel : ViewModel() {
private val repository: FlowRepository = FlowRepository.newInstance()
private val _uiIntent: Channel<FlowViewIntent> = Channel()
private val uiIntent: Flow<FlowViewIntent> = _uiIntent.receiveAsFlow()
private val _uiState: MutableStateFlow<FlowViewState<List<ItemBean>>> =
MutableStateFlow(FlowViewState.Init())
val uiState: StateFlow<FlowViewState<List<ItemBean>>> = _uiState
fun sendUiIntent(intent: FlowViewIntent) {
viewModelScope.launch {
_uiIntent.send(intent)
}
}
init {
viewModelScope.launch {
uiIntent.collect {
handleIntent(it)
}
}
}
private fun handleIntent(intent: FlowViewIntent) {
viewModelScope.launch {
repository.requestList().collect {
if (it.isEmpty()) {
_uiState.emit(FlowViewState.Failure(0, "data is invalid"))
} else {
_uiState.emit(FlowViewState.Success(it))
}
}
}
}
}
data class FlowViewIntent()
sealed class FlowViewState<T> {
@Keep
class Init<T> : FlowViewState<T>()
@Keep
class Success<T>(val result: T) : FlowViewState<T>()
@Keep
class Failure<T>(val code: Int, val msg: String) : FlowViewState<T>()
}
UI:
private var isRequestingList = false
private lateinit var listViewModel: ListViewModel
private fun initData() {
listViewModel = ViewModelProvider(this)[ListViewModel::class.java]
lifecycleScope.launchWhenStarted {
listViewModel.uiStateFlow.collect {
when (it) {
is FlowViewState.Success -> {
showList(it.result)
}
is FlowViewState.Failure -> {
showListIfFail()
}
else -> {}
}
}
}
requestList()
}
private fun requestList() {
if (!isRequestingList) {
isRequestingList = true
listViewModel.sendUiIntent( FlowViewIntent() )
}
}
LiveData を StateFlow に置き換え、MVVM を MVI に置き換えると、次の利点が得られます。
- 唯一の信頼できるデータ ソース: MVVM には大量の LiveData が存在する可能性があり、データ インタラクションまたは並列更新の制御不能なロジックが発生します。StateFlow と組み合わせた UIState を追加すると、データ ソースは UIState のみになります。
- データの一方向の流れ: MVVM ではデータ UI ⇆ ViewModel が相互に流れますが、MVI ではデータはデータ層→ViewModel→UI としか流れず、データは一方向に流れます。
StateFlow を使用して LiveData を置き換えてイベント ステータスを更新する場合には、次のような違いがあります。
- StateFlow では初期状態をコンストラクターに渡す必要がありますが、LiveData では必要ありません。
- LiveData.observe() は、ビューが STOPPED 状態になるとコンシューマーの登録を自動的に解除しますが、StateFlow またはその他のデータ フローからのデータ収集は自動的に停止しません。同じ動作を実現するには、Lifecycle.repeatOnLifecycle ブロックからデータ フローを収集します。
要約する
ヒートフローStateFlowはSharedFlowをベースに実装されているため、独立した存在と共有という特徴もあります。ただし、StateFlow でデータを発行する場合、最新の値のみがキャッシュされるため、新しいサブスクライバーと古いサブスクライバーがサブスクライブする場合、最後に更新された値のみを受け取ります。発行された新しい値が現在の値と等しい場合、サブスクライバーは通知を受信しません。