Android AsyncTask para operações de longa duração

90

Citando a documentação para AsyncTask encontrada aqui , diz:

O AsyncTasks deve ser usado idealmente para operações curtas (alguns segundos no máximo). Se você precisa manter os threads em execução por longos períodos, é altamente recomendável usar as várias APIs fornecidas pelo pacote java.util.concurrent, como Executor, ThreadPoolExecutor e FutureTask.

Agora surge a minha pergunta: por quê? A doInBackground()função é executada fora do thread da interface do usuário, então que mal há em ter uma operação longa aqui?

user1730789
fonte
2
O problema que enfrentei ao implantar um aplicativo (que usa asyncTask) em um dispositivo real foi que a doInBackgroundfunção de longa duração congela a tela se uma barra de progresso não for usada.
venkatKA
2
Porque uma AsyncTask está vinculada à Activity em que é iniciada. Portanto, se a Activity for eliminada, sua instância AsyncTask também poderá ser eliminada.
IgorGanapolsky
E se eu criar a AsyncTask dentro de um serviço? Isso não resolveria o problema?
redemoinho
1
Use IntentService, solução perfeita para executar longas operações em segundo plano.
Keyur Thumar

Respostas:

120

É uma pergunta muito boa, um programador Android leva tempo para entender completamente o problema. Na verdade, AsyncTask tem dois problemas principais relacionados:

  • Eles estão mal vinculados ao ciclo de vida da atividade
  • Eles criam vazamentos de memória com muita facilidade.

Dentro do aplicativo RoboSpice Motivations ( disponível no Google Play ), respondemos a essa pergunta em detalhes. Ele dará uma visão aprofundada de AsyncTasks, Loaders, seus recursos e desvantagens e também apresentará uma solução alternativa para solicitações de rede: RoboSpice. As solicitações de rede são um requisito comum no Android e são, por natureza, operações de longa duração. Aqui está um trecho do aplicativo:

O ciclo de vida AsyncTask e Activity

AsyncTasks não seguem o ciclo de vida das instâncias de Activity. Se você iniciar uma AsyncTask dentro de uma Activity e girar o dispositivo, a Activity será destruída e uma nova instância será criada. Mas o AsyncTask não morrerá. Ele continuará vivendo até que seja concluído.

E quando for concluído, o AsyncTask não atualizará a IU da nova Activity. Na verdade, ele atualiza a instância anterior da atividade que não é mais exibida. Isso pode levar a uma Exceção do tipo java.lang.IllegalArgumentException: Visualização não anexada ao gerenciador de janelas se você usar, por exemplo, findViewById para recuperar uma visualização dentro da Atividade.

Problema de vazamento de memória

É muito conveniente criar AsyncTasks como classes internas de suas atividades. Como o AsyncTask precisará manipular as visualizações da Activity quando a tarefa for concluída ou em andamento, o uso de uma classe interna da Activity parece conveniente: as classes internas podem acessar diretamente qualquer campo da classe externa.

No entanto, isso significa que a classe interna manterá uma referência invisível em sua instância de classe externa: a Activity.

No longo prazo, isso produz um vazamento de memória: se a AsyncTask durar muito, ela manterá a atividade "viva", enquanto o Android gostaria de eliminá-la, pois não pode mais ser exibida. A atividade não pode ser coletada como lixo e esse é um mecanismo central para o Android preservar os recursos do dispositivo.


É realmente uma péssima ideia usar AsyncTasks para operações de longa duração. No entanto, eles são bons para aqueles de curta duração, como atualizar uma Visualização após 1 ou 2 segundos.

Eu encorajo você a baixar o aplicativo RoboSpice Motivations , ele realmente explica isso em profundidade e fornece exemplos e demonstrações das diferentes maneiras de fazer algumas operações em segundo plano.

Snicolas
fonte
@Snicolas Hi. Eu tenho um aplicativo onde ele verifica os dados de uma tag NFC e os envia para o servidor. Ele funciona bem em áreas de bom sinal, mas onde não há sinal, a AsyncTask que faz o webcall continua funcionando. por exemplo, a caixa de diálogo de progresso funciona por minutos e, quando desaparece, a tela fica preta e não responde. My AsyncTask é uma classe interna. Estou escrevendo um Handler para cancelar a tarefa após X segundos. O aplicativo parece enviar dados antigos ao servidor horas após a verificação. Será que isso pode ser devido ao AsyncTask não terminar e talvez terminar horas depois? Eu apreciaria qualquer insight. obrigado
turtleboy
Rastreie para ver o que acontece. Mas sim, isso é perfeitamente possível! Se você desin bem sua asynctarefa, você pode cancelá-la apropriadamente, isso seria um bom ponto de partida se você não quiser migrar para o RS ou um serviço ...
Snicolas
@Snicolas Obrigado pela resposta. Eu fiz um post no SO ontem descrevendo meu problema e mostrando o código do manipulador que escrevi para tentar interromper a AsyncTask após 8 segundos. Você se importaria de dar uma olhada, se tiver tempo? Chamar AsyncTask.cancel (true) de um manipulador cancelará a tarefa corretamente? Eu sei que devo verificar periodicamente o valor de iscancelled () no meu doInBackgroud, mas não acho que se aplica às minhas circunstâncias, pois estou apenas fazendo um HttpPost de uma linha webcall e não publicando atualizações na UI. Existem alternativas para AsyncTask por exemplo, é possível fazer um HttPost de um IntentService
turtleboy
aqui está o link stackoverflow.com/questions/17725767/…
turtleboy
Você deve tentar no RoboSpice (no github). ;)
Snicolas
38

porque ?

Porque AsyncTask, por padrão, usa um pool de threads que você não criou . Nunca amarre recursos de um pool que você não criou, pois você não sabe quais são os requisitos desse pool. E nunca bloqueie recursos de um pool que você não criou, se a documentação desse pool disser que não, como é o caso aqui.

Em particular, começando com o Android 3.2, o pool de threads usado por AsyncTaskpadrão (para aplicativos com android:targetSdkVersiondefinido como 13 ou superior) tem apenas um thread nele - se você amarrar este thread indefinidamente, nenhuma de suas outras tarefas será executada.

CommonsWare
fonte
Obrigado por esta explicação. Não consegui encontrar nada realmente defeituoso sobre o uso de AsyncTasks para operações de longa duração (embora eu geralmente delegue isso aos Serviços), mas seu argumento sobre amarrar aquele ThreadPool (para o qual você pode de fato não faça suposições sobre seu tamanho) parece certeiro. Como um suplemento à postagem de Anup sobre o uso de um serviço: Esse serviço também deve executar a tarefa em um thread de segundo plano, e não bloquear seu próprio thread principal. Uma opção poderia ser um IntentService ou, para requisitos de simultaneidade mais complexos, aplique sua própria estratégia de multithreading.
baske em
É o mesmo se eu iniciar o AsyncTask dentro de um serviço ?? Quer dizer, a operação de longa duração ainda seria um problema?
redemoinho
1
@eddy: Sim, já que isso não muda a natureza do pool de threads. Para a Service, basta usar a Threadou a ThreadPoolExecutor.
CommonsWare
Obrigado @CommonsWare, apenas a última pergunta, TimerTasks compartilha as mesmas desvantagens de AsyncTasks? ou é uma coisa totalmente diferente?
redemoinho
1
@eddy: TimerTaské do Java padrão, não do Android. TimerTaskfoi amplamente abandonado em favor de ScheduledExecutorService(o que, apesar do nome, faz parte do Java padrão). Nenhum deles está vinculado ao Android e, portanto, você ainda precisa de um serviço se espera que esses itens sejam executados em segundo plano. E, você realmente deve considerar AlarmManager, a partir do Android, então você não precisa de um serviço rondando, apenas assistindo o tique-taque do relógio.
CommonsWare
4

As tarefas Aysnc são threads especializados que ainda devem ser usados ​​com a GUI de seus aplicativos, mas mantendo as tarefas pesadas de recursos do thread da interface do usuário. Então, quando coisas como atualizar listas, alterar suas visualizações etc. exigem que você faça algumas operações de busca ou atualização, você deve usar tarefas assíncronas para que possa manter essas operações fora do thread da IU, mas observe que essas operações ainda estão conectadas à IU de alguma forma .

Para tarefas de execução mais longa, que não requerem atualização da IU, você pode usar os serviços, porque eles podem viver mesmo sem uma IU.

Portanto, para tarefas curtas, use tarefas assíncronas porque elas podem ser eliminadas pelo sistema operacional depois que sua atividade de desova morrer (geralmente não morrerá no meio da operação, mas concluirá sua tarefa). E para tarefas longas e repetitivas, use os serviços.

para mais informações, veja os tópicos:

AsyncTask por mais de alguns segundos?

e

AsyncTask não para mesmo quando a atividade é destruída

Anup Cowkur
fonte
1

O problema com AsyncTask é que, se for definida como uma classe interna não estática da atividade, terá uma referência à atividade. No cenário em que a atividade do contêiner da tarefa assíncrona termina, mas o trabalho em segundo plano em AsyncTask continua, o objeto de atividade não será coletado como lixo porque há uma referência a ele, isso causa o vazamento de memória.

A solução para corrigir isso é definir a tarefa assíncrona como uma classe interna estática de atividade e usar uma referência fraca ao contexto.

Mesmo assim, é uma boa ideia usá-lo para tarefas simples e rápidas em segundo plano. Para desenvolver um aplicativo com código limpo, é melhor usar RxJava para executar tarefas complexas em segundo plano e atualizar a IU com os resultados dela.

Arnav Rao
fonte