Kotlin Flow flujo frío y flujo caliente

Este artículo analiza principalmente los principios de realización relevantes de flujo frío y flujo caliente, y la lógica del principio es larga y complicada. Especialmente cuando se trata de los principios de implementación relacionados con el flujo de calor SharedFlow, la lógica es más abstracta y difícil de entender. Este artículo es relativamente largo, se recomienda leerlo por secciones de acuerdo al catálogo, primero se pueden ver los conceptos básicos y flujo frío , y luego mirar el flujo caliente SharedFlow y StateFlow respectivamente .

Al leer este artículo, puedes pensar con las siguientes preguntas:

  1. ¿A qué se refieren las corrientes frías y las corrientes calientes?
  2. En el desarrollo de negocios, ¿para qué se puede usar o resolver el flujo frío y el flujo caliente?
  3. ¿Cuál es la diferencia entre flujo frío y flujo caliente?
  4. ¿Cuál es el principio de funcionamiento del flujo frío?
  5. ¿Cómo gestiona SharedFlow los datos que emite?
  6. ¿Cómo administra SharedFlow a sus suscriptores?
  7. ¿Cuál es la diferencia entre StateFlow y LiveData?

Las tecnologías sirven a los negocios, ya sea flujo frío o flujo caliente, todos necesitan resolver problemas prácticos en el desarrollo comercial, tales como:

  • Las corrutinas y los flujos fríos pueden reemplazar RxJavael marco para la programación receptiva. En los proyectos de Kotlin, usar corrutinas y flujos fríos tiene más ventajas que usar RxJava;
  • SharedFlow se puede usar como un bus de eventos para reemplazar EventBus;
  • Hot Flow StateFlow se puede usar para actualizaciones de estado de eventos, reemplazos LiveDatay MVIreemplazos MVVM.

Si hay un error en este artículo, se corregirá a tiempo. Bienvenido a leer.

concepto basico

Del artículo anterior: Explorando Kotlin Flow , sé que Kotlin Flow es un flujo de datos de Kotlin, y el flujo de datos debe incluir proveedor (productor), intermediario (operación intermedia) y consumidor (consumidor):

  • Proveedor (productor): datos de origen, agregar datos al flujo de datos;
  • Intermediarios (operaciones intermedias): pueden modificar los valores enviados al flujo de datos, o modificar el flujo de datos en sí;
  • Consumer (Consumidor): Datos de resultado, consume los valores en el flujo de datos.
flow
  .operator1()
  .operator2()
  .operator3()
  .collect(consumer)

Para crear un flujo de datos, puede usar la función de extensión de 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 {}

En el método anterior de crear un flujo de datos, la operación intermedia solo se ejecutará collect{}cuando que es Kotlin Sequenceslo mismo que .

Ahora, además de la forma anterior de crear flujos de datos, también puede usar SharedFlowy 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
        }
    }
}

Al crear una secuencia con ySharedFlow , no puede haber recopiladores o varios recopiladores , existe y no será cancelado por los consumidores , etc.StateFlowcollect{}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

Se puede ver que cuando no hay collect{}colector , ShareFlow y StateFlow aún se ejecutan. Agregue el recopilador a continuación collect{}y eche un vistazo nuevamente:

        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

Para SharedFlow, es similar a un bus de eventos, que distribuye eventos a los suscriptores de eventos y comparte eventos. Para ello StateFlow, es similar a LiveData, que actualiza el último estado del evento e informa a los suscriptores de la actualización del evento.

Ahora el flujo frío y el flujo caliente se pueden distinguir simplemente: el flujo de datos creado usando flowOf, asFlow, etc. se llama flujo frío, es decir, el flujo de datos creado usando, no puede existir independientemente del colector, y cada flujo de datos necesita el colector para poder Se denomina flujo de datos completo, el flujo de datos creado mediante el uso o se denomina flujo de calor, que puede existir independientemente del colector, y puede haber colectores múltiples o ninguno .flow{}: Flow<T>collect{}collect{}: SharedFlow<T>: StateFlow<T>collect{}collect{}

flujo frío

Un flujo es un flujo en frío similar a una secuencia: el código en el generador de flujo no se ejecuta hasta que se recopila el flujo.

El flujo es lo mismo que una secuencia, necesita tener un operador de terminal, es decir, solo se ejecutará cuando haya un colector collect{}o asListy asSetotras operaciones:

        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

Al collect{}usar , inicie la producción de datos: emita valor emit, luego ejecute la operación intermedia: maptransformación, y luego ejecute el consumo de datos: collect. A lo largo del proceso, el flujo de datos ocurre en orden cronológico, es decir, emit:1map:1collect:1, emit:2map:2collect:4, no emit:1emit:2, map:1map:2, collect:1collect:4.

Echemos un breve vistazo al principio de ejecución del flujo en frío a partir de un ejemplo:

class TestFlow {
    fun testColdFlow() {
        MainScope().launch {
            flow<Int> { emit(1) }.map { it * it }.collect {
                Log.e("Flow", "testColdFlow", Throwable())
            }
        }
    }
}

Ejecute testColdFlowel método y la consola generará collectla información de la pila de llamadas al método:

 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)

A partir de la información de la pila de llamadas del método anterior, podemos ver que el orden aproximado es: collectAbstractFlow.collectSafeFlow.collectSafelymapSafeCollector.emitemitTestFlow$testColdFlow$1$3.emit. (El registro anterior no corresponde al código fuente, aquí puede ver el archivo de clase de kotlin a través de AndroidStudio).

Aquí nos enfocamos en comprender la transformación del mapa de operaciones intermedias:

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

Del análisis de código anterior, el proceso de mapa es: maptransformunsafeTransformunsafeFlow { }collect {}执行上一个 flow拿到上一个 flow 的结果@collect transform(value)@transform emit(transform(value))map变换操作结果到下一个 flow 或 消费者 collect.

Al ver este proceso, sí, el proceso de ejecución de flujo en frío es similar al principio del proceso de ejecución de Kotin Sequence .

Por lo tanto, el proceso de activación de todo el flujo frío se puede resumir simplemente de la siguiente manera: la recopilación del consumidor activa la operación intermedia, el filtro de operación intermedia, la transformación del mapa, etc. activa el productor, y luego el productor emite los datos de producción, y luego pasa los datos a la operación intermedia para su transformación y, finalmente, los envía. Los datos transformados se entregan al consumidor. Este es el principio de la ejecución de flujo en frío. Es un poco similar, la sensación de ser activado de abajo hacia arriba y luego fluir de arriba hacia abajo .

Escena de negocios

: Flow<T>Para escenarios empresariales, RxJavaes similar al uso de la programación receptiva, por ejemplo:

 GlobalScope.launch(Dispatchers.Main) {
            flowOf(bitmap).map { bmp ->
                //在子线程中执行耗时操作,存储 bitmap 到本地
                saveBitmap(bmp)
            }.flowOn(Dispatchers.IO).collect { bitmapLocalPath ->
                //在主线程中处理存储 bitmap 后的本地路地址
            }
        }

En los proyectos de Kotlin, puede usar rutinas y flujo frío para reemplazar RxJava para la programación reactiva.

Resumir

Un flujo frío requiere productores de datos, 0 o más operaciones intermedias y consumidores de datos para construir juntos un flujo completo. Su principio de ejecución es similar a Kotin Sequence: cuando hay una recopilación de consumidores u otras operaciones de terminal, la secuencia comienza a activarse de abajo hacia arriba y luego fluye de arriba hacia abajo.

flujo de calor

El flujo de calor se divide en SharedFlow y StateFlow, los cuales existen independientemente del colector.

flujo compartido

SharedFlow, como su nombre indica, se denomina flujo caliente, principalmente porque permite que todos los colectores compartan los valores que emite, y la forma de compartir es la transmisión, y sus instancias pueden existir independientemente de la existencia de colectores. Para entender ShredFlow, es necesario comprender el significado de su existencia compartida e independiente .

Se analizará lo siguiente a partir de la creación, envío y cobro de SharedFlow .

crear

Cree un SharedFlow utilizando MutableSharedFlowel constructor :

private val _sharedFlow = MutableSharedFlow<Int>(
        replay = 0,
        extraBufferCapacity = 0,
        onBufferOverflow = BufferOverflow.SUSPEND
    )
    val sharedFlow: SharedFlow<Int> = _sharedFlow

Significado del parámetro:

  • replay: cuando se suscribe un nuevo suscriptor, cuántos valores enviados anteriormente se reenvían al nuevo suscriptor (similar a los datos fijos);
  • extraBufferCapacity: Además de la reproducción, la cantidad de valores en caché, cuando todavía hay valores en el espacio de caché, la emisión no se suspenderá (la emisión es demasiado rápida, la recopilación es demasiado lenta y los datos de emisión se almacenarán en caché);
  • onBufferOverflow: especifique la estrategia de procesamiento cuando el búfer esté lleno de elementos de datos para enviar (el tamaño del búfer está determinado conjuntamente por la reproducción y extraBufferCapacity). El valor predeterminado es BufferOverflow.SUSPEND, pero también puede ser BufferOverflow.DROP_LATEST o BufferOverflow.DROP_OLDEST (como sugiere el nombre).
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> {
    //.....省略
}

El constructor MutableSharedFlow devuelve una SharedFlowImplinstancia . Echemos un vistazo a la relación simple entre las clases y las interfaces asociadas con la clase SharedFlowImpl:
inserte la descripción de la imagen aquí
De arriba a abajo, las responsabilidades de cada clase o interfaz son:

  • Flow 接口: Se utiliza para la operación de consumo del flujo, es decir, el suscriptor se suscribe a cobrar, lo que proporciona public suspend fun collect(collector: FlowCollector<T>)el método de interfaz y el colector de parámetros de método es el colector del flujo frío o el flujo caliente, por lo que la interfaz de Flujo depende de la interfaz FlowCollector;
  • FlowCollecter 接口: se utiliza para la recopilación de secuencias, puede ser la operación final o la operación intermedia de la secuencia, proporciona public suspend fun emit(value: T)el método , el valor del parámetro del método es el valor enviado por el productor de datos o la emisión de la operación intermedia;
  • SharedFlow 接口: herede la interfaz de flujo y defina public val replayCache: List<T>el atributo, que representa la instantánea de caché de valor (número de reproducción) para los nuevos suscriptores;
  • MutableSharedFlow 接口: heredar la interfaz SharedFlow y FlowCollector, entonces collect(collecter: FlowCollecter<T>)también ser emit(value: T);
  • CancellableFlow 接口: Heredar la interfaz de Flow, que es una interfaz vacía, principalmente marcando que se puede cancelar el Flow, es decir, se puede cancelar el SharedFlow;
  • CancellableFlowImpl 类: implementar collect(collector: FlowCollector<T>)el método ;
  • FusibleFlow 接口: Trabaja en conjunto con las operaciones BufferOverflow y flowOn;
  • AbstractSharedFlow<S : AbstractSharedFlowSlot<*>> 抽象类: Responsable de la gestión de suscriptores, recepción AbstractSharedFlowSloty herencia SynchronizedObjectde la clase ;
  • SynchronizedObject 类: La rutina es responsable de proporcionar el objeto de bloqueo del parámetro del synchronized(lock: SynchronizedObject, block: () -> T)método ;
  • AbstractSharedFlowSlot<SharedFlowImpl<*>> 抽象类: Se definen los métodos fun allocateLocked(flow: F): Booleany fun freeLocked(flow: F): Array<Continuation<Unit>?>, que representan respectivamente la aplicación y el lanzamiento de SharedFlowSlot asociado con el suscriptor;
  • SharedFlowSlot 类: Herede la clase AbstractSharedFlowSlot, implemente allocateLockedy freeLockedresuma métodos, y defina atributos var index = -1Ly var cont: Continuation<Unit>? = null, donde índice indica el índice de los datos que se procesarán en la matriz de caché, y cont indica la continuación del suscriptor que espera que se envíen nuevos datos (suscripción envuelta Por);
  • BufferOverflow 枚举类: La estrategia de manejo de desbordamiento de búfer en el flujo, el valor de enumeración SUSPENDindica que ascendente de envío o envío del valor se suspende cuando el búfer está lleno, el valor de enumeración DROP_OLDESTindica que el valor más antiguo en el búfer se elimina cuando se desborda, y el nuevo el valor se agrega al área de caché, no suspender, el valor de enumeración DROP_LATESTsignifica eliminar el último valor agregado actualmente al búfer cuando el búfer se desborda (para que el contenido del búfer permanezca sin cambios), no suspender;
  • SharedFlowImpl 类: la clase de implementación real de SharedFlow, que hereda AbstractSharedFlow<SharedFlowSlot>la clase abstracta e implementa MutableSharedFlow<T>, CancellableFlow<T>, FusibleFlow<T>la interfaz.

A partir de la información anterior, después de crear un SharedFlow, las capacidades que proporciona se pueden resumir de la siguiente manera: puede usar para emittransmitir datos, el almacenamiento en caché de datos está involucrado durante la transmisión, la estrategia de desbordamiento del búfer, si se suspenderá, etc.; también puede use collectsubscribe, subscribe Se trata de cuestiones como la gestión de suscriptores, la adquisición de datos y si se suspenderá .

emisión

SharedFlowImplLa clase FlowCollectorimplementa emitel método de la interfaz, cuando se llama al método emit:

  1. Esta llamada puede suspenderse si hay suscriptores collectactualmente SharedFlow y onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND;
  2. Si ningún suscriptor está utilizando SharedFlow collectactualmente , el búfer no se utilizará. Si replay != 0, el valor emitido más recientemente simplemente se almacena en el caché de reproducción y reemplaza el elemento anterior en el caché de reproducción, si replay=0, el valor emitido más recientemente se descarta;
  3. El método emit está suspendmodificado y hay un suspendmétodo no modificado relacionado con él: tryEmit;
  4. Este método es seguro para subprocesos y se puede llamar de forma segura desde corrutinas simultáneas sin sincronización externa.

Las llamadas al método de emisión pueden bloquearse

Primero, observe emitla implementación del método:

    override suspend fun emit(value: T) {
        if (tryEmit(value)) return // fast-path
        emitSuspend(value)
    }

Que el método emit se suspenda depende principalmente del valor de retorno tryEmit(value)del método , si devuelve verdadero, no se ejecutará emitSuspend(value), es decir, no se suspenderá, de lo contrario emitSuspend(value), se suspenderá el emit.

Primero veamos los ejemplos en los que el método de emisión no se bloqueará y se bloqueará:

不会挂起,调用的是 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)

extraBufferCapacity = 1Cambie el método de construcción MutableSharedFlow anterior a extraBufferCapacity = 0, y mantenga los demás sin cambios:

会挂起,调用的是 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)

La diferencia entre los dos registros anteriores es:

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

A partir de la comparación del registro de salida, cuando onBufferOverflowla política BufferOverflow.SUSPENDes , si el espacio de caché extraBufferCapacitytiene un valor, la emisión no se suspenderá; de lo contrario, se suspenderá. Por lo tanto, ahora es posible conjeturar onBufferOverflowque extraBufferCapacitylos valores de y afectarán el valor de retorno del tryEmitmétodo .

  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
    }

El valor de retorno del método tryEmit depende del emittedvalor de emited, que a su vez depende del valor de retorno tryEmitLockeddel método . Si el valor de retorno de tryEmitLocked es falso depende de:

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

Campos: bufferSize, bufferCapacity, minCollectorIndex, replayIndex, son todas variables globales de 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 的结尾位置的后一位索引

De la estructura de lógica de caché en SharedFlowImpl anterior, combinada con:

MutableSharedFlow<Int>(
        replay = 0,
        extraBufferCapacity = 1 或 0,
        onBufferOverflow = BufferOverflow.SUSPEND
    )

Cuando extraBufferCapacity = 1, llame al método emit para emitir datos, luego bufferSize = 0, bufferCapacity = 1, minCollectorIndex = 0, replayIndex = 0, por lo que bufferSize >= bufferCapacity && minCollectorIndex <= replayIndexes y emit no se suspenderá .

Cuando extraBufferCapacity = 0, llame al método emit para emitir datos, en este momento bufferSize=0, bufferCapacity=0, minCollectorIndex=0, replayIndex=0, por lo que bufferSize >= bufferCapacity && minCollectorIndex <= replayIndexes BufferOverflow.SUSPENDfalso , por lo que ejecutará emitSuspend y emit se suspenderá.

Es por eso que llamar al método emit puede bloquearse. De hecho, satisfacer bufferSize >= bufferCapacity && minCollectorIndex <= replayIndexla condición de juicio significa un desbordamiento del búfer. En este momento, debe elegir una estrategia de procesamiento, ya sea BufferOverflow.SUSPEND, BufferOverflow.DROP_LATEST o BufferOverflow.DROP_OLDEST .

área de caché

El valor emitido por emit se almacena bufferen :

private var buffer: Array<Any?>? = null // 缓存数组,用于保存 emit 发送的数据

El búfer contiene: buffered valuesy queued emitters.

buffered valuesEl valor almacenado en el método emit(value), el tamaño de los valores almacenados en búfer depende de bufferCapacity=replay + extraBufferCapacity, replay y extraBufferCapacity son los valores MutableSharedFlow( replay: Int = 0, extraBufferCapacity: Int = 0, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND )pasados , por lo que los valores almacenados en búfer también se dividen en dos partes: reproducción y extraBufferCapacity.

queued emittersEntre ellos , el valor almacenado en el método emit(value) se envuelve en Emitterun valor:

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

Cuando se suspende emit (los valores del búfer en el área del búfer están llenos o el tamaño es 0), el valor del Emisor se almacena bufferen .

Ahora, para almacenar el valor en el búfer, existen los siguientes enlaces:

  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)
    }
Ningún suscriptor está recopilando el SharedFlow actual

Cuando no haya ningún suscriptor collectactualmente SharedFlow, almacene el valor en el búfer e intente seguir el enlace:

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

En este momento, si replay=0 nuevamente, el búfer no se utilizará. De lo contrario, el valor en emit(value) se almacenará en el rango inicial y final de replayCache de valores almacenados en el búfer en la matriz de búfer.

El valor de la repetición

Cuando un suscriptor es collectactualmente SharedFlow, en este momento, si replay=0, extraBufferCapacity=0, intentará seguir el enlace:

  • emitemitSuspendEmitterenqueueLocked

El valor de emit(value) se empaqueta en un objeto Emitter y se almacena en el rango inicial y final de emisores en cola en la matriz de búfer. Cuando no haya suscriptores, el valor en emit(value) será descartado.

Si replay!=0 o extraBufferCapacity!=0, intentará seguir el enlace:

  • emittryEmittryEmitLockedenqueueLocked
  • emitemitSuspendtryEmittryEmitLockedenqueueLocked
  • emitemitSuspendEmitterenqueueLocked

El valor en el método emit(value) se almacenará en el rango inicial y final de los valores almacenados en el búfer o emisores en cola en la matriz del búfer. Cuando se almacena en el rango inicial y final de los valores almacenados en el búfer, el valor en el rango inicial y final El rango de replayCache será Las actualizaciones también se verán afectadas por la estrategia de desbordamiento de búfer en BufferOverflow (BufferOverflow.SUSPEND, BufferOverflow.DROP_LATEST o BufferOverflow.DROP_OLDEST).

recolectar

De acuerdo con el análisis de emisión anterior, la recopilación es para obtener el valor bufferdel , puede obtener el valor de valor directamente del área de valores de búfer, o puede obtener el objeto Emisor de los emisores en cola y desensamblar el valor de valor .

SharedFlowImplLa clase Flowimplementa collectel método de la interfaz:

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

Los pasos principales cuando un suscriptor se suscribe son:

  1. Asignar un SharedFlowSlot:val slot = allocateSlot()
  2. Obtenga el valor del buffer buffer a través de la ranura asignada: newValue = tryTakeValue(slot); Si el valor se obtiene con éxito, vaya al siguiente paso directamente, de lo contrario, la rutina del suscriptor se suspenderá, esperando que emit emita nuevos datos al buffer:awaitValue(slot)
  3. Confirme si la corrutina donde se encuentra el suscriptor aún está viva, de lo contrario, se lanzará CancellationExceptionuna excepción y irá directamente a finalmente:collectorJob?.ensureActive()
  4. Dar el nuevo valor al suscriptor:collector.emit(newValue as T)
  5. Cuando el suscriptor no esté vivo, libere el espacio asignado:freeSlot(slot)

Analicemos allocateSlot, y . tryTakeValue(slot)_awaitValuefreeSlot

Asignar ranura

allocateSlotEl método se define en la clase AbstractSharedFlowabstracta :

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

De la lógica del código anterior, la función principal de este método es: asignar un objeto SharedFlowSlot al suscriptor, que puede usarse para asociar el índice del valor obtenido del búfer del búfer, es decir, para determinar el valor que el suscriptor recibir Y suspender la corrutina donde se encuentra el suscriptor, esperando que se envíe un nuevo valor al búfer del búfer.

Acerca de SharedFlowSlotla clase :

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
    }

La variable del objeto de ranura asignada al suscriptor index, el índice inicial del valor obtenido del búfer del búfer es replayIndex (index=replayIndex), es decir, el nuevo suscriptor obtiene el valor desde la posición inicial en el replayCache.

intentarTakeValue

tryTakeValueLa función del método es: obtener el valor del búfer del búfer a través del índice SharedFlowSlotde , y el índice puede señalar el rango de valores almacenados en el búfer o las posiciones inicial y final de los emisores en cola en el búfer del búfer. Cuando el valor se obtiene con éxito, el índice apunta a la siguiente posición del búfer 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
    }

Para juzgar si el índice está en línea con los valores almacenados en el búfer o el rango de posición inicial y final de los emisores en cola, principalmente a través tryPeekLockeddel método :

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

Cuando el método tryTakeValue devuelve NO_VALUEun valor , es decir -1L, cuando el método tryPeekLocked devuelve y el índice correspondiente no se puede encontrar en el búfer, se ejecutará 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
        }
    }

La función principal de este método es encapsular al suscriptor en Continuationun objeto de clase de implementación de interfaz y suspender la rutina donde se encuentra el suscriptor.

slot.cont es un objeto de clase de implementación Continuationde interfaz :

public interface Continuation<in T> {
    /**
     *  关联的协程
     */
    public val context: CoroutineContext

    /**
     *  恢复关联的协程,传递一个 successful 或 failed 结果值过去
     *  
     */
    public fun resumeWith(result: Result<T>)
}

Está contextasociado a la corrutina donde se encuentra el suscriptor. Entonces, el valor de slot.cont almacena el objeto Continuación del suscriptor asociado.

ranura libre

El método freeSlot corresponde a allocateSlot, cuando el suscriptor ya no esté vivo, freeSlotel método se ejecutará:

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

La función principal de este método: el número de suscriptores registrados se reduce en 1, y el índice y el cont en el objeto de ranura se restablecen, es decir, el índice ya no apunta al rango de las posiciones inicial y final del búfer, y cout ya no está asociado con la rutina del suscriptor.

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

flow.updateCollectorIndexLocked(oldIndex)El método llamado en freeLocked se usa para actualizar la ubicación de la matriz de caché .

En este punto, la creación, el envío, la recopilación y el análisis de SharedFlow han terminado. Tiene una comprensión general de sus características: compartir y existencia independiente.

Escena de negocios

Después de conocer los principios de creación, envío y recopilación de SharedFlow, basados ​​en su existencia compartida e independiente, se puede utilizar como un bus de eventos en los negocios , similar al EventBus utilizado anteriormente. El siguiente es un ejemplo simple de EventBus implementado por SharedFlow:

Defina el bus de eventos:

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

Emisión y suscripción de eventos:

        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

El uso de SharedFlow como un bus de eventos tiene las siguientes ventajas:

  1. Los eventos se pueden enviar tarde
  2. Los eventos fijos se pueden definir
  3. Los eventos pueden detectar el ciclo de vida de la Actividad o Fragmento
  4. los eventos estan ordenados

Resumir

En el flujo dinámico SharedFlow, existe después de que se crea y puede ejecutarse de forma independiente sin que el consumidor recopile datos cuando el productor emite datos. Después de que el productor emita los datos, los datos se almacenarán en caché y tanto los consumidores nuevos como los antiguos pueden recibir los datos para lograr datos compartidos.

Para la operación de emisión de datos, se verá afectada por los parámetros del constructor MutableSharedFlow replay, extraBufferCapacity, onBufferOverflow value, estos parámetros determinarán si la operación de emisión se suspende o no. Los datos transmitidos se administrarán mediante una matriz de búfer, y el área de administración se divide en valores almacenados en búfer y emisores en cola. Los parámetros de reproducción y extraBufferCapacity determinan el tamaño del área de valores almacenados en búfer. Cuando el área de valores almacenados en búfer está llena y se desborda, el área se ajustará de acuerdo con la política de desbordamiento onBufferOverflow. Cuando replay=0 y extraBufferCapacity=0, o replay!=0 y extraBufferCapacity!=0 y el área de valores almacenados en búfer está llena, los datos emitidos se empaquetarán como un emisor y se almacenarán en el área de emisores en cola. Además, el número de suscriptores determina si los datos emitidos se almacenan en el búfer o se descartan. Finalmente, los datos almacenados en el caché se comparten con todos los suscriptores.

Para las operaciones de recopilación de datos, use la matriz slots: Array<SharedFlowSlot?> para administrar suscriptores, donde cada objeto slot corresponde a un suscriptor, y el slot.index del objeto slot asocia los datos que recopilará el suscriptor con el área de caché , slot.cont asocia la rutina del suscriptor con el contexto de SharedFlow. Si el valor se puede obtener en el área de caché a través de slot.index, el valor se enviará directamente al suscriptor. De lo contrario, el suscriptor se encapsula en un objeto de clase de implementación de interfaz de continuación y se almacena en slot.cont, la rutina donde se encuentra el suscriptor se suspende y, cuando hay un valor en el búfer, la rutina del suscriptor se reanuda y se proporciona el valor. lo. Cuando la rutina del suscriptor no sobreviva, se liberará el objeto de ranura asociado al suscriptor, es decir, se restablecerán los valores de slot.inext y slot.cont, y se reajustará la posición de la matriz de caché.

flujo de estado

StateFlowTambién se implementa en base a SharedFlow, por lo que StateFlow puede entenderse como una existencia especial de SharedFlow.

public interface StateFlow<out T> : SharedFlow<T> {
    /**
     * The current value of this state flow.
     */
    public val value: T
}

StateFlow, que también es un flujo activo, también puede permitir que todos los recopiladores compartan el valor que emite, pero este valor es el valor más reciente y su instancia también puede existir independientemente de la existencia del recopilador.

A continuación se analizará desde la creación, envío y recopilación de StateFlow. El principio es similar a la creación, envío y recopilación de SharedFlow, por lo que aquí hay solo un análisis simple.

crear

Cree un StateFlow usando MutableStateFlowel constructor :

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 debe tener un valor inicial, que se almacena en caché en un objeto atómico: _state = atomic(initialState). Si este valor no se actualiza, el suscriptor recibirá este valor al suscribirse.

emisión

La clase StateFlowImpl implementa EmitData emity tryEmitmétodos:

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
    }

}

Estos dos métodos son para actualizar el valor _statealmacenado , si el valor actual es igual al nuevo valor, no se actualizará, de lo contrario, se actualizará y se le dará el nuevo valor al suscriptor.

recolectar

Según el análisis del lanzamiento anterior, la recopilación significa obtener valores _statedel .

StateFlowImplLa clase Flowimplementa collectel método de la interfaz:

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

Los pasos principales cuando un suscriptor se suscribe son:

  1. Asignar un StateFlowSlot:val slot = allocateSlot()
  2. _stateObtenga el valor del caché a través de la ranura asignada :val newState = _state.value
  3. Confirme si la corrutina donde se encuentra el suscriptor aún está viva, de lo contrario, se lanzará CancellationExceptionuna excepción y irá directamente a finalmente:collectorJob?.ensureActive()
  4. Dar el nuevo valor al suscriptor:collector.emit(NULL.unbox(newState))
  5. Cuando el suscriptor no esté vivo, libere el espacio asignado:freeSlot(slot)

Escena de negocios

Conozca la creación de StateFlow, el principio general de envío y recolección, y sus características de compartir el último estado. Se puede utilizar para la actualización de estado (en sustitución de LiveData) en los negocios .

Por ejemplo, obtenga datos de una lista del servidor y muestre los datos de la lista en la interfaz de usuario. Los siguientes usos MVI (Model-View-Intent)para hacerlo :

Capa de datos:

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

Ver modelo:

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

interfaz de usuario:

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

Después de reemplazar LiveData con StateFlow y reemplazar MVVM con MVI, puede tener las siguientes ventajas:

  1. La única fuente de datos confiable : puede haber una gran cantidad de LiveData en MVVM, lo que conduce a una lógica incontrolable de interacción de datos o actualización paralela. Agregue UIState combinado con StateFlow, y la fuente de datos es solo UIState;
  2. Flujo de datos unidireccional : en MVVM, los datos UI ⇆ ViewModel fluyen mutuamente, mientras que en MVI, los datos solo pueden fluir desde Data Layer → ViewModel → UI, y los datos fluyen en una dirección.

El uso de StateFlow para reemplazar LiveData para la actualización del estado del evento tiene las siguientes diferencias:

  • StateFlow requiere que se pase un estado inicial al constructor, LiveData no.
  • LiveData.observe() cancela automáticamente el registro del consumidor cuando View ingresa al estado DETENIDO, pero la recopilación de datos de StateFlow o cualquier otro flujo de datos no se detiene automáticamente. Para lograr el mismo comportamiento, recopile el flujo de datos del bloque Lifecycle.repeatOnLifecycle.

Resumir

El flujo de calor StateFlow se implementa en base a SharedFlow, por lo que también tiene las características de existencia independiente y uso compartido. Pero cuando se emiten datos en StateFlow, solo se almacena en caché el valor más reciente, por lo que cuando los suscriptores nuevos y antiguos se suscriban, solo recibirán el último valor actualizado. Si el nuevo valor emitido es igual al valor actual, el suscriptor no recibirá notificaciones.

Supongo que te gusta

Origin blog.csdn.net/wangjiang_qianmo/article/details/129505497
Recomendado
Clasificación