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