terça-feira, 22 de maio de 2012

Lidando com tarefas pesadas com AsyncTask

É comum, em aplicativos um pouco mais complexo, que seja necessário realizar alguma tarefa "pesada", ou seja, que demore um tempo considerável até terminar a execução. Pode ser uma requisição web, um calculo demorado ou configuração pesada no banco de dados.

Elas requerem uma atenção especial pois não é nada recomendável que as executem como parte do processo principal pois isso impede que o estado da aplicação mude, ou seja, não será possível atualizar a tela ou receber nenhum comando de entrada, a aplicação estará travada durante o processo, e no caso do Android, se isso demorar muito (mais que 5 segundos), será exibido um aviso dizendo que o aplicativo travou e o usuário poderá mata-lo.

Fora que isso oferece uma péssima experiencia pro usuário. O que se deve fazer é utilizar uma outra thread para o processamento pesado, deixando assim a thread principal responsável apenas pela operações básica e E/S.


Quem entende de multiprogramação já deve estar imaginando a solução. Mas no Android tem-se um pequeno detalhe: apenas a thread que iniciou a UI (user interface) pode altera-la, ou seja, não é possível criar uma nova thread de maneira crua e fazer com que ela atualize algo na tela. Então o Android fornece algumas maneira de se fazer esse serviço:


  1. Service: Executa uma tarefa que não necessariamente está ligada ao que a aplicação está apresentando pro usuário. Mais indicado para execuções de tarefas continuas. Não permite modificação da UI diretamente;
  2. Handler: Permite a execução de tarefas agendadas (com timer) e fornece um meio de outras threads se comunicarem com a thread principal através do envio de mensagens;
  3. Activity.runOnUiThread e  View.post: São métodos que permitem que qualquer thread adicione um Runnable a ser executado na thread a UI.
  4. AsyncTask: Encapsula todo o processo pesado, e fornece métodos de modificar a UI antes, durante e depois da execução
Assim, o mais simples, poderoso e seguro jeito de se implementar, na minha visão, é utilizando o AsyncTask. Para utiliza-lo você deve estende-lo declarando-o com 3 tipos genéricos:

class DownloadFilesTask extends AsyncTask<Parametro, Progresso, Resultado>

Ou seja, você pode definir qual objeto deseja passar como parâmetro, para execução da tarefa, qual objeto deseja receber como resultado do progresso parcial (normalmente um inteiro que indica a porcentagem concluída) e qual objeto deseja receber como resultado da operação. Se for uma tarefa muito especifica você pode definir esses valores(ou algum deles) como Void, então não precisará se preocupar com ele.

É fornecido os seguintes métodos que você pode subscrever para implementar sua tarefa:

  1. onPreExecute(): É executado na thread da UI, geralmente para mostrar ao usuário que algo está sendo carregado, geralmente com um  ProgressDialog;
  2. doInBackground(Parametro... params): Esse é o método que você terá que subscrever e onde ficará todo seu processamento pesado a ser executado em uma thread distinta. É recebido como parâmetro um array do tipo Parametro (se sempre usar apenas um objeto, basta utilizar params[0]). Dentro desse método é possível chamar  publishProgress(Progresso... values) para atualizar o progresso. Deve retornar um objeto Resultado;
  3. onProgressUpdate(Progresso... values): Executada sempre que publishProgress é chamada. Também é executada na thread da UI, e é usada para atualizar a UI com informações sobre o progresso recebidas por parâmetro;
  4. onPostExecute(Resultado result): Executado na UI thread assim que o doInBackground termina a execução. Aqui deve-se dispensar o ProgressDialog (se houver algum), e atualizar a tela com as informações processadas e recebidas pelo parâmetro, se for o caso;
  5. onCancelled(Result result): Executado na UI thread caso a tarefa tenha sido cancelada pela chamada de  cancel(boolean mayInterruptIfRunning).
Para executar bastar chamar o método execute(Params... params):

 new DownloadFilesTask().execute(url1, url2, url3);

A seguir um exemplo:

 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

É recebido como parâmetros várias urls que terão seus conteúdos baixados, enquanto isso é setado o progresso, e quando terminado é exibido o número de bytes baixado. Claro que esse exemplo é apenas uma abstração, há classes e métodos que precisam ser criados para funcionar.

É isso! Aí está uma forma simples e fácil de ser implementada e muito útil para muitos aplicativos.

6 comentários:

웃 ڳÞẵώἤ at 30 de junho de 2012 às 23:14 disse...
Este comentário foi removido pelo autor.
Victor Beckman at 7 de março de 2013 às 19:51 disse...

Muito útil. Parabéns pelo post.

Anselmo MS at 24 de outubro de 2013 às 07:36 disse...

Explicações simples. Parabéns! Como ressalva: seria interessante ter um pequeno exemplo pronto para que a absorção do conteúdo fosse plena.

Anônimo at 31 de julho de 2014 às 09:34 disse...

Excelente post, tirou minhas dúvidas sobre AnsyncTask! Realmente faltou um exemplo completo para que ficasse ainda melhor o post. Parabéns ao autor.

Unknown at 29 de setembro de 2014 às 10:27 disse...

Uma duvida, supondo que um processo que não esteja na UI Thread chame uma AsyncTask que por sua vez fique um certo tempo processando. Existe o perigo de quem executou a AsincTasc não existir mais por uma coleta do GC?

Unknown at 5 de agosto de 2015 às 18:40 disse...

Ótimo tutorial, valeu!

Postar um comentário

 
© 2011 Tutoriandroid | Recode by Ardhiansyam | Based on Android Developers Blog