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