Un artículo lo lleva a comprender a fondo las corrutinas de Kotlin

Fondo

Para resolver el infierno de devolución de llamada generado por subprocesos asincrónicos

//传统回调方式
api.login(phone,psd).enquene(new Callback<User>(){
  public void onSuccess(User user){
    api.submitAddress(address).enquene(new Callback<Result>(){
      public void onSuccess(Result result){
        ...
      }
    });
  }
});
//使用协程后
val user=api.login(phone,psd)
api.submitAddress(address)
...

Que es una corrutina

Esencialmente, las corrutinas son hilos ligeros.

Sustantivos clave para corrutinas

val job = GlobalScope.launch {
    delay(1000)
    println("World World!")
}
  • CoroutineScope (ámbito de acción)

    Controle el hilo de ejecución y el ciclo de vida del bloque de código de corrutina, incluidos GlobeScope, lifecycleScope, viewModelScope y otros CoroutineScope personalizados

    GlobeScope: alcance global, no finalizará automáticamente la ejecución

    lifecycleScope: alcance del ciclo de vida, utilizado para la actividad y otros componentes del ciclo de vida, finalizará automáticamente cuando se DESTRUYE, y se requiere una introducción adicional

    viewModelScope: el alcance viewModel, utilizado en ViewModel, finalizará automáticamente cuando se recicle ViewModel, se requiere una introducción adicional

  • Trabajo

    La unidad de medida de la corrutina, que equivale a una tarea de trabajo. El método de inicio devuelve un nuevo trabajo de forma predeterminada.

  • suspender

    Actuar sobre el método, significa que el método es una tarea que requiere mucho tiempo, como el método de demora anterior

public suspend fun delay(timeMillis: Long) {
    ...
}

La introducción de corrutinas

Marco principal ($ coroutines_version se reemplaza con la última versión, como 1.3.9, la misma a continuación)

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"

lifecycleScope (opcional, versión 2.2.0)

implementation 'androidx.activity:activity-ktx:$lifecycle_scope_version'

viewModelScope (opcional, versión 2.3.0-beta01)

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$coroutines_viewmodel_version"

Fácil de usar

Dejame darte un ejemplo simple

lifecycleScope.launch { 
    delay(2000)
    tvTest.text="Test"
}

La función implementada en el ejemplo anterior es esperar 2 segundos y luego modificar el valor de texto del control TextView cuya identificación es tvTest to Test

Método de devolución de retraso personalizado

En Kotlin, para los métodos que requieren demora para devolver resultados, debe usar suspender para indicar

lifecycleScope.launch {
    val text=getText()
    tvTest.text = text
}
suspend fun getText():String{
    delay(2000)
    return "getText"
}

Si necesita usar Continuation para cambiar de hilo en otros hilos, puede usar suspendCancellableCoroutine o suspendCoroutine para envolver (el primero se puede cancelar, que es equivalente a la extensión del segundo), llamarlo.resume () exitosamente, llamarlo. resumeWithException (Exception ()) si falla, lanza una excepción

suspend fun getTextInOtherThread() = suspendCancellableCoroutine<String> {
    thread {
        Thread.sleep(2000)
        it.resume("getText")
    }
}

Captura de excepción

Las fallas en la corrutina pueden detectarse mediante excepciones para tratar situaciones especiales de manera uniforme

lifecycleScope.launch {
    try {
        val text=getText()
        tvTest.text = text
    } catch (e:Exception){
        e.printStackTrace()
    }
}

Cancelar función

A continuación se ejecutan dos trabajos, el primero es original, el segundo es cancelar el primer trabajo después de 1 segundo, lo que hará que el texto de tvText no cambie

val job = lifecycleScope.launch {
    try {
        val text=getText()
        tvTest.text = text
    } catch (e:Exception){
        e.printStackTrace()
    }
}
lifecycleScope.launch {
    delay(1000)
    job.cancel()
}

Establecer tiempo de espera

Esto es equivalente al sistema que encapsula la función de cancelación automática, correspondiente a la función conTimeout

lifecycleScope.launch {
    try {
        withTimeout(1000) {
            val text = getText()
            tvTest.text = text
        }
    } catch (e:Exception){
        e.printStackTrace()
    }
}

Trabajo con valor de retorno

Al igual que en el lanzamiento, también hay un método asíncrono, que devuelve un objeto diferido, que pertenece a la clase extendida de Trabajo. Diferido puede obtener el resultado devuelto. El uso específico es el siguiente

lifecycleScope.launch {
    val one= async {
        delay(1000)
        return@async 1
    }
    val two= async {
        delay(2000)
        return@async 2
    }
    Log.i("scope test",(one.await()+two.await()).toString())
}

Avanzado avanzado

CoroutineScope personalizado

Primer vistazo al código fuente de CoroutineScope

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

CoroutineScope contiene principalmente un objeto coroutineContext, solo necesitamos implementar el método get de coroutineContext para personalizar

class TestScope() : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = TODO("Not yet implemented")
}

Para crear un coroutineContext, primero debe saber qué es CoroutineContext, y luego miramos el código fuente de CoroutineContext

/**
 * Persistent context for the coroutine. It is an indexed set of [Element] instances.
 * An indexed set is a mix between a set and a map.
 * Every element in this set has a unique [Key].
 */
public interface CoroutineContext {
    public operator fun <E : Element> get(key: Key<E>): E?
    public fun <R> fold(initial: R, operation: (R, Element) -> R): R
    public operator fun plus(context: CoroutineContext): CoroutineContext = 
        ...
    public fun minusKey(key: Key<*>): CoroutineContext

    public interface Key<E : Element>
    public interface Element : CoroutineContext {
        ...
    }
}

A través de los comentarios, sabemos que es esencialmente una colección que contiene Element, pero a diferencia de las colecciones de conjuntos y mapas, implementa adquisición (obtener), plegado (plegado, una combinación de suma y reemplazo) y sustracción (minusKey, shift). ), combinación de objetos (más, como val coroutineContext = coroutineContext1 + coroutineContext2)

Su contenido principal es Element, y la realización de Element ha

  • Tarea de trabajo
  • Continuación Interceptor Interceptor
  • AbstractCoroutineContextElement
  • CoroutineExceptionHandler
  • ThreadContextElement
  • DownstreamExceptionElement
  • ...

Puede ver que Element está implementado en muchos lugares y su propósito principal es limitar el alcance y manejar excepciones. Aquí primero entendemos dos elementos importantes, uno es Job y el otro es CoroutineDispatcher

Trabajo
  • Trabajo: si se cancela el trabajo secundario, se cancelarán el trabajo principal y otros trabajos secundarios; si se cancela el trabajo principal, se cancelarán todos los trabajos secundarios
  • SupervisorJob: el trabajo principal se cancela, todos los trabajos secundarios se cancelan
CoroutineDispatcher
  • Dispatchers.Main: ejecución del hilo principal
  • Dispatchers.IO: ejecución de subprocesos IO

Simulamos un TestScope personalizado similar a lifecycleScope

class TestScope() : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = SupervisorJob() +Dispatchers.Main
}

Aquí definimos una línea de proceso total SupervisorJob () y un entorno de ejecución específico Dispatchers.Main (hilo principal de Android). Si queremos reemplazar el lifecycleScope de la actividad, necesitamos crear una instancia en la actividad.

val testScope=TestScope()

Luego cancele todos los trabajos cuando se destruya la actividad

override fun onDestroy() {
    testScope.cancel()
    super.onDestroy()
}

Otros métodos de uso son los mismos que lifecycleScope, como

testScope.launch{
    val text = getText()
    tvTest.text = text
}

Profundo entendimiento de Job

CoroutineScope contiene un trabajo principal, y los trabajos creados por el lanzamiento u otros métodos que se llaman posteriormente pertenecen a los trabajos secundarios de CoroutineScope. Cada trabajo tiene su propio estado, que incluye isActive, isCompleted, isCancelled y algunas operaciones básicas start (), cancel (), join (), el proceso de conversión específico es el siguiente

diagrama de estado del trabajo.png

Comencemos con la creación de un trabajo.Cuando se llama a launch, hay tres parámetros CoroutineContext, CoroutineStart y parámetros de bloque de código por defecto.

  • contexto: el objeto de CoroutineContext, el valor predeterminado es CoroutineStart.DEFAULT, que se plegará con el contexto de CoroutineScope
  • start: el objeto de CoroutineStart, el valor predeterminado es CoroutineStart.DEFAULT, que representa la ejecución inmediata, y CoroutineStart.LAZY, que representa la ejecución no inmediata, debe llamar a start () del trabajo para iniciar la ejecución
val job2= lifecycleScope.launch(start =  CoroutineStart.LAZY) {
    delay(2000)
    Log.i("scope test","lazy")
}
job2.start()

Cuando se crea en este modo, el valor predeterminado es el nuevo estado. En este momento, isActive, isCompleted y isCancelled son todos falsos. Cuando se llama al inicio, se convierte al estado activo. Solo isActive es verdadero. Si su tarea se completa , entrará en el estado Completado. En este momento, está esperando la finalización del trabajo secundario. En este estado, solo es Activo es verdadero. Si todos los trabajos secundarios también se completan, entrará en el estado Completado, y solo está Completado es verdad. Si hay una cancelación o excepción en el estado activo o Completado, entrará en el estado Cancelando. Si es necesario cancelar el trabajo principal y otros trabajos secundarios, esperará a que se complete su cancelación. En este momento, solo se cancela. es verdadero. Una vez completada la cancelación, finalmente entrará en el estado Cancelado, tanto isCancelled como isCompleted son verdaderos

Expresar está activo esta completado está cancelado
Nuevo FALSO FALSO FALSO
Activo CIERTO FALSO FALSO
Completando CIERTO FALSO FALSO
Cancelado FALSO FALSO CIERTO
Cancelado FALSO CIERTO CIERTO
Terminado FALSO CIERTO FALSO

Las diferentes interacciones de trabajo deben usar join () y cancelAndJoin ()

  • join (): agrega el trabajo actual a otras tareas de rutina
  • cancelAndJoin (): cancela la operación, solo agrégala y luego cancela
val job1= GlobleScope.launch(start =  CoroutineStart.LAZY) {
    delay(2000)
    Log.i("scope test","job1")
}
lifecycleScope.launch {
    job1.join()
    delay(2000)
    Log.i("scope test","job2")
}

Comprensión profunda de suspender

Suspender como un nuevo modificador de método de kotlin, la implementación final sigue siendo java, veamos sus diferencias

suspend fun test1(){}
fun test2(){}

Correspondiente al código java

public final Object test1(@NotNull Continuation $completion) {
  return Unit.INSTANCE;
}
public final void test2() {
}

Código de bytes correspondiente

public final test1(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
  ...
   L0
    LINENUMBER 6 L0
    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/lieni/android_c/ui/test/TestActivity; L0 L1 0
    LOCALVARIABLE $completion Lkotlin/coroutines/Continuation; L0 L1 1
    MAXSTACK = 1
    MAXLOCALS = 2

public final test2()V
   L0
    LINENUMBER 9 L0
    RETURN
   L1
    LOCALVARIABLE this Lcom/lieni/android_c/ui/test/TestActivity; L0 L1 0
    MAXSTACK = 0
    MAXLOCALS = 1

Como puede ver, el método con suspender es en realidad el mismo que el método normal, excepto que hay un objeto Continuation adicional cuando se pasa y se devuelve el objeto Unit.INSTANCE.

La continuación es una interfaz que contiene el objeto de contexto y el método resumeWith

public interface Continuation<in T> {
    public val context: CoroutineContext
    public fun resumeWith(result: Result<T>)
}

La implementación específica de Continuation está en BaseContinuationImpl

internal abstract class BaseContinuationImpl(...) : Continuation<Any?>, CoroutineStackFrame, Serializable {
    public final override fun resumeWith(result: Result<Any?>) {
        ...
        while (true) {
            ...
            with(current) {
              	val outcome = invokeSuspend(param)
                ...
                releaseIntercepted() 
                if (completion is BaseContinuationImpl) {
                    ...
                } else {
                    ...
                    return
                }
            }
        }
    }
    ...
}

Cuando llamamos a resumeWith, continuará ejecutando un bucle, llamar a invokeSuspend (param) y releaseIntercepted (), hasta que se complete la finalización de nivel superior y regrese, y liberará el interceptor de la corrutina.

La versión final se implementa en ContinuationImpl

internal abstract class ContinuationImpl(...) : BaseContinuationImpl(completion) {
    ...
    protected override fun releaseIntercepted() {
        val intercepted = intercepted
        if (intercepted != null && intercepted !== this) {
            context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted)
        }
        this.intercepted = CompletedContinuation 
    }
}

A través de aquí, el lanzamiento finalmente se realiza a través del Elemento de ContinuationInterceptor en CoroutineContext

Lo mismo ocurre con la suspensión, continúe mirando suspendCoroutine

public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
    suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
        val safe = SafeContinuation(c.intercepted())
        ...
    }

El método de continuación interceptado () se llama de forma predeterminada

internal abstract class ContinuationImpl(...) : BaseContinuationImpl(completion) {
    ...
    public fun intercepted(): Continuation<Any?> =intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }
}

Se puede ver que la suspensión finalmente se realiza mediante el elemento que es ContinuationInterceptor en el CoroutineContext.

Resumen del proceso (cambio de hilo)

  • Crear una nueva continuación
  • Llame al método interceptContinuation del ContinuationInterceptor del contexto en CoroutineScope para suspender la tarea principal
  • Ejecutar subtareas (si se especifica un hilo, se ejecutará en un nuevo hilo y se pasará el objeto Continuación)
  • Una vez completada la ejecución, el usuario llama a resume o resumeWith of Continuation para devolver el resultado.
  • Llame al método releaseInterceptedContinuation del ContinuationInterceptor del contexto en CoroutineScope para restaurar la tarea principal

Bloqueo y no bloqueo

CoroutineScope no bloquea el hilo actual de forma predeterminada. Si necesita bloquear, puede usar runBlocking. Si ejecuta el siguiente código en el hilo principal, aparecerá una pantalla blanca de 2 segundos.

runBlocking { 
    delay(2000)
    Log.i("scope test","runBlocking is completed")
}

Principio de bloqueo: la ejecución de runBlocking creará BlockingCoroutine de forma predeterminada, y BlockingCoroutine continuará ejecutando un bucle hasta que el trabajo actual esté en el estado isCompleted.

public fun <T> runBlocking(...): T {
    ...
    val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    return coroutine.joinBlocking()
}
private class BlockingCoroutine<T>(...) : AbstractCoroutine<T>(parentContext, true) {
    ...
    fun joinBlocking(): T {
      ...
      while (true) {
        ...
        if (isCompleted) break
        ...
      }    
      ...
    }
}

Permítanme compartir con ustedes una copia del manual práctico mejorado avanzado de Kotlin de Google (con demostración).

Capítulo 1 Introducción a Kotlin

  • Descripción general de Kotlin
  • Comparación de Kotlin y Java
  • Usando Android Studio hábilmente
  • Conoce los tipos básicos de Kotlin
  • Entra en la matriz de Kotlin
  • Entra en la colección Kotlin
  • Problema de cobranza
  • Código completo
  • Gramática básica

Capítulo 2 Guía práctica de Kotlin para evitar trampas

  • Los parámetros de entrada del método son constantes y no se pueden modificar
  • ¿Sin Compañero, INSTANCIA?
  • Sobrecarga de Java, ¿cómo se hace una transición inteligente en Kotlin?
  • Postura vacía en Kotlin
  • Kotlin sobrescribe los métodos en la clase principal de Java
  • ¡Kotlin se vuelve "despiadado" e incluso TODO no lo deja pasar!
  • hoyo es, como`
  • Comprensión de la propiedad en Kotlin
  • también palabra clave
  • takeIf palabra clave
  • takeIf palabra clave
  • Modo singleton

Capítulo 3 Proyecto de combate real "Combate real de Kotlin Jetpack"

  • Partiendo de una demostración que adora a un gran dios
  • ¿Qué tipo de experiencia tiene Kotlin escribiendo guiones de Gradle?
  • El triple reino de la programación de Kotlin
  • Funciones de orden superior de Kotlin
  • Genéricos de Kotlin
  • Extensión de Kotlin
  • Comisión de Kotlin
  • Habilidades de depuración "desconocidas" para corrutinas
  • Corutina gráfica: suspender

Los amigos que necesiten este documento PDF pueden unirse a la falda de comunicación aquí, frente: 1102, en medio: 405 y finalmente: 044. Hay estudiantes y tipos grandes en la falda, y los recursos son gratuitos para compartir.

Supongo que te gusta

Origin blog.csdn.net/zhireshini233/article/details/114854185
Recomendado
Clasificación