Kotlin フローのコールド フローとホット フロー

この記事は主にコールドフローとホットフローの関連実現原理を分析しますが、原理ロジックは長くて複雑です。特に、熱流 SharedFlow の関連実装原理に関しては、ロジックがより抽象的で理解しにくくなります。この記事は比較的長いです。カタログに従ってセクションに分けて読むことをお勧めします。最初に基本概念とコールド フローを見てから、ホット フローの SharedFlow と StateFlowをそれぞれ見ていきます。

この記事を読むと、次のような疑問が湧いてきます。

  1. 寒流と熱流とは何を指しますか?
  2. ビジネス開発において、コールド フローとホット フローは何に使用できるか、または解決できるでしょうか?
  3. コールドフローとホットフローの違いは何ですか?
  4. コールドフローの動作原理は何ですか?
  5. SharedFlow は送信するデータをどのように管理しますか?
  6. SharedFlow はサブスクライバーをどのように管理しますか?
  7. StateFlow と LiveData の違いは何ですか?

コールド フローであろうとホット フローであろうと、ビジネスに役立つテクノロジーはすべて、次のようなビジネス開発における実際的な問題を解決する必要があります。

  • コルーチンとコールド フローは、レスポンシブ プログラミングのRxJavaフレームワーク。Kotlin プロジェクトでは、コルーチンとコールド フローを使用すると、RxJava を使用するよりも多くの利点があります。
  • SharedFlow は、 を置き換えるイベント バスとして使用できますEventBus
  • LiveDataHot Flow StateFlow は、イベント状態の更新、置換、および結合MVI置換に使用できますMVVM

この記事に誤りがある場合は、時間内に修正されます。ようこそお読みください。

基本的な考え方

前の記事: Kotlin Flow の探索から、Kotlin Flow は Kotlin データ フローであり、データ フローにはプロバイダー (プロデューサー)、仲介者 (中間操作)、およびコンシューマー (消費者) を含める必要があることがわかりました。

  • プロバイダー (プロデューサー): ソース データ、データ フローにデータを追加します。
  • 仲介者 (中間操作): データ ストリームに送信される値を変更したり、データ ストリーム自体を変更したりできます。
  • Consumer (Consumer): 結果データ。データ ストリーム内の値を消費します。
flow
  .operator1()
  .operator2()
  .operator3()
  .collect(consumer)

データ ストリームを作成するには、Kotlin 拡張関数flowOf、を使用できますasFlowflow{}

 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
        }
    }
}

と を使用してストリームを作成する場合、コレクターが存在しないか、複数のコレクターが存在する可能性があります。コレクターとは独立して存在しコンシューマーなどによって終了されません。SharedFlowStateFlowcollect{}collect{}asListasSet

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:1map:1→ 、ではなく、 → → collect:1ですemit:2map:2collect:4emit:1emit:2map:1map:2collect:1collect: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.collectSafeFlow.collectSafelymapSafeCollector.emitemitであることがわかります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()
        }
    }
}

上記のコード分析から、マップ プロセスは次のようになります。maptransformunsafeTransformunsafeFlow { }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 クラスを継承し、メソッドallocateLockedfreeLocked抽象メソッドを実装し、属性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このクラスはFlowCollectoremitemit メソッドが呼び出されたときに、インターフェイスのメソッドを実装します。

  1. collect現在SharedFlow および onBufferOverflow にサブスクライバが存在する場合、この呼び出しは一時停止される可能性があります。 BufferOverflow = BufferOverflow.SUSPEND;
  2. collect現在SharedFlow を使用しているサブスクライバがいない場合、バッファは使用されません。restart != 0 の場合、最後に発行された値は単純にリプレイ キャッシュに格納され、リプレイ キャッシュ内の古い要素と置き換えられます。replay=0 の場合、最後に発行された値は破棄されます。
  3. 放出メソッドはsuspend変更されており、suspendそれに関連する未変更のメソッドがありますtryEmit
  4. このメソッドはスレッドセーフであり、外部同期なしで同時コルーチンから安全に呼び出すことができます。

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 つのログの違いは次のとおりです。

  • emitsuspendImpltryEmitCancellableContinuationImpl.resumeWithDispatchedTaskKt.resumeTestFlowFragment$initView$5$1.emit
  • emitsuspendImplemitSuspendCancellableContinuationImpl.resumeWithDispatchedTaskKt.resumeTestFlowFragment$initView$5$1.emit

出力ログの比較から、onBufferOverflowポリシーBufferOverflow.SUSPENDが の場合、キャッシュ領域extraBufferCapacityに値がある場合、発行は一時停止されません。それ以外の場合、発行は一時停止されます。したがって、と の値がメソッドonBufferOverflow推測できるようになりました。extraBufferCapacitytryEmit

  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
            }
        }

フィールド: bufferSizebufferCapacityminCollectorIndexreplayIndex、これらはすべて 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 valuesqueued emitters

buffered valuesEmit(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に。

ここで値をバッファに保存するには、次のリンクがあります。

  1. emittryEmittryEmitLockedtryEmitNoCollectorsLockedenqueueLocked
  2. emittryEmittryEmitLockedenqueueLocked
  3. emitemitSuspendtryEmitLockedtryEmitNoCollectorsLockedenqueueLocked
  4. emitemitSuspendtryEmittryEmitLockedenqueueLocked
  5. emitemitSuspendEmitterenqueueLocked
 // 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 にサブスクライバが存在しない場合は、値をバッファに保存し、リンクをたどってみてください。

  • emittryEmittryEmitLockedtryEmitNoCollectorsLockedenqueueLocked
  • emitemitSuspendtryEmitLockedtryEmitNoCollectorsLockedenqueueLocked
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 であれば、リンクをたどろうとします。

  • emitemitSuspendEmitterenqueueLocked

Emit(value) の値は Emitter オブジェクトにパックされ、バッファ配列内のキューに入れられたエミッタの開始範囲と終了範囲に格納されます。購読者がいない場合、emit(value) の値は破棄されます。

restart!=0 または extraBufferCapacity!=0 の場合、次のリンクをたどろうとします。

  • emittryEmittryEmitLockedenqueueLocked
  • emitemitSuspendtryEmittryEmitLockedenqueueLocked
  • emitemitSuspendEmitterenqueueLocked

Emit(value) メソッドの値は、バッファー配列内のバッファーされた値またはキューに入れられたエミッターの開始および終了範囲に保管されます。バッファーされた値の開始および終了範囲に保管される場合、開始および終了の値は更新は、BufferOverflow のバッファ オーバーフロー ポリシー (BufferOverflow.SUSPEND、BufferOverflow.DROP_LATEST または BufferOverflow.DROP_OLDEST) の影響を受けます。

収集

上記のエミッションの分析によると、コレクションはバッファ領域bufferから、バッファされた値領域から直接値値を取得することも、キューに入れられたエミッタからエミッタオブジェクトを取得して値値を逆アセンブルすることもできます。

SharedFlowImplこのクラスFlowcollectインターフェイスのメソッドを実装します。

 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)
        }
    }

購読者が購読するときの主な手順は次のとおりです。

  1. SharedFlowSlot を割り当てます。val slot = allocateSlot()
  2. 割り当てられたスロットを通じてバッファ バッファから値を取得します: newValue = tryTakeValue(slot); 値が正常に取得された場合は、次のステップに直接進みます。それ以外の場合、サブスクライバのコルーチンは一時停止され、新しいデータをバッファに送信するための Emit を待機します。awaitValue(slot)
  3. サブスクライバーが配置されているコルーチンがまだ生きているかどうかを確認します。生きていない場合は、CancellationException例外が、finally に直接進みます。collectorJob?.ensureActive()
  4. 新しい値をサブスクライバーに与えます。collector.emit(newValue as T)
  5. サブスクライバがアクティブでない場合は、割り当てられたスロットを解放します。freeSlot(slot)

allocateSlottryTakeValue(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値、つまり-1LtryPeekLocked メソッドが返すとき、対応するインデックスがバッファ内に見つからないときは、次のメソッドが実行されます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 をイベント バスとして使用すると、次の利点があります。

  1. イベントは遅れて送信される可能性があります
  2. スティッキーイベントを定義可能
  3. イベントはアクティビティまたはフラグメントのライフサイクルを感知できます
  4. イベントは順序付けられています

要約する

ホット フロー 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 クラスは、EmitDataemittryEmitメソッドを実装します。

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このクラスFlowcollectインターフェイスのメソッドを実装します。

    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)
        }
    }

購読者が購読するときの主な手順は次のとおりです。

  1. StateFlowSlot を割り当てます。val slot = allocateSlot()
  2. 割り当てられたスロットを介してキャッシュ_stateから値を取得します。val newState = _state.value
  3. サブスクライバーが配置されているコルーチンがまだ生きているかどうかを確認します。生きていない場合は、CancellationException例外が、finally に直接進みます。collectorJob?.ensureActive()
  4. 新しい値をサブスクライバーに与えます。collector.emit(NULL.unbox(newState))
  5. サブスクライバがアクティブでない場合は、割り当てられたスロットを解放します。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 に置き換えると、次の利点が得られます。

  1. 唯一の信頼できるデータ ソース: MVVM には大量の LiveData が存在する可能性があり、データ インタラクションまたは並列更新の制御不能なロジックが発生します。StateFlow と組み合わせた UIState を追加すると、データ ソースは UIState のみになります。
  2. データの一方向の流れ: MVVM ではデータ UI ⇆ ViewModel が相互に流れますが、MVI ではデータはデータ層→ViewModel→UI としか流れず、データは一方向に流れます。

StateFlow を使用して LiveData を置き換えてイベント ステータスを更新する場合には、次のような違いがあります。

  • StateFlow では初期状態をコンストラクターに渡す必要がありますが、LiveData では必要ありません。
  • LiveData.observe() は、ビューが STOPPED 状態になるとコンシューマーの登録を自動的に解除しますが、StateFlow またはその他のデータ フローからのデータ収集は自動的に停止しません。同じ動作を実現するには、Lifecycle.repeatOnLifecycle ブロックからデータ フローを収集します。

要約する

ヒートフローStateFlowはSharedFlowをベースに実装されているため、独立した存在と共有という特徴もあります。ただし、StateFlow でデータを発行する場合、最新の値のみがキャッシュされるため、新しいサブスクライバーと古いサブスクライバーがサブスクライブする場合、最後に更新された値のみを受け取ります。発行された新しい値が現在の値と等しい場合、サブスクライバーは通知を受信しません。

おすすめ

転載: blog.csdn.net/wangjiang_qianmo/article/details/129505497