É 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:
- 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;
- 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;
- Activity.runOnUiThread e View.post: São métodos que permitem que qualquer thread adicione um Runnable a ser executado na thread a UI.
- 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:
- onPreExecute(): É executado na thread da UI, geralmente para mostrar ao usuário que algo está sendo carregado, geralmente com um ProgressDialog;
- 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;
- 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;
- 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;
- 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:
Muito útil. Parabéns pelo post.
Explicações simples. Parabéns! Como ressalva: seria interessante ter um pequeno exemplo pronto para que a absorção do conteúdo fosse plena.
Excelente post, tirou minhas dúvidas sobre AnsyncTask! Realmente faltou um exemplo completo para que ficasse ainda melhor o post. Parabéns ao autor.
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?
Ótimo tutorial, valeu!
Postar um comentário