Corotinas de Kotlin e o uso do Android em resumo (um conhecimento básico)

Insira a descrição da imagem aqui
Ao aprender as corotinas de Kotlin, leio muitos blogs e os resumirei aqui: entre eles, extrairei muito do site oficial e de outros posts do blog.
Esta série de resumos contém muitos artigos, começando com a introdução de corotinas, como usá-los, comparando com o RxJava, como converter o código existente em formulários de corotina e a biblioteca de três vias existente de corotinas (injeção de dependência, carregamento de imagem, permissões Solicitação, etc.).

0 O que é corotina

Site oficial : https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.htmlO
site oficial tem uma breve introdução sobre as corotinas, que podem ser simplesmente entendidas como um pacote de threads Java (usando CPS + A máquina de estado para concluir a suspensão e o resumo da corotina), para que os desenvolvedores possam controlar mais facilmente o agendamento de threads.

Retirado do crack de Bennyhuo, Kotlin corotina (2), algumas introduções a várias implementações comuns de corotinas :

Portanto, a implementação das corotinas também possui os dois tipos a seguir, de acordo com a abertura da pilha de chamadas correspondente:

  • Existem coroutines de pilha Stackout Coroutine: Cada coroutine terá sua própria pilha de chamadas, que é um pouco semelhante à pilha de chamadas de um encadeamento.Neste caso, a implementação da corotina é realmente muito próxima ao encadeamento, e a principal diferença está no agendamento.
  • Corotina sem pilha: a corotina não possui sua própria pilha de chamadas e o estado do ponto de interrupção é implementado por meio de uma sintaxe, como uma máquina de estado ou um fechamento.

As corotinas de Kotlin são uma implementação de corotinas sem pilha, cujo fluxo de controle depende do fluxo de estado da máquina de estado compilada pela própria corotina.O armazenamento variável também é obtido através da sintaxe de fechamento.

Portanto, a corotina de Kotlin também é chamada de pseudo corotina, porque não corresponde à pilha de chamadas exclusiva no sistema operacional.

Veja também os seguintes artigos:
KOTLIN co-rotinas (coroutines) completamente resolvido (a), co-rotinas Introdução
KOTLIN co-rotinas (co-rotinas) para a plena determinação (b), em profundidade compreensão da co-pendente processo, recuperação e programação de
pausa Kotlin história lado coroutine (2) -Várias implementações comuns de corotinas

Usar cena

No artigo sobre o uso de corotinas Kotlin para melhorar o desempenho do aplicativo no site oficial, o cenário do uso de corotinas é apresentado,

Na plataforma Android, as corotinas ajudam a resolver dois problemas principais:

  • Gerenciar tarefas de longa execução: se não forem gerenciadas adequadamente, essas tarefas podem bloquear o encadeamento principal e fazer com que seu aplicativo congele.
  • Forneça segurança de thread principal ou chame com segurança operações de rede ou disco a partir do thread principal. (Em outras palavras, o responsável pela chamada do método não precisa saber que efeito o método terá no encadeamento principal atual, independentemente de ser demorado, de gerar uma exceção e de como lidar com a exceção, todos executados pelo criador do método.)

1 Introduzindo corotinas no Android

implementação 'org.jetbrains.kotlinx: kotlinx-coroutines-core: 1.3.2'
implementação 'org.jetbrains.kotlinx: kotlinx-coroutines-android: 1.3.2'
implementação “org.jetbrains.kotlinx: kotlinx-coroutines-rx2: 1.3.2 "

  • O kotlinx-coroutines-core fornece a API básica para corotinas
  • O kotlinx-coroutines-android fornece um encadeamento principal do Android (semelhante ao uso de io.reactivex.rxjava2: rxandroid ao usar o RxJava )
  • O kotlinx-coroutines-rx2 fornece suporte para uso com o RxJava

2 Um exemplo de uma rotina

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

No código acima, a palavra-chave CoroutineScopeindica um escopo de corotina e todas as funções pendentes devem ser executadas dentro de um escopo.
Dispatchers.Main + Job()Representa um contexto de corotina CoroutineContext, incluindo um planejador para especificar a corotina (ou seja, em qual thread Resume retoma a corotina) e um Job de corotina pai, além de um manipulador de exceções de manipulação de exceção.
launchIndica um construtor de corotina e o corpo da corotina entre parênteses (incluindo a função de suspensão e a função comum).

3 Tipos de CoroutineScope

(1) CoroutineScope

Conforme mostrado no código acima, esse escopo de rotina é usado principalmente CoroutineContextpara criar um escopo usando um contexto de rotina personalizado , conforme explicado acima.

) 2) MainScope

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

Através da definição do código MainScope acima, pode-se ver que ele usa Dispatchers.Main, ou seja, a corotina será executada no encadeamento principal.SupervisorJob () especifica qualquer subobra no corpo da corotina (pode ser entendida como uma tarefa de corotina ) Quando a execução falha, ela não afeta outros trabalhos filhos.

(2) GlobalScope

As corotinas de nível superior criadas por este escopo não serão relacionadas a nenhum outro trabalho, e seu ciclo de vida segue o ciclo de vida de todo o aplicativo e não será cancelado prematuramente; portanto, esse escopo geralmente deve ser usado com cuidado para evitar o consumo de recursos e memória.

4 CoroutineContext

O CoroutineContext contém principalmente três elementos principais: Dispatchers, lógica de tratamento de exceções CoroutineExceptionHandler e uma tarefa pai de rotina principal Job.

(1) Despachantes

Os despachantes são usados ​​para especificar em qual encadeamento o corpo atual da rotina é executado.Há principalmente as seguintes categorias:

  • Dispatchers.Default
    Use um conjunto de encadeamentos compartilhados, o número máximo de encadeamentos é o número de núcleos da CPU, o mínimo é 2 e o estilo do nome é Encadeamento [DefaultDispatcher-worker-2,5, main] , adequado para tarefas com uso intenso da CPU.
  • Dispatchers.IO
    Os threads são compartilhados com Dispatchers.Default, mas o número de threads é limitado pelo kotlinx.coroutines.io.parallelism, o padrão é 64 threads ou o número de núcleos (o que for maior). Adequado para tarefas intensivas de IO.
  • Dispatchers.Main
    O thread principal do Android, o nome do thread é Thread [main, 5, main]
  • Dispatchers.Unconfined
    O agendador de rotinas não está limitado a nenhum encadeamento específico. A corotina é executada primeiro no encadeamento atual e permite que ela retorne a qualquer encadeamento usado pela função de suspensão correspondente.
    Um exemplo do uso de Dispatchers.Unconfined, com comentários explicando em qual thread cada parte do corpo da rotina será executada.
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) Manipulador de exceção CoroutineExceptionHandler

Normalmente, a exceção não capturada apenas usando o lançamento construtor gerado cria co-rotina. As corotinas criadas usando async sempre capturam todas as suas exceções e as representam no objeto Adiado gerado.

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

No código acima, a tentativa de captura de toda a rotina fora do pão não funciona, o aplicativo ainda trava.

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

No código acima, use CoroutineExceptionHandler para capturar as exceções que ocorrem no corpo da corotina.

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

No código acima, se o corpo da corotina lançar uma CancellationException, o CoroutineExceptionHandler não poderá ser capturado porque a execução de CancellationException é o mecanismo normal para finalizar a corotina.


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

No código acima, use o método invokeOnCompletion do objeto Job para capturar todas as exceções, incluindo CancellationException .

5 Trabalho vs Supervisor

Usando corotinas pai-filho, é possível obter um controle estruturado do processo.

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

O relacionamento entre as rotinas pai e filho especificadas no código acima é mostrado na figura a seguir:
Insira a descrição da imagem aqui
onde a rotina pai é cancelada, todas as suas rotinas filho serão canceladas imediatamente:

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:
Se uma corotina filho lançar uma exceção diferente de CancellationException , sua corotina pai e todas as corotinas filho serão canceladas.
A corotina principal pode usar cancelChildren () para cancelar todas as suas corotinas filho sem cancelar seu próprio corpo de corotina
Se um trabalho for cancelado, ele não poderá mais ser usado como trabalho pai

Job possui vários estados, podemos usar Job.isActive para determinar se a atual rotina atual ainda está no estado Ativo.
Insira a descrição da imagem aqui
Se a corotina pai for especificada usando SupervisorJob, qualquer exceção a qualquer corotina filha não afetará a execução de outras corotinas filhas. Conforme mostrado no código a seguir:

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 construtor Coroutine

Iniciar e assíncrono são as duas corotinas internas, usadas para executar o corpo da corotina de forma síncrona e assíncrona.
Duas funções de suspensão são executadas de forma síncrona no seguinte 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
}

A saída do log é a seguinte:

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: The sum is 3
2019-12-09 00: 00: 36.555 D / demo: concluída em 2008 ms

O código a seguir usa async para executar de forma assíncrona duas funções de suspensão:

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
}

A saída do log é a seguinte:

2019/12/08 23: 52: 01,714 D / demonstração: fetchDataFromServerOne ()
2019/12/08 23: 52: 01,718 D / demonstração: fetchDataFromServerTwo ()
2019/12/08 23: 52: 02,722 D / demonstração: A soma is 3
2019-12-08 23: 52: 02.722 D / demo: concluída em 1133 ms

Além dos dois construtores acima, também há um liveData para uso no ViewModel, consulte Corotinas do Kotlin e o resumo do uso no Android (dois usados ​​com os componentes da arquitetura Jetpack) .

Referência:
coroutines KOTLIN em Android - Basics
documento oficial website co-rotina: coroutine Basics
site oficial coroutine Codelabs: os Usando coroutines KOTLIN em seu aplicativo Android

Publicado 82 artigos originais · Gosto 86 · Visite mais de 110.000

Acho que você gosta

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