Um artigo leva você a entender completamente as corrotinas de Kotlin

Fundo

A fim de resolver o inferno de callback gerado por threads assíncronos

//传统回调方式
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)
...

O que é uma co-rotina

Essencialmente, as corrotinas são roscas leves.

Principais substantivos para corrotinas

val job = GlobalScope.launch {
    delay(1000)
    println("World World!")
}
  • CoroutineScope (escopo de ação)

    Controle o thread de execução e o ciclo de vida do bloco de código de co-rotina, incluindo GlobeScope, lifecycleScope, viewModelScope e outro CoroutineScope personalizado

    GlobeScope: escopo global, não encerrará automaticamente a execução

    lifecycleScope: escopo do ciclo de vida, usado para atividades e outros componentes do ciclo de vida, ele terminará automaticamente quando DESTRUÍDO, e introdução adicional é necessária

    viewModelScope: o escopo viewModel, usado em ViewModel, terminará automaticamente quando ViewModel for reciclado, introdução adicional é necessária

  • Trabalho

    A unidade de medida da co-rotina, que é equivalente a uma tarefa de trabalho. O método de lançamento retorna um novo trabalho por padrão

  • suspender

    Atuando no método, significa que o método é uma tarefa demorada, como o método de atraso acima

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

A introdução de corrotinas

Frame principal ($ coroutines_version foi substituído pela versão mais recente, como 1.3.9, a mesma abaixo)

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

lifecycleScope (opcional, versão 2.2.0)

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

viewModelScope (opcional, versão 2.3.0-beta01)

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

Simples de usar

Deixe-me dar um exemplo simples

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

A função implementada no exemplo acima é esperar 2 segundos e, em seguida, modificar o valor de texto do controle TextView cujo id é tvTest to Test

Método de retorno de atraso personalizado

No Kotlin, para métodos que exigem atraso para retornar resultados, você precisa usar suspender para indicar

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

Se você precisar usar Continuação para troca de thread em outros threads, você pode usar suspendCancellableCoroutine ou suspendCoroutine para encapsular (o primeiro pode ser cancelado, o que é equivalente à extensão do último), chame it.resume () com sucesso, chame-o. resumeWithException (Exception ()) se falhar, lança uma exceção

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

Captura de exceção

Falhas na co-rotina podem ser capturadas por exceções para lidar com situações especiais de maneira uniforme

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

Cancelar função

Duas tarefas são executadas abaixo, a primeira é original, a segunda é para cancelar a primeira tarefa após 1 segundo, o que fará com que o texto de tvText não mude

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

Definir tempo limite

Isso é equivalente ao sistema que encapsula a função de cancelamento automático, correspondendo à função withTimeout

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

Trabalho com valor de retorno

Semelhante ao lançamento, há também um método assíncrono, que retorna um objeto Deferred, que pertence à classe estendida de Job. Adiado pode obter o resultado retornado. O uso específico é o seguinte

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())
}

Avançado avançado

CoroutineScope personalizada

Primeiro, olhe para o código-fonte do CoroutineScope

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

CoroutineScope contém principalmente um objeto coroutineContext, só precisamos implementar o método get de coroutineContext para personalizar

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

Para criar um coroutineContext, você deve primeiro saber o que é CoroutineContext, e então veremos o código-fonte do 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 {
        ...
    }
}

Por meio de comentários, sabemos que é essencialmente uma coleção que contém o Elemento, mas ao contrário das coleções de conjuntos e mapas, ela implementa aquisição (get), dobramento (dobrar, uma combinação de adição e substituição) e subtração (minusKey, shift). ), combinação de objetos (mais, como val coroutineContext = coroutineContext1 + coroutineContext2)

Seu conteúdo principal é o Elemento, e a realização do Elemento tem

  • Tarefa do serviço
  • Interceptor ContinuationInterceptor
  • AbstractCoroutineContextElement
  • CoroutineExceptionHandler
  • ThreadContextElement
  • DownstreamExceptionElement

Você pode ver que Element é implementado em muitos lugares e seu objetivo principal é limitar o escopo e lidar com exceções. Aqui, primeiro entendemos dois elementos importantes, um é Job e o outro é CoroutineDispatcher

Trabalho
  • Trabalho: Se o trabalho filho for cancelado, o trabalho pai e outros trabalhos filho serão cancelados; se o trabalho pai for cancelado, todos os trabalhos filho serão cancelados
  • SupervisorJob: o trabalho pai é cancelado, todos os trabalhos filhos são cancelados
CoroutineDispatcher
  • Dispatchers.Main: execução do thread principal
  • Dispatchers.IO: execução de thread de IO

Simulamos um TestScope personalizado semelhante ao lifecycleScope

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

Aqui definimos uma linha de processo total SupervisorJob () e um ambiente de execução específico Dispatchers.Main (thread principal do Android). Se quisermos substituir o lifecycleScope da atividade, precisamos criar uma instância na atividade

val testScope=TestScope()

Em seguida, cancele todos os trabalhos quando a atividade for destruída

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

Outros métodos de uso são os mesmos que lifecycleScope, como

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

Compreensão profunda de Job

CoroutineScope contém um trabalho principal, e os trabalhos criados por lançamento ou outros métodos que são chamados posteriormente pertencem aos trabalhos filhos de CoroutineScope. Cada trabalho tem seu próprio estado, incluindo isActive, isCompleted, isCancelled e algumas operações básicas start (), cancel (), join (), o processo de conversão específico é o seguinte

job status diagram.png

Vamos começar criando um job. Quando o launch é chamado, há três parâmetros CoroutineContext, CoroutineStart e parâmetros de bloco de código por padrão.

  • contexto: O objeto de CoroutineContext, o padrão é CoroutineStart.DEFAULT, que será dobrado com o contexto de CoroutineScope
  • start: O objeto de CoroutineStart, o padrão é CoroutineStart.DEFAULT, que representa a execução imediata, e CoroutineStart.LAZY, que representa a execução não imediata, deve chamar start () da tarefa para iniciar a execução
val job2= lifecycleScope.launch(start =  CoroutineStart.LAZY) {
    delay(2000)
    Log.i("scope test","lazy")
}
job2.start()

Quando criado neste modo, o padrão é o novo estado. Neste momento, isActive, isCompleted e isCancelled são todos falsos. Quando start é chamado, ele é convertido para o estado ativo. Apenas isActive é true. Se sua tarefa for concluída , ele entrará no estado Concluindo. No momento, ele está aguardando a conclusão do trabalho filho. Nesse estado, apenas isActive é verdadeiro. Se todos os trabalhos filhos também forem concluídos, ele entrará no estado Concluído e apenas isCompleted é verdade. Se houver um cancelamento ou exceção no estado ativo ou Concluindo, ele entrará no estado Cancelando. Se for necessário cancelar o trabalho pai e outros trabalhos filho, ele aguardará a conclusão do cancelamento. No momento, apenas é cancelado. é verdadeiro. Depois que o cancelamento for concluído, ele finalmente entrará no estado Cancelado, isCancelled e isCompleted são ambos verdadeiros

Estado está ativo está completo é cancelado
Novo FALSO FALSO FALSO
Ativo VERDADE FALSO FALSO
Concluindo VERDADE FALSO FALSO
Cancelando FALSO FALSO VERDADE
Cancelado FALSO VERDADE VERDADE
Concluído FALSO VERDADE FALSO

Diferentes interações de trabalho precisam usar join () e cancelAndJoin ()

  • join (): Adiciona o trabalho atual a outras tarefas de co-rotina
  • cancelAndJoin (): cancela a operação, basta adicioná-la e depois cancelá-la
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")
}

Compreensão profunda da suspensão

Suspender como um novo modificador de método do kotlin, a implementação final ainda é java, vamos olhar suas diferenças

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

Correspondente ao código java

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

Bytecode correspondente

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 você pode ver, o método com suspend é na verdade o mesmo que o método normal, exceto que há um objeto Continuation adicional quando passado, e o objeto Unit.INSTANCE é retornado.

Continuação é uma interface que contém o objeto de contexto e o método resumeWith

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

A implementação específica de Continuation está em 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
                }
            }
        }
    }
    ...
}

Quando chamamos resumeWith, ele continua a executar um loop, chama invokeSuspend (param) e releaseIntercepted (), até que a conclusão de nível superior seja concluída e retorne, e libere o interceptor da co-rotina

A versão final é implementada em 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 
    }
}

Por aqui, a liberação é finalmente realizada por meio do Elemento de ContinuationInterceptor em CoroutineContext

O mesmo é verdadeiro para suspender, continue olhando para suspendCoroutine

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

O método interceptado () de Continuação é chamado por padrão

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

Pode-se ver que a suspensão é finalmente realizada pelo Elemento que é o ContinuationInterceptor no CoroutineContext.

Resumo do processo (troca de thread)

  • Crie uma nova continuação
  • Chame o método interceptContinuation do ContinuationInterceptor do contexto em CoroutineScope para suspender a tarefa pai
  • Executar subtarefas (se um thread for especificado, ele será executado em um novo thread e o objeto Continuation será passado)
  • Após a execução ser concluída, o usuário chama resume ou resumeWith of Continuation para retornar o resultado
  • Chame o método releaseInterceptedContinuation do ContinuationInterceptor do contexto em CoroutineScope para restaurar a tarefa pai

Bloqueio e não bloqueador

CoroutineScope não bloqueia o thread atual por padrão. Se você precisar bloquear, você pode usar runBlocking. Se você executar o código a seguir no thread principal, uma tela branca de 2s aparecerá.

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

Princípio de bloqueio: Executar runBlocking criará BlockingCoroutine por padrão, e BlockingCoroutine continuará a executar um loop até que o trabalho atual esteja no 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
        ...
      }    
      ...
    }
}

Deixe-me compartilhar com vocês uma cópia do manual prático avançado Kotlin aprimorado do Google (com demonstração).

Capítulo 1 Introdução ao Kotlin

  • Visão geral do Kotlin
  • Comparação de Kotlin e Java
  • Usando o Android Studio com habilidade
  • Conheça os tipos básicos de Kotlin
  • Entre na matriz de Kotlin
  • Entre na coleção Kotlin
  • Problema de coleta
  • Código completo
  • Gramática básica

Capítulo 2 Guia prático do Kotlin para evitar armadilhas

  • Os parâmetros de entrada do método são constantes e não podem ser modificados
  • Sem companheiro, INSTANCE?
  • Sobrecarga do Java, como você faz uma transição inteligente no Kotlin?
  • Postura vazia em Kotlin
  • Kotlin substitui os métodos na classe pai Java
  • Kotlin fica "implacável" e até TODO não desiste!
  • pit in is, as`
  • Compreensão da propriedade em Kotlin
  • também palavra-chave
  • palavra-chave takeIf
  • palavra-chave takeIf
  • Modo singleton

Capítulo 3 Projeto de combate real "Combate real do Kotlin Jetpack"

  • Começando com uma demonstração que adora um grande deus
  • Que tipo de experiência Kotlin está escrevendo scripts do Gradle?
  • O reino triplo da programação Kotlin
  • Funções de ordem superior do Kotlin
  • Genéricos Kotlin
  • Extensão Kotlin
  • Comissão Kotlin
  • Habilidades de depuração "desconhecidas" para corrotinas
  • Corrotina gráfica: suspender

Amigos que precisam deste documento PDF podem juntar a saia de comunicação aqui, frente: 1102, meio: 405 e finalmente: 044. Há alunos e marmanjos na saia, e os recursos são gratuitos para compartilhar.

Acho que você gosta

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