Kotlin coroutines no Android
Um coroutine é um padrão de design concomitante que você pode usar no Android para simplificar o código que executa assincronamente.Coroutines foram adicionados ao Kotlin na versão 1.3 e são baseados em conceitos estabelecidos de outras linguagens.
No Android, coroutines ajudam a gerenciar tarefas de longa duração que bloqueiam a linha principal e fazem com que seu aplicativo não responda.Mais de 50% dos desenvolvedores profissionais que usam coroutines relataram ter visto um aumento na produtividade. Este tópico descreve como você pode usar o Kotlin coroutines para resolver esses problemas, permitindo que você escreva um código de aplicação mais limpo e conciso.
Faatures
Coroutines é nossa solução recomendada para programação assíncrona no Android. Entre as funcionalidades notáveis estão as seguintes:
- Leve: Você pode executar muitos coroutinos em um único fio devido ao suporte à suspensão, que não bloqueia o fio onde o coroutine está rodando. Suspensões economizam memória sobre bloqueio enquanto suporta muitas operações simultâneas.
- Menos vazamentos de memória: Utiliza simultaneidade estruturada para executar operações dentro de um escopo.
- Suporte de cancelamento embutido:O cancelamento é propagado automaticamente através da hierarquia de coroutina em execução.
- Integração Jetpack: Muitas bibliotecas Jetpack incluem extensões que fornecem suporte completo a coroutinas. Somelibraries também fornecem seu próprio escopocoroutine que você pode usar para concorrência estruturada.
Síntese de exemplos
Baseado no Guia para arquitetura do aplicativo, os exemplos neste tópico fazem uma requisição de rede e retornam o resultado para o tópico principal, onde o aplicativo pode então exibir o resultado para o usuário.
Especificamente, o componente ViewModel
Arquitetura chama a camada de repositório no tópico principal totrigger a requisição de rede. Este guia itera através de várias soluções que utilizam coroutinas mantêm a thread principal desbloqueada.
ViewModel
inclui um conjunto de extensões KTX que funcionam diretamente com coroutinas. Estas extensões sãolifecycle-viewmodel-ktx
biblioteca e são usadas neste guia.
Informação de dependência
Para usar coroutinos no seu projeto Android, adicione a seguinte dependência ao seu aplicativo build.gradle
arquivo:
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'}
Executar em um thread de fundo
Fazer uma solicitação de rede no thread principal faz com que ele espere, ou bloqueie, até que receba uma resposta. Como a thread está bloqueada, a tabela do SO é chamada para onDraw()
, o que faz com que a sua aplicação congele e potencialmente leva a um diálogo de Não Resposta de Aplicação (ANR). Para uma melhor experiência de uso, vamos executar esta operação em uma thread de fundo.
Primeiro, vamos dar uma olhada na nossa classe Repository
e ver como ela está fazendo a requisição de rede:
sealed class Result<out R> { data class Success<out T>(val data: T) : Result<T>() data class Error(val exception: Exception) : Result<Nothing>()}class LoginRepository(private val responseParser: LoginResponseParser) { private const val loginUrl = "https://example.com/login" // Function that makes the network request, blocking the current thread fun makeLoginRequest( jsonBody: String ): Result<LoginResponse> { val url = URL(loginUrl) (url.openConnection() as? HttpURLConnection)?.run { requestMethod = "POST" setRequestProperty("Content-Type", "application/json; utf-8") setRequestProperty("Accept", "application/json") doOutput = true outputStream.write(jsonBody.toByteArray()) return Result.Success(responseParser.parse(inputStream)) } return Result.Error(Exception("Cannot open HttpURLConnection")) }}
makeLoginRequest
é síncrona e bloqueia a thread de chamada. Para modelar a resposta da requisição de rede, temos a nossa própria Result
class.
O ViewModel
aciona a requisição de rede quando o usuário clica, forexample, em um botão:
class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun login(username: String, token: String) { val jsonBody = "{ username: \"$username\", token: \"$token\"}" loginRepository.makeLoginRequest(jsonBody) }}
Com o código anterior, LoginViewModel
está bloqueando a thread UI ao fazer a requisição de rede. A solução mais simples para mover a execução do thread principal é criar um novo coroutine e executar a requisição de rede em um thread de E/S:
class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun login(username: String, token: String) { // Create a new coroutine to move the execution off the UI thread viewModelScope.launch(Dispatchers.IO) { val jsonBody = "{ username: \"$username\", token: \"$token\"}" loginRepository.makeLoginRequest(jsonBody) } }}
Vamos dissecar o código dos coroutines na função login
:
-
viewModelScope
é um pré-definidoCoroutineScope
que está incluído com as extensõesViewModel
KTX. Note que todos os coroutinos devem ser executados no ascope. ACoroutineScope
gere um ou mais coroutinos relacionados. -
launch
é uma função que cria um coroutine e envia a execução do seu corpo de funções para o despachante correspondente. -
Dispatchers.IO
indica que este coroutine deve ser executado em um tópico reservado para operações de E/S.
A função login
é executada da seguinte forma:
- A aplicação chama a função
login
a partir da camadaView
na thread principal. -
launch
cria um novo coroutine, e a requisição de rede é feita independentemente de uma thread reservada para operações de E/S. - Enquanto o coroutine está em execução, a função
login
continua a execução e retorna, possivelmente antes da requisição de rede ser finalizada. Note que para simplificar, a resposta da rede é ignorada por enquanto.
Desde que este coroutine seja iniciado com viewModelScope
, ele é executado no escopo do ViewModel
. Se o ViewModel
for destruído porque o usuário está navegando longe da tela, viewModelScope
é automaticamente cancelado, e todos os coroutinos em execução também são cancelados.
Um problema com o exemplo anterior é que qualquer coisa chamando makeLoginRequest
precisa lembrar explicitamente de mover a execução para fora do tópico principal. Vamos ver como podemos modificar o Repository
para resolver este problema para nós.
Utilizar coroutinos para segurança principal
Consideramos uma função de segurança principal quando ela não bloqueia atualizações de IU na thread principal. A função makeLoginRequest
não é de segurança principal, pois a chamadamakeLoginRequest
a partir do fio principal bloqueia a IU. Use a funçãowithContext()
da biblioteca de corotinas para mover a execução de uma corotina para uma thread diferente:
class LoginRepository(...) { ... suspend fun makeLoginRequest( jsonBody: String ): Result<LoginResponse> { // Move the execution of the coroutine to the I/O dispatcher return withContext(Dispatchers.IO) { // Blocking network request code } }}
>withContext(Dispatchers.IO)
move a execução da corotina para uma thread de I/O, tornando a nossa função de chamada principal-safe e habilitando a data de toque da IU conforme necessário.
makeLoginRequest
também é marcada com a palavra-chave suspend
. Esta palavra-chave é a forma do Kotlin de impor uma função a ser chamada de dentro de um coroutine.
No exemplo a seguir, o coroutine é criado em LoginViewModel
.Como makeLoginRequest
move a execução para fora do fio principal, o coroutine na função login
pode agora ser executado no fio principal:
class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun login(username: String, token: String) { // Create a new coroutine on the UI thread viewModelScope.launch { val jsonBody = "{ username: \"$username\", token: \"$token\"}" // Make the network call and suspend execution until it finishes val result = loginRepository.makeLoginRequest(jsonBody) // Display result of the network request to the user when (result) { is Result.Success<LoginResponse> -> // Happy path else -> // Show error in UI } } }}
Nota que o coroutine ainda é necessário aqui, já que makeLoginRequest
isa suspend
função, e todas as funções suspend
devem ser executadas em coroutine.
Este código difere do anterior login
exemplo de algumas maneiras:
-
launch
não aceita um parâmetroDispatchers.IO
. Quando não se ultrapassa umDispatcher
alaunch
, qualquer coroutino lançado deviewModelScope
corre na linha principal. - O resultado do pedido de rede é agora tratado para mostrar a IU de falha do sucessor.
A função de login agora é executada da seguinte forma:
- A aplicação chama a função
login()
da camadaView
na thread principal. -
launch
cria um novo coroutine para fazer a requisição de rede na thread principal, e o coroutine inicia a execução. - Com o coroutine, a chamada para
loginRepository.makeLoginRequest()
suspende a execução do coroutine até que owithContext
block emmakeLoginRequest()
termine a execução. - Após o
withContext
bloco terminar, o coroutine emlogin()
retoma a execução na thread principal com o resultado da requisição de rede.
Exceções de manuseio
Para lidar com exceções que a camada Repository
pode lançar, use o suporte Kotlin’sbuilt-in para exceções. No exemplo a seguir, usamos um bloco:try-catch
class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun makeLoginRequest(username: String, token: String) { viewModelScope.launch { val jsonBody = "{ username: \"$username\", token: \"$token\"}" val result = try { loginRepository.makeLoginRequest(jsonBody) } catch(e: Exception) { Result.Error(Exception("Network request failed")) } when (result) { is Result.Success<LoginResponse> -> // Happy path else -> // Show error in UI } } }}
Neste exemplo, qualquer exceção inesperada lançada pela camada makeLoginRequest()
chamada é tratada como um erro na UI.
Leave a Reply