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:
- ¿A qué se refieren las corrientes frías y las corrientes calientes?
- En el desarrollo de negocios, ¿para qué se puede usar o resolver el flujo frío y el flujo caliente?
- ¿Cuál es la diferencia entre flujo frío y flujo caliente?
- ¿Cuál es el principio de funcionamiento del flujo frío?
- ¿Cómo gestiona SharedFlow los datos que emite?
- ¿Cómo administra SharedFlow a sus suscriptores?
- ¿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
RxJava
el 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
LiveData
yMVI
reemplazosMVVM
.
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 Sequences
lo mismo que .
Ahora, además de la forma anterior de crear flujos de datos, también puede usar SharedFlow
y 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.StateFlow
collect{}
collect{}
asList
asSet
testFlow.testSharedFlow()
testFlow.testStateFlow()
控制台输出结果:
Flow com.wangjiang.example E sharedFlow:emit 1
Flow com.wangjiang.example E sharedFlow:emit 2
Flow com.wangjiang.example E stateFlow:value 1
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 asList
y asSet
otras 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: map
transformació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:1
→ map:1
→ collect:1
, emit:2
→ map:2
→ collect:4
, no emit:1
→ emit:2
, map:1
→ map:2
, collect:1
→ collect: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 testColdFlow
el método y la consola generará collect
la 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: collect
→ AbstractFlow.collect
→ SafeFlow.collectSafely
→ map
→ SafeCollector.emit
→ emit
→ TestFlow$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: map
→ transform
→ unsafeTransform
→ unsafeFlow { }
→ 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, RxJava
es 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 MutableSharedFlow
el 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 SharedFlowImpl
instancia . Echemos un vistazo a la relación simple entre las clases y las interfaces asociadas con la clase SharedFlowImpl:
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 proporcionapublic 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, proporcionapublic 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 definapublic 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, entoncescollect(collecter: FlowCollecter<T>)
también seremit(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 类
: implementarcollect(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ónAbstractSharedFlowSlot
y herenciaSynchronizedObject
de la clase ;SynchronizedObject 类
: La rutina es responsable de proporcionar el objeto de bloqueo del parámetro delsynchronized(lock: SynchronizedObject, block: () -> T)
método ;AbstractSharedFlowSlot<SharedFlowImpl<*>> 抽象类
: Se definen los métodosfun allocateLocked(flow: F): Boolean
yfun 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, implementeallocateLocked
yfreeLocked
resuma métodos, y defina atributosvar index = -1L
yvar 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ónSUSPEND
indica que ascendente de envío o envío del valor se suspende cuando el búfer está lleno, el valor de enumeraciónDROP_OLDEST
indica 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ónDROP_LATEST
significa 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 heredaAbstractSharedFlow<SharedFlowSlot>
la clase abstracta e implementaMutableSharedFlow<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 emit
transmitir 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 collect
subscribe, subscribe Se trata de cuestiones como la gestión de suscriptores, la adquisición de datos y si se suspenderá .
emisión
SharedFlowImpl
La clase FlowCollector
implementa emit
el método de la interfaz, cuando se llama al método emit:
- Esta llamada puede suspenderse si hay suscriptores
collect
actualmente SharedFlow y onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND; - Si ningún suscriptor está utilizando SharedFlow
collect
actualmente , 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; - El método emit está
suspend
modificado y hay unsuspend
método no modificado relacionado con él:tryEmit
; - 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 emit
la 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 = 1
Cambie 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:
emit
→suspendImpl
→tryEmit
→CancellableContinuationImpl.resumeWith
→DispatchedTaskKt.resume
→TestFlowFragment$initView$5$1.emit
emit
→suspendImpl
→emitSuspend
→CancellableContinuationImpl.resumeWith
→DispatchedTaskKt.resume
→TestFlowFragment$initView$5$1.emit
A partir de la comparación del registro de salida, cuando onBufferOverflow
la política BufferOverflow.SUSPEND
es , si el espacio de caché extraBufferCapacity
tiene un valor, la emisión no se suspenderá; de lo contrario, se suspenderá. Por lo tanto, ahora es posible conjeturar onBufferOverflow
que extraBufferCapacity
los valores de y afectarán el valor de retorno del tryEmit
mé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 emitted
valor de emited, que a su vez depende del valor de retorno tryEmitLocked
del 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 <= replayIndex
es 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 <= replayIndex
es BufferOverflow.SUSPEND
falso , 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 <= replayIndex
la 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 buffer
en :
private var buffer: Array<Any?>? = null // 缓存数组,用于保存 emit 发送的数据
El búfer contiene: buffered values
y queued emitters
.
buffered values
El 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 emitters
Entre ellos , el valor almacenado en el método emit(value) se envuelve en Emitter
un 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 buffer
en .
Ahora, para almacenar el valor en el búfer, existen los siguientes enlaces:
emit
→tryEmit
→tryEmitLocked
→tryEmitNoCollectorsLocked
→enqueueLocked
emit
→tryEmit
→tryEmitLocked
→enqueueLocked
emit
→emitSuspend
→tryEmitLocked
→tryEmitNoCollectorsLocked
→enqueueLocked
emit
→emitSuspend
→tryEmit
→tryEmitLocked
→enqueueLocked
emit
→emitSuspend
→Emitter
→enqueueLocked
// enqueues item to buffer array, caller shall increment either bufferSize or queueSize
private fun enqueueLocked(item: Any?) {
val curSize = totalSize
val buffer = when (val curBuffer = buffer) {
// 创建一个大小为 2 的缓存数组
null -> growBuffer(null, 0, 2)
// 如果当前缓存数据已经存满,则扩容,扩大为原来的 2 倍
else -> if (curSize >= curBuffer.size) growBuffer(curBuffer, curSize,curBuffer.size * 2) else curBuffer
}
buffer.setBufferAt(head + curSize, item)
}
Ningún suscriptor está recopilando el SharedFlow actual
Cuando no haya ningún suscriptor collect
actualmente SharedFlow, almacene el valor en el búfer e intente seguir el enlace:
emit
→tryEmit
→tryEmitLocked
→tryEmitNoCollectorsLocked
→enqueueLocked
emit
→emitSuspend
→tryEmitLocked
→tryEmitNoCollectorsLocked
→enqueueLocked
private fun tryEmitLocked(value: T): Boolean {
// Fast path without collectors -> no buffering
if (nCollectors == 0) return tryEmitNoCollectorsLocked(value) // always returns true
// ..... 省略
return true
}
private fun tryEmitNoCollectorsLocked(value: T): Boolean {
assert { nCollectors == 0 }
if (replay == 0) return true // no need to replay, just forget it now
enqueueLocked(value) // enqueue to replayCache
bufferSize++ // value was added to buffer
// drop oldest from the buffer if it became more than replay
if (bufferSize > replay) dropOldestLocked()
minCollectorIndex = head + bufferSize // a default value (max allowed)
return true
}
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 collect
actualmente SharedFlow, en este momento, si replay=0, extraBufferCapacity=0, intentará seguir el enlace:
emit
→emitSuspend
→Emitter
→enqueueLocked
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:
emit
→tryEmit
→tryEmitLocked
→enqueueLocked
emit
→emitSuspend
→tryEmit
→tryEmitLocked
→enqueueLocked
emit
→emitSuspend
→Emitter
→enqueueLocked
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 buffer
del , 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 .
SharedFlowImpl
La clase Flow
implementa collect
el 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:
- Asignar un SharedFlowSlot:
val slot = allocateSlot()
- 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)
- Confirme si la corrutina donde se encuentra el suscriptor aún está viva, de lo contrario, se lanzará
CancellationException
una excepción y irá directamente a finalmente:collectorJob?.ensureActive()
- Dar el nuevo valor al suscriptor:
collector.emit(newValue as T)
- Cuando el suscriptor no esté vivo, libere el espacio asignado:
freeSlot(slot)
Analicemos allocateSlot
, y . tryTakeValue(slot)
_awaitValue
freeSlot
Asignar ranura
allocateSlot
El método se define en la clase AbstractSharedFlow
abstracta :
@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 SharedFlowSlot
la 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
tryTakeValue
La función del método es: obtener el valor del búfer del búfer a través del índice SharedFlowSlot
de , 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 tryPeekLocked
del 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_VALUE
un 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 Continuation
un 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 Continuation
de interfaz :
public interface Continuation<in T> {
/**
* 关联的协程
*/
public val context: CoroutineContext
/**
* 恢复关联的协程,传递一个 successful 或 failed 结果值过去
*
*/
public fun resumeWith(result: Result<T>)
}
Está context
asociado 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, freeSlot
el 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:
- Los eventos se pueden enviar tarde
- Los eventos fijos se pueden definir
- Los eventos pueden detectar el ciclo de vida de la Actividad o Fragmento
- 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
StateFlow
Tambié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 MutableStateFlow
el 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 emit
y tryEmit
mé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 _state
almacenado , 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 _state
del .
StateFlowImpl
La clase Flow
implementa collect
el 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:
- Asignar un StateFlowSlot:
val slot = allocateSlot()
_state
Obtenga el valor del caché a través de la ranura asignada :val newState = _state.value
- Confirme si la corrutina donde se encuentra el suscriptor aún está viva, de lo contrario, se lanzará
CancellationException
una excepción y irá directamente a finalmente:collectorJob?.ensureActive()
- Dar el nuevo valor al suscriptor:
collector.emit(NULL.unbox(newState))
- 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:
- 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;
- 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.