Primer de multithreading e callbacks

O curso Como desenvolver apps Android no Kotlin pressupõe que você conheça o conceito e a terminologia do uso de várias linhas de execução. Esta página é uma introdução e um resumo de alto nível.

Dispositivos móveis têm processadores e, hoje em dia, a maioria tem vários processadores que são executados simultaneamente. Isso é chamado de multiprocessamento.

Para usar processadores com mais eficiência, o sistema operacional pode permitir que um aplicativo crie mais de uma linha de execução em um processo. Isso é chamado de uso de várias linhas de execução.

imagem

É como ler vários livros ao mesmo tempo, trocando de livro após cada capítulo e, com o tempo, terminando todas as leituras, mas não é possível ler mais de um livro simultaneamente.

Para gerenciar todos essas linhas de execução, é preciso uma infraestrutura.

imagem

O programador considera fatores como as prioridades e garante que todas as linhas de execução sejam executadas e concluídas. Nenhum livro pode ficar na prateleira para sempre juntando poeira, mas, quando o livro é muito longo ou não é urgente, ele pode levar um tempo até chegar a você.

O agente configura linhas de execução, ou seja, envia os livros que você precisa ler e especifica um contexto em que isso pode acontecer. O contexto é como uma sala de leitura isolada e especializada. Alguns contextos são melhores para operações na interface do usuário, enquanto outros são especializados para processar operações de entrada/saída.

Outra coisa que você precisa saber é que um aplicativo voltado ao usuário geralmente tem uma linha de execução principal que é executada em primeiro plano e pode enviar outras linhas de execução que podem ser executadas em segundo plano.

No Android, a linha de execução principal é uma única linha de execução que processa todas as atualizações na IU. Além disso, essa linha de execução principal, também chamada de linha de execução de IU, é a linha de execução que chama todos os gerenciadores de clique e outros callbacks da IU e do ciclo de vida. A linha de execução de IU é a padrão. A menos que seu app mude explicitamente de linha de execução ou use uma classe executada em outra linha de execução, tudo o que ele fizer estará na linha de execução principal.

Isso cria um possível desafio. A linha de execução de IU precisa ser executada sem problemas para garantir uma ótima experiência de usuário. Para que o app seja exibido sem pausas visíveis, a linha de execução principal precisa atualizar a tela a cada 16 ms ou mais, ou então a cerca de 60 frames por segundo. Nessa velocidade, seres humanos não percebem a mudança de frames. São muitos em pouco tempo. Portanto, no Android é fundamental evitar o bloqueio da linha de execução de IU. Bloqueio nesse contexto significa que a linha de execução de IU fica parada enquanto aguarda a conclusão de algo como a atualização de um banco de dados.

imagem

Muitas tarefas comuns levam mais de 16 milissegundos, como buscar dados da Internet, ler um arquivo grande ou programar dados em um banco de dados. Portanto, chamar código para realizar tarefas como essas da linha de execução principal pode fazer com que o app seja interrompido, atrasar a renderização ou até causar travamentos. Além disso, se você bloquear a linha de execução principal por muito tempo, poderá até ocorrer um erro, que será acompanhado de uma caixa de diálogo "O app não está respondendo" (ANR, na sigla em inglês).

Callbacks

Você tem várias opções para usar a linha de execução principal.

Um padrão para realizar tarefas de longa duração sem bloquear a linha de execução principal é o uso de callbacks. Com callbacks, você pode iniciar tarefas de longa duração em uma linha de execução em segundo plano. Quando a tarefa é concluída, o callback, fornecido como argumento, é chamado para informar ao código o resultado na linha de execução principal.

Os callbacks são um ótimo padrão, mas eles têm algumas desvantagens. Códigos que usam muitos callbacks podem ser difíceis de ler e mais difíceis de entender. Por mais que o código pareça sequencial, o código do callback será executado em algum momento assíncrono no futuro. Além disso, callbacks não permitem o uso de alguns recursos de linguagem, como exceções.

Corrotinas

No Kotlin, as corrotinas são a solução para processar tarefas de longa duração de maneira elegante e eficaz. As corrotinas do Kotlin permitem converter códigos baseados em callback em códigos sequenciais. Geralmente, é mais fácil ler um código programado de maneira sequencial. Além disso, ele também pode usar recursos de linguagem, como exceções. No fim, corrotinas e callbacks fazem o mesmo trabalho: esperam até que um resultado esteja disponível em uma tarefa de longa duração e continuam a execução.