Las rutinas de Kotlin y el resumen de uso en Android (comparación de cinco colecciones, secuencia, flujo y RxJava (a continuación))

Inserte la descripción de la imagen aquí

aplanamiento de flujo

Supongamos ahora que existe tal llamada:

fun requestFlow(i: Int): Flow<String> = flow {
    emit("$i: First") 
    delay(500) // wait 500 ms
    emit("$i: Second")    
}

(1..3).asFlow().map { requestFlow(it) }

Esta vez va a conseguir un flujo tal Flow<Flow<String>>, cuando aplastó el proceso final es separar en una Flow<String>colección de secuencias, y para este propósito se aplane y flatMap operadores. Sin embargo, debido a la naturaleza asincrónica del flujo, requieren diferentes modos de aplanamiento, por lo tanto, hay una serie de operadores de aplanamiento en el flujo.

flatMapConcat

El modo de concatenación es implementado por los operadores flatMapConcat y flattenConcat. Son los análogos más directos de los operadores de secuencia correspondientes. Esperan a que se complete el proceso interno y luego comienzan a recopilar el siguiente ejemplo, como se muestra en el siguiente ejemplo:

val startTime = System.currentTimeMillis() // remember the start time 
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms 
    .flatMapConcat { requestFlow(it) }                                                                           
    .collect { value -> // collect and print 
        println("$value at ${System.currentTimeMillis() - startTime} ms from start") 
    } 

La naturaleza del pedido de flatMapConcat se puede ver claramente en la salida:

1: Primero a 121 ms desde el inicio
1: Segundo a 622 ms desde el inicio
2: Primero a 727 ms desde el inicio
2: Segundo a 1227 ms desde el inicio
3: Primero a 1328 ms desde el inicio
3: Segundo a 1829 ms desde el inicio

flatMapMerge

Otro modo de aplanamiento es recopilar todas las secuencias entrantes al mismo tiempo y fusionar sus valores en una sola secuencia para que los valores se envíen lo antes posible. Lo implementan los operadores flatMapMerge y flattenMerge. Ambos aceptan un parámetro de concurrencia opcional, que limita el número de secuencias simultáneas que se recopilan simultáneamente (de forma predeterminada es igual a 16).

val startTime = System.currentTimeMillis() // remember the start time 
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms 
    .flatMapMerge { requestFlow(it) }                                                                           
    .collect { value -> // collect and print 
        println("$value at ${System.currentTimeMillis() - startTime} ms from start") 
    } 

La naturaleza de concurrencia de flatMapMerge es obvia:

1: Primero a 136 ms desde el inicio
2: Primero a 231 ms desde el inicio
3: Primero a 333 ms desde el inicio
1: Segundo a 639 ms desde el inicio
2: Segundo a 732 ms desde el inicio
3: Segundo a 833 ms desde el inicio

Tenga en cuenta que flatMapMerge llama a sus bloques de código secuencialmente (en este ejemplo, * {requestFlow (it)} ), pero al mismo tiempo recopila el flujo de resultados simultáneamente, lo que equivale a ejecutar primero el mapa secuenciado {requestFlow (it)} , y luego el resultado Llame a flattenMerge *

flatMapLatest

Como en la introducción anterior al operador collectLatest, la lógica del operador flatMapLatest cuando se aplana el flujo es la misma, es decir, cada vez que el flujo emite un nuevo valor, si el colector actual no se ha procesado, la ejecución se cancela y el nuevo lanzamiento Valor, es decir:

val startTime = System.currentTimeMillis() // remember the start time 
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms 
    .flatMapLatest { requestFlow(it) }                                                                           
    .collect { value -> // collect and print 
        println("$value at ${System.currentTimeMillis() - startTime} ms from start") 
    } 

El principio de funcionamiento del operador flatMapLatest se muestra en el siguiente registro:

1: Primero a 142 ms desde el inicio
2: Primero a 322 ms desde el inicio
3: Primero a 425 ms desde el inicio
3: Segundo a 931 ms desde el inicio


Excepciones de flujo

Cuando el código en el emisor emisor u operador arroja una excepción, el flujo también se completará con una excepción. Hay varias formas de lidiar con estas excepciones.

Use try-catch para atrapar la excepción del recopilador

fun foo(): Flow<Int> = flow {
    for (i in 1..3) {
        println("Emitting $i")
        emit(i) // emit next value
    }
}

fun main() = runBlocking<Unit> {
    try {
        foo().collect { value ->         
            println(value)
            check(value <= 1) { "Collected $value" }
        }
    } catch (e: Throwable) {
        println("Caught $e")
    } 
}  

El recopilador detendrá el procesamiento del flujo después de que ocurra una excepción, y la salida del registro después de la excepción se captura de la siguiente manera:

Emitiendo 1
1
Emitiendo 2
2
Atrapado java.lang.IllegalStateException: Recogido 2

Cualquier anomalía en el flujo puede ser atrapada

A través de try-catch, además de capturar excepciones lanzadas por el recolector, se pueden capturar las excepciones generadas por el operador intermedio y el operador terminal. El siguiente código genera una excepción en el operador intermedio:

fun foo(): Flow<String> = 
    flow {
        for (i in 1..3) {
            println("Emitting $i")
            emit(i) // emit next value
        }
    }
    .map { value ->
        check(value <= 1) { "Crashed on $value" }                 
        "string $value"
    }

fun main() = runBlocking<Unit> {
    try {
        foo().collect { value -> println(value) }
    } catch (e: Throwable) {
        println("Caught $e")
    } 
}   

Se detecta la excepción y finaliza el proceso del recopilador, el registro es el siguiente:

Emitiendo 1
cadena 1
Emitiendo 2
Atrapado java.lang.IllegalStateException: Falló el 2

Transparencia de excepción

Los flujos deben garantizar una transparencia excepcional. En otras palabras, no puede simplemente envolver toda la lógica de emisión y recolección de flujo en el try-catch como en el código anterior. Debe manejar la lógica de manejo de excepciones por separado para la parte de emisión de flujo, de modo que el colector ya no necesite preocuparse por lo que sucedió antes del procesamiento Condiciones anormales

Esto utiliza la captura del operador intermedio para realizar el procesamiento de captura de excepción en la parte de emisión del flujo, y se puede realizar un procesamiento diferente de acuerdo con la excepción capturada en el bloque de captura, incluidos los siguientes métodos de procesamiento:

  • Puedes lanzar una excepción de nuevo lanzando
  • La anormalidad se puede convertir en valor de emisión por emisión
  • Puede ignorar excepciones, o imprimir registros, o usar otra lógica de procesamiento de código

La excepción se convierte al valor de emisión de la siguiente manera:

foo()
    .catch { e -> emit("Caught $e") } // emit on exception
    .collect { value -> println(value) }

El registro es lo mismo que usar try-catch para ajustar todo el código:

Emitiendo 1
cadena 1
Emitiendo 2
Atrapado java.lang.IllegalStateException: Falló el 2

El operador catch garantiza la transparencia de la excepción del flujo, pero solo es válido para el flujo de flujo por encima del bloque catch. No puede manejar la excepción generada por el proceso de recopilación. El siguiente código genera una excepción en la recopilación y arrojará una excepción:

fun foo(): Flow<Int> = flow {
    for (i in 1..3) {
        println("Emitting $i")
        emit(i)
    }
}

fun main() = runBlocking<Unit> {
    foo()
        .catch { e -> println("Caught $e") } // does not catch downstream exceptions
        .collect { value ->
            check(value <= 1) { "Collected $value" }                 
            println(value) 
        }
} 

Para reemplazar todo el try-catch anidado del flujo, es decir, garantizar la transparencia de la excepción del flujo y permitir que la excepción en el proceso de recopilación se capture y procese, la lógica en el proceso de recopilación se puede transferir a la captura antes del procesamiento, como a través de la operación onEach El operador procesa los valores de secuencia asíncrona en flujo antes de capturar:

foo()
    .onEach { value ->
        check(value <= 1) { "Collected $value" }                 
        println(value) 
    }
    .catch { e -> println("Caught $e") }
    .collect()

La salida del registro será la misma que la anterior y se detectará la excepción:

Emitiendo 1
1
Emitiendo 2
Atrapado java.lang.IllegalStateException: Recogido 2

Finalización de flujo

Es posible que el flujo necesite realizar una operación cuando finaliza normalmente o cuando ocurre una excepción. Podemos usar try-catch-finally para agregar una operación, o podemos usar el operador onCompletion , que tiene un parámetro de tipo Throwable vacío para determinar la corriente final es normal o anormal, por supuesto, esto sólo puede ser determinado para onCompletion flujo antes del operador, y onCompletion operador no captura la excepción, esto va a continuar para propagar hacia abajo, el siguiente código:

foo()
    .onCompletion { println("Done") }
    .collect { value -> println(value) }
fun foo(): Flow<Int> = flow {
    emit(1)
    throw RuntimeException()
}

fun main() = runBlocking<Unit> {
    foo()
        .onCompletion { cause -> if (cause != null) println("Flow completed exceptionally") }
        .catch { cause -> println("Caught exception") }
        .collect { value -> println(value) }
} 

El código anterior generará el registro:

1
flujo completado excepcionalmente
atrapado excepción

fun foo(): Flow<Int> = (1..3).asFlow()

fun main() = runBlocking<Unit> {
    foo()
        .onCompletion { cause -> println("Flow completed with $cause") }
        .collect { value ->
            check(value <= 1) { "Collected $value" }                 
            println(value) 
        }
}

En el código anterior, el proceso anterior a onCompletion no generó una excepción, por lo que la causa es nula y la excepción generada en la recopilación aún se generará, el registro es el siguiente:

1
Flujo completado con una
excepción nula en el subproceso "main" java.lang.IllegalStateException: recopilada 2


Flujo de lanzamiento

En el código anterior, ejecutamos el flujo de procesamiento de flujo dentro de un bloque * runBlocking {} *, es decir, en una rutina, lo que hace que el procesamiento de flujo de flujo bloquee la ejecución del siguiente código, como:

// Imitate a flow of events
fun events(): Flow<Int> = (1..3).asFlow().onEach { delay(100) }

fun main() = runBlocking<Unit> {
    events()
        .onEach { event -> println("Event: $event") }
        .collect() // <--- Collecting the flow waits
    println("Done")
} 

Saldrá el registro:

Evento: 1
Evento: 2
Evento: 3
Hecho

Si queremos que el código después de la colección se ejecute al mismo tiempo, podemos usar el operador launchIn , que coloca el flujo del flujo en una nueva rutina, de modo que su código posterior se pueda ejecutar inmediatamente, de la siguiente manera:

fun main() = runBlocking<Unit> {
    events()
        .onEach { event -> println("Event: $event") }
        .launchIn(this) // <--- Launching the flow in a separate coroutine
    println("Done")
}   

Saldrá el registro:

Hecho
Evento: 1
Evento: 2
Evento: 3

El parámetro pasado por el operador launchIn debe ser un objeto que declare CoroutineScope, como el CoroutineScope creado por el generador de rutina runBlocking. Por lo general, este CoroutineScope es un objeto con un período de declaración limitado, como viewModelScope, lifecycleScope, etc. En este momento, su proceso de flujo interno también finalizará, onEach {...} .launchIn (scope) aquí puede funcionar como addEventListener (use un código para procesar el evento entrante en consecuencia y continúe trabajando más) .

El operador launchIn devuelve un trabajo al mismo tiempo , por lo que también podemos usar cancel para cancelar la rutina de recopilación del flujo, o usar join para ejecutar el trabajo .

Flujo y corrientes reactivas

La inspiración para el diseño de flujos de flujo proviene de los flujos reactivos, como RxJava, pero el objetivo principal del flujo es tener el diseño más simple posible, usar Kotlin, soporte de función de suspensión amigable y adherirse a la concurrencia estructurada.

Aunque es diferente de otros flujos reactivos (como RxJava), el flujo en sí mismo también es un flujo reactivo, por lo que puede usar la biblioteca de conversión proporcionada por Kotlin para convertir directamente entre los dos, como la rutina de Kotlin y el resumen de uso de Android (tres Reescriba devoluciones de llamada y llamadas RxJava para suspender funciones) como se menciona en kotlinx-coroutines-rx2 .


: :
Https://kotlinlang.org/docs/reference/coroutines/flow.html#suspending-functions
Desarrollo asincrónico en Android: RxJava Vs. Kotlin Flow

82 artículos originales publicados · Me gusta 86 · Visita 110,000+

Supongo que te gusta

Origin blog.csdn.net/unicorn97/article/details/105209834
Recomendado
Clasificación