Passei 4 dias inteiros tentando de tudo para descobrir o vazamento de memória em um aplicativo que estou desenvolvendo, mas as coisas deixaram de fazer sentido há muito tempo.
O aplicativo que estou desenvolvendo é de natureza social, então pense no perfil de Atividades (P) e liste Atividades com dados - por exemplo, emblemas (B). Você pode pular de um perfil para uma lista de emblemas, para outros perfis, para outras listas, etc.
Então imagine um fluxo como este P1 -> B1 -> P2 -> B2 -> P3 -> B3, etc. Para consistência, estou carregando perfis e emblemas do mesmo usuário, então cada página P é a mesma e assim é cada página B.
A essência geral do problema é: depois de navegar um pouco, dependendo do tamanho de cada página, recebo uma exceção de falta de memória em locais aleatórios - bitmaps, strings, etc - não parece ser consistente.
Depois de fazer tudo que você pode imaginar para descobrir por que estou ficando sem memória, não encontrei nada. O que não entendo é por que o Android não está matando P1, B1, etc, se ficar sem memória ao carregar e, em vez disso, travar. Eu esperaria que essas atividades anteriores morressem e fossem ressuscitadas se eu voltasse a elas via onCreate () e onRestoreInstanceState ().
Sem falar nisso - mesmo se eu fizer P1 -> B1 -> Voltar -> B1 -> Voltar -> B1, ainda assim recebo um travamento. Isso indica algum tipo de vazamento de memória, mas mesmo depois de despejar hprof e usar MAT e JProfiler, não consigo identificá-lo.
Desativei o carregamento de imagens da web (e aumentei os dados de teste carregados para compensar e tornar o teste justo) e certifiquei-me de que o cache de imagem usa SoftReferences. Na verdade, o Android tenta liberar as poucas SoftReferences que possui, mas logo antes de perder memória.
As páginas do Badge obtêm dados da web, carregam-nos em um array de EntityData de um BaseAdapter e enviam para um ListView (na verdade estou usando o excelente MergeAdapter do CommonsWare , mas nesta atividade do Badge, há realmente apenas 1 adaptador, mas eu queria mencionar esse fato de qualquer maneira).
Eu examinei o código e não consegui encontrar nada que vazasse. Limpei e anulei tudo que pude encontrar e até mesmo System.gc () à esquerda e à direita, mas ainda assim o aplicativo trava.
Ainda não entendo por que as atividades inativas que estão na pilha não são colhidas e eu realmente adoraria descobrir isso.
Neste ponto, estou procurando por dicas, conselhos, soluções ... qualquer coisa que possa ajudar.
Obrigado.
fonte
outOfMemory
erro. Obrigado!Respostas:
Não é assim que as coisas funcionam. O único gerenciamento de memória que impacta o ciclo de vida da atividade é a memória global em todos os processos, já que o Android decide que está com pouca memória e, portanto, precisa eliminar os processos em segundo plano para recuperá-la.
Se o seu aplicativo está em primeiro plano, iniciando mais e mais atividades, ele nunca vai para o segundo plano, portanto, sempre atingirá seu limite de memória de processo local antes que o sistema chegue perto de encerrar seu processo. (E quando ele matar seu processo, ele irá matar o processo que hospeda todas as atividades, incluindo tudo o que está atualmente em primeiro plano.)
Portanto, parece-me que seu problema básico é: você está permitindo que muitas atividades sejam executadas ao mesmo tempo e / ou cada uma dessas atividades está segurando muitos recursos.
Você só precisa redesenhar sua navegação para não depender do empilhamento de um número arbitrário de atividades potencialmente pesadas. A menos que você faça muitas coisas em onStop () (como chamar setContentView () para limpar a hierarquia de visualização da atividade e limpar as variáveis de qualquer outra coisa que ela possa estar segurando), você simplesmente vai ficar sem memória.
Você pode querer considerar o uso das novas APIs de fragmento para substituir essa pilha arbitrária de atividades por uma única atividade que gerencia de forma mais rígida sua memória. Por exemplo, se você usar os recursos de pilha de retorno de fragmentos, quando um fragmento vai para a pilha de retorno e não está mais visível, seu método onDestroyView () é chamado para remover completamente sua hierarquia de visualização, reduzindo bastante sua área de cobertura.
Agora, quanto a você travar no fluxo onde pressiona para trás, vai para uma atividade, pressiona para trás, vai para outra atividade, etc e nunca tem uma pilha profunda, então sim, você só tem um vazamento. Esta postagem do blog descreve como depurar vazamentos: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html
fonte
Activity
JavaDocs.Algumas dicas:
Certifique-se de não estar no contexto da atividade de vazamento.
Certifique-se de não manter referências em bitmaps. Limpe todas as suas ImageViews em Activity # onStop, algo assim:
Recicle bitmaps se você não precisar mais deles.
Se você usa cache de memória, como memory-lru, certifique-se de que não está usando muita memória.
Não apenas as imagens ocupam muita memória, mas certifique-se de não manter muitos outros dados na memória. Isso pode acontecer facilmente se você tiver listas infinitas em seu aplicativo. Tente armazenar dados em cache no DataBase.
No Android 4.2, há um bug (stackoverflow # 13754876) com aceleração de hardware, então se você usar
hardwareAccelerated=true
em seu manifesto, haverá vazamento de memória.GLES20DisplayList
- mantenha as referências retidas, mesmo se você executou a etapa (2) e ninguém mais está fazendo referência a este bitmap. Aqui você precisa:a) desative a aceleração de hardware para api 16/17;
ou
b) desanexar a vista que contém o bitmap
Para Android 3+, você pode tentar usar
android:largeHeap="true"
no seuAndroidManifest
. Mas não resolverá seus problemas de memória, apenas adie-os.Se você precisa de navegação infinita, então Fragments - deve ser sua escolha. Portanto, você terá 1 atividade, que apenas alternará entre os fragmentos. Dessa forma, você também resolverá alguns problemas de memória, como o número 4.
Use o Memory Analyzer para descobrir a causa do vazamento de memória.
Aqui está um vídeo muito bom do Google I / O 2011: Gerenciamento de memória para aplicativos Android
Se você está lidando com bitmaps, deve ler: Exibindo bitmaps com eficiência
fonte
outOfMemory
erro. Obrigado!Os bitmaps costumam ser os culpados por erros de memória no Android, então essa seria uma boa área para verificar novamente.
fonte
outOfMemory
erro. Obrigado!Você está segurando algumas referências para cada atividade? AFAIK esta é uma razão que impede o Android de excluir atividades da pilha.
Você também consegue reproduzir esse erro em outros dispositivos? Eu experimentei um comportamento estranho de alguns dispositivos Android dependendo da ROM e / ou fabricante do hardware.
fonte
Acho que o problema pode ser uma combinação de muitos fatores declarados aqui nas respostas que está causando problemas. Como @Tim disse, uma referência (estática) a uma atividade ou um elemento nessa atividade pode fazer com que o GC pule a atividade. Aqui está o artigo que discute essa faceta. Eu acho que o problema provável vem de algo que mantém a Activity em um estado de "Processo Visível" ou superior, o que garante que a Activity e seus recursos associados nunca serão recuperados.
Eu passei pelo problema oposto um tempo atrás com um serviço, então foi isso que me fez pensar: há algo mantendo sua atividade no topo da lista de prioridades do processo para que ela não esteja sujeita ao GC do sistema, como uma referência (@Tim) ou um loop (@Alvaro). O loop não precisa ser um item interminável ou de longa duração, apenas algo que funciona muito como um método recursivo ou loop em cascata (ou algo nesse sentido).
EDIT: Pelo que entendi, onPause e onStop são chamados automaticamente pelo Android conforme necessário. Os métodos existem principalmente para você substituir, para que possa cuidar do que precisa antes que o processo de hospedagem seja interrompido (salvar variáveis, salvar manualmente o estado, etc.); mas observe que está claramente declarado que onStop (junto com onDestroy) pode não ser chamado em todos os casos. Além disso, se o processo de hospedagem também estiver hospedando uma Atividade, Serviço, etc. que tenha um status "Forground" ou "Visível", o SO pode nem mesmo olhar para interromper o processo / thread. Por exemplo: uma atividade e um serviço são avaliados no mesmo processo e o serviço retorna
START_STICKY
deonStartCommand()
o processo assume automaticamente pelo menos um status visível. Essa pode ser a chave aqui, tente declarar um novo proc para a Activity e veja se isso muda alguma coisa. Tente adicionar esta linha à declaração de sua atividade no manifesto como:android:process=":proc2"
e, em seguida, execute os testes novamente se sua atividade compartilha um processo com qualquer outra coisa. O pensamento aqui é que se você limpar sua atividade e são bastante certeza de que o problema não é a sua atividade, em seguida, outra coisa é o problema e é hora de caçador para isso.Além disso, não me lembro onde o vi (se é que o vi nos documentos do Android), mas me lembro de algo sobre fazer
PendingIntent
referência a uma Activity pode fazer com que uma Activity se comporte dessa maneira.Aqui está um link para a
onStartCommand()
página com alguns insights sobre a frente de não matar do processo.fonte
então, a única coisa que posso realmente pensar é se você tem uma variável estática que faz referência direta ou indiretamente ao contexto. Mesmo algo como uma referência a parte do aplicativo. Tenho certeza que você já tentou, mas vou sugerir apenas no caso, tente apenas anular TODAS as suas variáveis estáticas no onDestroy () apenas para garantir que o coletor de lixo o pegue
fonte
A maior fonte de vazamento de memória que encontrei foi causada por alguma referência global, de alto nível ou de longa data ao contexto. Se você estiver mantendo o "contexto" armazenado em uma variável em qualquer lugar, poderá encontrar vazamentos de memória imprevisíveis.
fonte
Tente passar getApplicationContext () para qualquer coisa que precise de um Context. Você pode ter uma variável global que está mantendo uma referência às suas atividades e evitando que sejam coletadas como lixo.
fonte
Uma das coisas que realmente ajudaram no problema de memória no meu caso acabou sendo configurada inPurgeable como true para meus bitmaps. Consulte Por que eu NÃO usaria a opção inPurgeable do BitmapFactory? e a discussão da resposta para mais informações.
A resposta de Dianne Hackborn e nossa discussão subsequente (também obrigada, CommonsWare) ajudaram a esclarecer certas coisas sobre as quais eu estava confuso, então obrigada por isso.
fonte
Eu encontrei o mesmo problema com você. Estava trabalhando em um aplicativo de mensagens instantâneas, para o mesmo contato, é possível iniciar um ProfileActivity em um ChatActivity, e vice-versa. Acabei de adicionar uma string extra na intenção de iniciar outra atividade, ela pega as informações do tipo de classe da atividade inicial e o ID do usuário. Por exemplo, ProfileActivity inicia uma ChatActivity, então em ChatActivity.onCreate, eu marco o tipo de classe de invocador 'ProfileActivity' e a id do usuário, se for para iniciar uma Activity, eu verificaria se é uma 'ProfileActivity' para o usuário ou não . Nesse caso, basta chamar 'finish ()' e voltar para o ProfileActivity anterior em vez de criar um novo. Vazamento de memória é outra coisa.
fonte