Eu tenho investigado esse problema há meses, encontrei soluções diferentes para ele, das quais não estou feliz, pois todos são hacks maciços. Ainda não consigo acreditar que uma classe que falhou no design entrou na estrutura e ninguém está falando sobre isso, então acho que devo estar perdendo alguma coisa.
O problema está com AsyncTask
. De acordo com a documentação
"permite executar operações em segundo plano e publicar resultados no thread da interface do usuário sem ter que manipular threads e / ou manipuladores."
O exemplo continua a mostrar como showDialog()
é chamado um método exemplar onPostExecute()
. Isso, no entanto, parece inteiramente artificial para mim, porque mostrar uma caixa de diálogo sempre precisa de uma referência a um válido Context
, e um AsyncTask nunca deve ter uma referência forte a um objeto de contexto .
O motivo é óbvio: e se a atividade for destruída, o que desencadeou a tarefa? Isso pode acontecer o tempo todo, por exemplo, porque você virou a tela. Se a tarefa contiver uma referência ao contexto que a criou, você não estará apenas segurando um objeto de contexto inútil (a janela será destruída e qualquer interação da interface do usuário falhará com uma exceção!), Você corre o risco de criar um vazamento de memória.
A menos que minha lógica seja falha aqui, isso se traduz em: onPostExecute()
é totalmente inútil, porque de que serve esse método para executar no thread da interface do usuário se você não tiver acesso a nenhum contexto? Você não pode fazer nada significativo aqui.
Uma solução alternativa seria não passar instâncias de contexto para um AsyncTask, mas uma Handler
instância. Isso funciona: como um manipulador vincula livremente o contexto e a tarefa, você pode trocar mensagens entre eles sem arriscar um vazamento (certo?). Mas isso significaria que a premissa do AsyncTask, a saber, que você não precisa se preocupar com os manipuladores, está errada. Também parece abusar do Handler, pois você está enviando e recebendo mensagens no mesmo thread (você o cria no thread da interface do usuário e o envia em onPostExecute (), que também é executado no thread da interface do usuário).
Além disso, mesmo com essa solução alternativa, você ainda tem o problema de que, quando o contexto é destruído, você não tem registro das tarefas que ele disparou. Isso significa que você deve reiniciar qualquer tarefa ao recriar o contexto, por exemplo, após uma alteração na orientação da tela. Isso é lento e desperdício.
Minha solução para isso (conforme implementada na biblioteca Droid-Fu ) é manter um mapeamento de WeakReference
s dos nomes dos componentes para suas instâncias atuais no objeto de aplicativo exclusivo. Sempre que um AsyncTask é iniciado, ele registra o contexto de chamada nesse mapa e, em cada retorno de chamada, ele busca a instância de contexto atual desse mapeamento. Isso garante que você nunca faça referência a uma instância de contexto obsoleta e sempre tenha acesso a um contexto válido nos retornos de chamada, para que você possa fazer um trabalho significativo na interface do usuário. Também não vaza, porque as referências são fracas e são limpas quando não existe mais instância de um determinado componente.
Ainda assim, é uma solução alternativa complexa e requer a subclasse de algumas das classes da biblioteca Droid-Fu, tornando essa uma abordagem bastante intrusiva.
Agora, eu simplesmente quero saber: Estou apenas perdendo alguma coisa ou o AsyncTask é realmente totalmente defeituoso? Como suas experiências estão trabalhando com isso? Como você resolveu esse problema?
Obrigado pela sua contribuição.
fonte
Respostas:
Que tal algo como isso:
fonte
Desassociar manualmente a atividade do
AsyncTask
inonDestroy()
. Associe manualmente novamente a nova atividade aoAsyncTask
inonCreate()
. Isso requer uma classe interna estática ou uma classe Java padrão, além de talvez 10 linhas de código.fonte
Parece que
AsyncTask
é um pouco mais do que apenas conceitualmente falho . Também é inutilizável por problemas de compatibilidade. Os documentos do Android liam:Quando introduzidas pela primeira vez, as AsyncTasks foram executadas em série em um único encadeamento em segundo plano. Começando com DONUT, isso foi alterado para um pool de threads, permitindo que várias tarefas operassem em paralelo. Iniciando o HONEYCOMB, as tarefas voltam a ser executadas em um único encadeamento para evitar erros comuns de aplicativo causados pela execução paralela. Se você realmente deseja execução paralela, pode usar a
executeOnExecutor(Executor, Params...)
versão deste método comTHREAD_POOL_EXECUTOR
; no entanto, consulte os comentários para avisos sobre seu uso.Ambos
executeOnExecutor()
eTHREAD_POOL_EXECUTOR
são adicionados no nível de API 11 (3.0.x Android, Honeycomb).Isso significa que, se você criar dois
AsyncTask
s para baixar dois arquivos, o segundo download não será iniciado até que o primeiro termine. Se você conversar através de dois servidores, e o primeiro servidor estiver inativo, você não se conectará ao segundo antes que a conexão com o primeiro expire. (A menos que você use os novos recursos da API11, é claro, mas isso tornará seu código incompatível com 2.x).E se você deseja segmentar tanto 2.x como 3.0+, as coisas se tornam realmente complicadas.
Além disso, os documentos dizem:
Cuidado: Outro problema que você pode encontrar ao usar um encadeamento de trabalho é reiniciar inesperadamente em sua atividade devido a uma alteração na configuração do tempo de execução (como quando o usuário altera a orientação da tela), que pode destruir o encadeamento de trabalho . Para ver como você pode manter sua tarefa durante uma dessas reinicializações e como cancelar corretamente a tarefa quando a atividade é destruída, consulte o código-fonte do aplicativo de exemplo Shelves.
fonte
Provavelmente todos nós, incluindo o Google, estamos
AsyncTask
usando mal do ponto de vista do MVC .Uma Atividade é um Controlador , e o controlador não deve iniciar operações que possam sobreviver à Visualização . Ou seja, AsyncTasks deve ser usado no Model , de uma classe que não está vinculada ao ciclo de vida da atividade - lembre-se de que as atividades são destruídas na rotação. (No que diz respeito ao View , você normalmente não programa classes derivadas de, por exemplo, android.widget.Button, mas pode. Geralmente, a única coisa que você faz sobre o View é o xml.)
Em outras palavras, é errado colocar derivados do AsyncTask nos métodos de Atividades. OTOH, se não devemos usar AsyncTasks em Atividades, o AsyncTask perde sua atratividade: costumava ser anunciado como uma solução rápida e fácil.
fonte
Não sei se é verdade que você corre o risco de um vazamento de memória com uma referência a um contexto de uma AsyncTask.
A maneira usual de implementá-las é criar uma nova instância do AsyncTask no escopo de um dos métodos da Activity. Portanto, se a atividade for destruída, depois que o AsyncTask for concluído, ele não poderá ser alcançado e qualificado para a coleta de lixo? Portanto, a referência à atividade não importa, porque o AsyncTask em si não fica por aqui.
fonte
Seria mais robusto manter um WeekReference em sua atividade:
fonte
Por que não substituir o
onPause()
método na Atividade proprietária e cancelar aAsyncTask
partir daí?fonte
onPause
pois é tão ruim quanto segurá-lo em outro lugar? Ou seja, você pode obter um ANR?onPause
ou outra coisa) porque corremos o risco de obter um ANR.Você está absolutamente certo - é por isso que um movimento para não usar tarefas / carregadores assíncronos nas atividades para buscar dados está ganhando força. Uma das novas maneiras é usar uma estrutura Volley que essencialmente fornece um retorno de chamada assim que os dados estiverem prontos - muito mais consistente com o modelo MVC. O Volley foi preenchido no Google I / O 2013. Não sei por que mais pessoas não estão cientes disso.
fonte
Pessoalmente, apenas estendo o Thread e uso uma interface de retorno de chamada para atualizar a interface do usuário. Eu nunca poderia fazer o AsyncTask funcionar corretamente sem problemas de FC. Eu também uso uma fila sem bloqueio para gerenciar o pool de execução.
fonte
Eu pensei que cancelar funciona, mas não funciona.
aqui eles RTFMing sobre isso:
"" Se a tarefa já foi iniciada, o parâmetro mayInterruptIfRunning determina se o encadeamento que está executando esta tarefa deve ser interrompido na tentativa de interromper a tarefa. "
Isso não implica, no entanto, que o encadeamento seja interrompível. Isso é coisa do Java, não do AsyncTask ".
http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d
fonte
É melhor pensar em AsyncTask como algo que está mais fortemente associado a uma Atividade, Contexto, ContextWrapper, etc. É mais conveniente quando seu escopo é totalmente compreendido.
Verifique se você possui uma política de cancelamento em seu ciclo de vida, para que ela seja coletada como lixo e não mantenha mais uma referência à sua atividade, e também pode ser coletada.
Sem cancelar o seu AsyncTask enquanto você se afasta do seu Contexto, ocorrerá vazamentos de memória e NullPointerExceptions, se você simplesmente fornecer feedback como um Toast em uma caixa de diálogo simples, um singleton do seu Contexto do Aplicativo ajudará a evitar o problema do NPE.
O AsyncTask não é tão ruim, mas definitivamente há muita mágica acontecendo que pode levar a armadilhas imprevistas.
fonte
Quanto à "experiências de trabalho com ele": é possível para matar o processo , juntamente com todos os AsyncTasks, Android irá recriar a pilha de atividade de forma que o usuário não vai falar nada.
fonte