Las rutinas de Kotlin y el uso de Android en resumen (un conocimiento básico)

Inserte la descripción de la imagen aquí
Cuando aprendí las corutinas de Kotlin, leí muchos blogs y los resumiré aquí. Entre ellos, extraeré mucho del sitio web oficial y otras publicaciones de blog.
Esta serie de resumen contiene muchos artículos, comenzando con la introducción de corutinas, cómo usarlas, comparándolas con RxJava, cómo convertir el código existente en formas de corutina y la biblioteca tripartita existente de corutinas (inyección de dependencia, carga de imágenes, permisos Solicitud, etc.).

0 ¿Qué es la corutina?

Sitio web oficial : https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html El
sitio web oficial tiene una breve introducción sobre las corutinas, que puede entenderse simplemente como un paquete de hilos Java (usando CPS + La máquina de estado para completar la suspensión y la reanudación de la rutina), para que los desarrolladores puedan controlar más fácilmente la programación de subprocesos.

Tomado del crack de Bennyhuo, Kotlin coroutine (2), algunas introducciones a varias implementaciones comunes de coroutines :

Por lo tanto, la implementación de las rutinas también tiene los siguientes dos tipos según se abra o no la pila de llamadas correspondiente:

  • Existen rutinas de pila. Pila de rutina: cada rutina tendrá su propia pila de llamadas, que es algo similar a la pila de llamadas de un subproceso. En este caso, la implementación de la rutina está realmente muy cerca del subproceso, y la principal diferencia está en la programación.
  • Corutina sin pila: la rutina no tiene su propia pila de llamadas, y el estado del punto de suspensión se implementa a través de una sintaxis como una máquina de estados o un cierre.

Las corutinas de Kotlin son una implementación de corutinas sin pila. Su flujo de control depende del flujo de estado de la máquina de estados compilada por la propia rutina. El almacenamiento variable también se logra a través de la sintaxis de cierre.

Entonces, la corutina de Kotlin también se llama pseudocorutina, porque no corresponde a la pila de llamadas única en el sistema operativo.

También puede consultar los siguientes artículos:
análisis completo de corotinas de Kotlin (corutinas) (1), introducción de
corutinas análisis completo de corutinas de Kotlin (corutinas) (2), comprensión en profundidad de la suspensión de corutina, recuperación y programación de
grietas de corotinas de Kotlin (2) -Varias implementaciones comunes de corutinas

Usar escena

En el artículo sobre el uso de las rutinas de Kotlin para mejorar el rendimiento de la aplicación en el sitio web oficial, se presenta el escenario de uso de las rutinas,

En la plataforma Android, las rutinas ayudan a resolver dos problemas principales:

  • Administre tareas de larga duración. Si no se administran correctamente, estas tareas pueden bloquear el hilo principal y hacer que su aplicación se congele.
  • Proporcione seguridad del subproceso principal o llame de forma segura a la red o operaciones de disco desde el subproceso principal. (En otras palabras, la persona que llama al método no necesita saber qué efecto tendrá el método en el hilo principal actual, independientemente de si lleva mucho tiempo, si se generará una excepción y cómo manejar la excepción, todo lo que hace el escritor del método).

1 Introducción de corutinas en Android

implementación 'org.jetbrains.kotlinx: kotlinx-coroutines-core: 1.3.2'
implementación 'org.jetbrains.kotlinx: kotlinx-coroutines-android: 1.3.2'
implementación “org.jetbrains.kotlinx: kotlinx-coroutines-rx2: 1.3.2 ”

  • kotlinx-coroutines-core proporciona la API básica para las corutinas
  • kotlinx-coroutines-android proporciona un hilo principal de Android (similar al uso de io.reactivex.rxjava2: rxandroid cuando se usa RxJava )
  • kotlinx-coroutines-rx2 proporciona soporte para usar con RxJava

2 Un ejemplo de una corutina

CoroutineScope(Dispatchers.Main + Job()).launch {
  val user = fetchUser() // A suspending function running in the I/O thread.
  updateUser(user) // Updates UI in the main thread.
}

private suspend fun fetchUser(): User = withContext(Dispatchers.IO) {
  // Fetches the data from server and returns user data.
}

En el código anterior, la palabra clave CoroutineScopeindica un alcance de rutina y cualquier función pendiente debe ejecutarse dentro de un alcance.
Dispatchers.Main + Job()Representa un contexto de rutina CoroutineContext, que incluye un planificador para especificar la rutina (es decir, en qué hilo Reanudar reanuda la rutina) y un trabajo de rutina principal, y un controlador de excepciones de lógica de manejo de excepciones.
launchIndica un constructor de corutina y el cuerpo de corutina entre paréntesis (incluida la función de suspensión y la función ordinaria).

3 tipos de coroutinecope

(1) CoroutineScope

Como se muestra en el código anterior, este alcance de rutina se usa principalmente CoroutineContextpara construir un Alcance utilizando un contexto de rutina personalizado , como se explicó anteriormente.

(2) MainScope

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

A través de la definición del código MainScope anterior, se puede ver que utiliza Dispatchers. Principal, es decir, la rutina se ejecutará en el hilo principal. SupervisorJob () especifica cualquier sub-trabajo en el cuerpo de la rutina (puede entenderse como una tarea de rutina ) Cuando la ejecución falla, no afectará a otros trabajos secundarios.

(2) GlobalScope

Las rutinas de nivel superior creadas por este Alcance no estarán relacionadas con ningún otro Trabajo, y su ciclo de vida sigue el ciclo de vida de toda la aplicación, y no se cancelará prematuramente, por lo que generalmente este Alcance debe usarse con cuidado para evitar el consumo de recursos y memoria.

4 CoroutineContext

CoroutineContext contiene principalmente tres elementos principales: Despachadores, lógica de manejo de excepciones CoroutineExceptionHandler y un trabajo de tarea de rutina principal.

(1) Despachadores

Los despachadores se utilizan para especificar en qué subproceso se ejecuta el cuerpo de rutina actual. Existen principalmente las siguientes categorías:

  • Dispatchers.Default
    Utilice un grupo de subprocesos compartidos, el número máximo de subprocesos es el número de núcleos de CPU, el mínimo es 2 y el estilo de nombre es Thread [DefaultDispatcher-worker-2,5, main] , que es adecuado para tareas intensivas de CPU.
  • Dispatchers.IO
    Los hilos se comparten con Dispatchers.Default, pero el número de hilos está limitado por kotlinx.coroutines.io.parallelism. El valor predeterminado es 64 hilos o el número de núcleos (lo que sea mayor). Adecuado para tareas intensivas de IO.
  • Dispatchers.Main
    El hilo principal de Android, el nombre del hilo es Thread [main, 5, main]
  • Dispatchers.Unconfined
    El programador de rutina no está limitado a ningún subproceso en particular. La corutina se ejecuta primero en el hilo actual y permite que se reanude en cualquier hilo usado por la función de suspensión correspondiente.
    Un ejemplo de uso de Dispatchers.Unconfined, con comentarios que explican en qué hilo se ejecutará cada parte del cuerpo de rutina.
CoroutineScope(Dispatchers.Unconfined).launch {
    // Writes code here running on Main thread.
    
    delay(1_000)
    // Writes code here running on `kotlinx.coroutines.DefaultExecutor`.
    
    withContext(Dispatchers.IO) { ... }
    // Writes code running on I/O thread.
    
    withContext(Dispatchers.Main) { ... }
    // Writes code running on Main thread.
}

(2) Manejador de excepciones CoroutineExceptionHandler

En general, las excepciones no detectadas solo pueden generarse mediante corutinas creadas con el generador de inicio . Las rutinas creadas usando async siempre capturarán todas sus excepciones y las representarán en el objeto diferido generado.

Ejemplo 1:

try {
  CoroutineScope(Dispatchers.Main).launch {
    doSomething()
  }
} catch (e: IOException) {
  // Cannot catch IOException() here.
  Log.d("demo", "try-catch: $e")
}

private suspend fun doSomething() {
  delay(1_000)
  throw IOException()
}

En el código anterior, el intento de capturar toda la rutina fuera del pan no funciona, la aplicación aún se bloqueará.

// Handles coroutine exception here.
val handler = CoroutineExceptionHandler { _, throwable ->
  Log.d("demo", "handler: $throwable") // Prints "handler: java.io.IOException"
}

CoroutineScope(Dispatchers.Main + handler).launch {
  doSomething()
}

private suspend fun doSomething() {
  delay(1_000)
  throw IOException()
}

En el código anterior, use CoroutineExceptionHandler para capturar las excepciones que ocurren en el cuerpo de la rutina.

// Handles coroutine exception here.
val handler = CoroutineExceptionHandler { _, throwable ->
  // Won't print the log because the exception is "CancellationException()".
  Log.d("demo", "handler: $throwable")
}

CoroutineScope(Dispatchers.Main + handler).launch {
  doSomething()
}

private suspend fun doSomething() {
  delay(1_000)
  throw CancellationException()
}

En el código anterior, si el cuerpo de la rutina arroja una CancellationException, entonces el CoroutineExceptionHandler no puede ser atrapado porque arrojar la CancellationException es el mecanismo normal para terminar la coroutina.


val job = CoroutineScope(Dispatchers.Main).launch {
  doSomething()
}

job.invokeOnCompletion {
    val error = it ?: return@invokeOnCompletion
    // Prints "invokeOnCompletion: java.util.concurrent.CancellationException".
    Log.d("demo", "invokeOnCompletion: $error")
  }
}

private suspend fun doSomething() {
  delay(1_000)
  throw CancellationException()
}

En el código anterior, use el método invokeOnCompletion del objeto Job para capturar todas las excepciones, incluida CancellationException .

5 Job vs SupervisorJob

Mediante el uso de rutinas padre-hijo, se puede lograr un control estructurado del proceso.

val parentJob1 = Job()
val parentJob2 = Job()
val childJob1 = CoroutineScope(parentJob1).launch {
    val childJob2 = launch { ... }
    val childJob3 = launch(parentJob2) { ... }
}

La relación entre las corutinas padre e hijo especificadas en el código anterior se muestra en la siguiente figura:
Inserte la descripción de la imagen aquí
cuando se cancela la corutina padre, todas sus corutinas hijo se cancelarán inmediatamente:

val parentJob = Job()
CoroutineScope(Dispatchers.Main + parentJob).launch {
    val childJob = launch {
        delay(5_000)
        
        // This function won't be executed because its parentJob is 
        // already cancelled after 1 sec. 
        canNOTBeExcecuted()
    }
    launch {
        delay(1_000)
        parentJob.cancel() // Cancels parent job after 1 sec.
    }
}

⚠️ Nota:
Si una rutina infantil arroja una excepción que no sea CancellationException , su rutina principal y todas las corutinas secundarias se cancelarán.
La corutina principal puede usar cancelChildren () para cancelar todas sus corutinas secundarias sin cancelar su propio cuerpo de corutina
Si se cancela un trabajo, ya no se puede usar como trabajo principal

Job tiene una variedad de estados, podemos usar Job.isActive para determinar si la rutina actual todavía está en el estado Activo.
Inserte la descripción de la imagen aquí
Si la rutina principal se especifica usando SupervisorJob, cualquier excepción a cualquier rutina secundaria no afectará la ejecución de otras corutinas secundarias. Como se muestra en el siguiente código:

val parentJob = Job()
val handler = CoroutineExceptionHandler { _, _ -> }
val scope = CoroutineScope(Dispatchers.Default + parentJob + handler)
val childJob1 = scope.launch {
    delay(1_000)
    // ChildJob1 fails with the IOException().
    throw IOException()
}

val childJob2 = scope.launch {
    delay(2_000)
    // This line won't be executed due to childJob1 failure.
    canNOTBeExecuted()
}
val parentJob = SupervisorJob()
val handler = CoroutineExceptionHandler { _, _ -> }
val scope = CoroutineScope(Dispatchers.Default + parentJob + handler)
val childJob1 = scope.launch {
    delay(1_000)
    // ChildJob1 fails with the IOException().
    throw IOException()
}

val childJob2 = scope.launch {
    delay(2_000)
    // Since we use SupervisorJob() as parent job, the failure of
    // childJob1 won't affect other child jobs. This function will be 
    // executed.
    canDoSomethinghHere()
}

6 constructor de rutina

Launch y async son las dos corutinas incorporadas, que se utilizan para ejecutar el cuerpo de la misma sincrónicamente y asincrónicamente.
Dos funciones de suspensión se ejecutan sincrónicamente en el siguiente código:

override fun onCreate(savedInstanceState: Bundle?) {
  ...

  val scope = MainScope()
  scope.launch {
    val time = measureTimeMillis {
      val one = fetchDataFromServerOne()
      val two = fetchDataFromServerTwo()
      Log.d("demo", "The sum is ${one + two}")
    }
    Log.d("demo", "Completed in $time ms")
  }
}

private suspend fun fetchDataFromServerOne(): Int {
  Log.d("demo", "fetchDataFromServerOne()")
  delay(1_000)
  return 1
}
  
private suspend fun fetchDataFromServerTwo(): Int {
  Log.d("demo", "fetchDataFromServerTwo()")
  delay(1_000)
  return 2
}

La salida del registro es la siguiente:

2019-12-09 00: 00: 34.547 D / demo: fetchDataFromServerOne ()
2019-12-09 00: 00: 35.553 D / demo: fetchDataFromServerTwo ()
2019-12-09 00: 00: 36.555 D / demo: La suma es 3
2019-12-09 00: 00: 36.555 D / demo: Completado en 2008 ms

El siguiente código usa async para ejecutar asincrónicamente dos funciones de suspensión:

override fun onCreate(savedInstanceState: Bundle?) {
  ...
  
  val scope = MainScope()
  scope.launch {
    val time = measureTimeMillis {
      val one = async { fetchDataFromServerOne() }
      val two = async { fetchDataFromServerTwo() }
      Log.d("demo", "The sum is ${one.await() + two.await()}")
    }
    
    // Function one and two will run asynchrously,
    // so the time cost will be around 1 sec only. 
    Log.d("demo", "Completed in $time ms")
  }
}

private suspend fun fetchDataFromServerOne(): Int {
  Log.d("demo", "fetchDataFromServerOne()")
  delay(1_000)
  return 1
}

private suspend fun fetchDataFromServerTwo(): Int {
  Log.d("demo", "fetchDataFromServerTwo()")
  Thread.sleep(1_000)
  return 2
}

La salida del registro es la siguiente:

2019-12-08 23: 52: 01.714 D / demo: fetchDataFromServerOne ()
2019-12-08 23: 52: 01.718 D / demo: fetchDataFromServerTwo ()
2019-12-08 23: 52: 02.722 D / demo: La suma es 3
2019-12-08 23: 52: 02.722 D / demo: Completado en 1133 ms

Además de los dos constructores anteriores, también hay un constructor liveData para usar en ViewModel, ver las rutinas de Kotlin y el resumen de uso en Android (dos usados ​​con componentes de arquitectura Jetpack) .

Referencia:
Kotlin Coroutines en Android - Conceptos básicos sobre el
sitio web oficial documento: Coroutine Basics
sitio web oficial sobre la rutina Codelabs: Uso de Kotlin Coroutines en su aplicación de Android

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

Supongo que te gusta

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