Web Site Landing pages a partir de 100.000,00 KZ.

Padrões de corrotina no Android e por que funcionam

Um organograma

Conheço muitos desenvolvedores Android que aprendem corrotinas por meio de padrões de código, e isso geralmente é suficiente para sobreviver. Mas fazer isso perde a beleza por trás deles – e o fato de que, no fundo, é tudo muito simples, na verdade. Então, o que faz esses padrões funcionarem?

Pegue seu kit de ferramentas, vamos abrir alguns padrões de corrotinas comuns que você provavelmente já viu centenas de vezes e maravilhe-se com o funcionamento do relógio.

E claro, se você é novo nisso tudo, seja bem-vindo! Aqui está um bom conjunto de padrões que realmente vale a pena aprender como desenvolvedor Android.

Padrão 1: A função de suspensão

Veja como você faz torradas. Você já deve saber disso:

  1. Coloque pão na torradeira
  2. Espere
  3. Pegue o pão – agora torrada – da torradeira

Se você revisar suas ações ao longo desse processo, verá que, na maioria das vezes, estará apenas por aí, esperando que o pão se transforme em torrada. Apenas uma pequena proporção do tempo você está realmente ativo.

Então, o que você faz enquanto espera? Bem, o que você quiser. Você pode marcar outro item da sua lista de tarefas. Contanto que você volte a tempo de cuidar do pão recém-torrado quando estiver pronto, você estará bem.

E é isso que uma função de suspensão faz. Durante o atraso, diz-se que sua corrotina está suspensa , o que sinaliza para a biblioteca de corrotinas (especificamente o expedidor ) que ela pode fazer outra coisa.

Então — e aqui está a parte principal — quando você chama essa função de suspensão , o thread subjacente não é bloqueado . A biblioteca de corrotinas usa o atraso de forma eficiente e o thread é colocado para funcionar.

É claro que, para o código que chama a makeToast()função acima, nenhum desses detalhes importa. Você chama makeToast()e a função retorna um pouco mais tarde, quando o brinde estiver pronto. Se ele sentou e esperou pelo brinde, ou fez outras tarefas, é irrelevante.

Padrão 2: Chamando uma função de suspensão do thread principal

É por isso que geralmente é seguro chamar uma função de suspensão do thread principal/UI. Dado que não bloqueia o thread de chamada, o thread de chamada está livre para continuar fazendo as coisas da interface do usuário.

Aqui está um exemplo desse padrão. Ao clicar em um botão, revelamos um número PIN por 10 segundos e depois o ocultamos novamente:

Isso é perfeitamente seguro porque não bloqueia o thread da UI. A IU continuará respondendo durante o atraso de 10 segundos.

Padrão 3: Troca de contextos

Muitas funções suspensas passam a maior parte do tempo suspensas. Um bom exemplo é obter dados da Internet: é preciso pouco esforço para configurar uma conexão, mas aguardar o download dos dados leva a maior parte do tempo.

Então, é seguro executar tarefas de rede suspensas no thread da UI? Não! De jeito nenhum.

O thread de chamada só é desbloqueado durante o tempo em que a tarefa suspensa está realmente suspensa (ou seja, aguardando) .

As tarefas de rede envolvem todos os tipos de trabalho fora da espera: configuração, criptografia, análise de respostas, etc. Elas podem levar apenas milissegundos – mas são milissegundos de tempo em que o thread da UI é bloqueado.

Por motivos de desempenho, você precisa que seu thread de IU atualize a IU constantemente. Não o interrompa ou o desempenho do seu aplicativo será prejudicado.

Então é por isso que temos o padrão de “troca de contexto”:

withContext acima garante que esta função de suspensão seja executada em um pool de threads de E/S. Com isso implementado, a saveNotefunção pode ser chamada com segurança a partir do thread da UI.

Como regra geral: certifique-se de que suas funções de suspensão alternem os contextos quando necessário, para que possam ser chamadas a partir do thread da UI.

Padrão 4: executando corrotinas em um escopo

Isso não é tanto um padrão, já que todas as corrotinas precisam de um contexto para serem executadas.

Mas veja o padrão de exemplo abaixo. O que um código como esse realmente significa?

Vamos começar com esta visão simplificada: O escopo de uma corrotina representa a extensão do seu tempo de vida . (Na verdade, há um pouco mais do que isso, e escreverei mais sobre esse assunto em um artigo futuro, mas este é um bom ponto de partida).

Então, ao dizer, viewModelScope.launchvocê está dizendo: lance uma corrotina cujo tempo de vida é limitado pelo viewModelScope.

Portanto, “viewModelScope” aqui é como um balde que contém corrotinas para o View Model, incluindo o acima. Quando o bucket for esvaziado — ou seja, quando viewModelScope for cancelado — seu conteúdo também será cancelado. Na prática, isso significa que você pode escrever código sem se preocupar quando ele precisar ser encerrado.

Padrão 5: múltiplas operações em uma função de suspensão

Nos deparamos viewModelScopeacima. Existem muitos outros, por exemplo:

  • rememberCoroutineScope()no Compose, que fornece um escopo que dura enquanto @Composable estiver na tela. (O padrão 1 acima tem um exemplo disso)
  • viewLifecycleOwner.lifecycleScopenas visualizações do Android, que dura tanto quanto a atividade/fragmento
  • GlobalScope, que dura para sempre (e geralmente é, mas nem sempre, A Bad Idea™)

Agora, por que você iria querer fazer isso? Bem, coroutineScopeé uma função especial que cria um novo escopo de corrotina e suspende até que qualquer/todas as corrotinas filhas sejam concluídas.

Portanto, o padrão acima significa “fazer essas coisas em paralelo e retornar quando tudo estiver pronto”.

Isso é útil em classes de repositório que possuem fontes de dados locais e remotas, por exemplo, porque muitas vezes você deseja fazer algo em ambas as fontes de dados ao mesmo tempo. A operação só é considerada concluída quando ambas as ações são concluídas.

Padrão 6: Loops infinitos (aparentemente)

Agora que entendemos os escopos da corrotina, podemos ver por que um padrão como este realmente funciona:

while(true) – que teria sido uma enorme bandeira vermelha há 5 anos – é na verdade perfeitamente seguro aqui. Assim que o viewModelScope for cancelado, a corrotina iniciada será cancelada e o loop ‘infinito’ será interrompido.

Mas a razão pela qual isso para é bastante interessante…

A chamada para delay() gera o thread para o despachante da corrotina. Isso significa que permite que o despachante da rotina verifique se algo mais precisa ser feito e pode fazê-lo.

Mas também significa que o despachante da corrotina verifica se a corrotina foi cancelada e, em caso afirmativo, lança uma CancellationException. Você não precisa lidar com essa exceção, mas o resultado é que a pilha se desenrola e é while(true)descartada.

Antipadrão 1: uma função de suspensão que não suspende

Ceder lugar ao despachante de rotina é, portanto, essencial. É perfeitamente seguro usar bibliotecas como Room, Retrofit e Coil, porque elas passam para o despachante quando necessário.

Isso leva um tempo considerável para ser executado. E uma vez iniciado, não pode ser interrompido.

Uma versão segura para corrotina acima usaria a yield()função. yield()é um pouco como executar delay()sem o atraso real: ele cede ao despachante e receberá uma CancellationException se precisar parar.

Então vamos lá. Seis padrões usando corrotinas e um antipadrão — e o mais importante, por que funcionam e o que está por trás deles.

Em um blog futuro irei me aprofundar, por exemplo, na diferença entre escopo e contexto da corrotina, o que é um Job e o que acontece quando você usa o lançamento. Por enquanto, porém, faça todas as perguntas abaixo!