Eu tenho lido alguns artigos sobre vazamentos de memória no Android e assisti a este interessante vídeo do Google I / O sobre o assunto .
Ainda assim, não entendo completamente o conceito, e especialmente quando é seguro ou perigoso para o usuário classes internas dentro de uma Activity .
Isto é o que eu entendi:
Um vazamento de memória ocorrerá se uma instância de uma classe interna sobreviver mais do que sua classe externa (uma Atividade). -> Em que situações isso pode acontecer?
Neste exemplo, suponho que não há risco de vazamento, porque não há como a classe anônima estender OnClickListener
a vida mais do que a atividade, certo?
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.dialog_generic);
Button okButton = (Button) dialog.findViewById(R.id.dialog_button_ok);
TextView titleTv = (TextView) dialog.findViewById(R.id.dialog_generic_title);
// *** Handle button click
okButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
dialog.dismiss();
}
});
titleTv.setText("dialog title");
dialog.show();
Agora, este exemplo é perigoso e por quê?
// We are still inside an Activity
_handlerToDelayDroidMove = new Handler();
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 10000);
private Runnable _droidPlayRunnable = new Runnable() {
public void run() {
_someFieldOfTheActivity.performLongCalculation();
}
};
Tenho uma dúvida quanto ao fato de que entender esse tópico tem a ver com entender detalhadamente o que é mantido quando uma atividade é destruída e recriada.
É isso?
Digamos que eu mudei a orientação do dispositivo (que é a causa mais comum de vazamento). Quando super.onCreate(savedInstanceState)
será chamado no meu onCreate()
, isso restaurará os valores dos campos (como eram antes da mudança de orientação)? Isso também restaurará os estados das classes internas?
Sei que minha pergunta não é muito precisa, mas eu realmente aprecio qualquer explicação que possa tornar as coisas mais claras.
fonte
Respostas:
O que você está perguntando é uma pergunta bastante difícil. Embora você pense que é apenas uma pergunta, na verdade você está fazendo várias perguntas ao mesmo tempo. Farei o meu melhor com o conhecimento que tenho para cobri-lo e, espero, alguns outros se juntarão para cobrir o que posso sentir falta.
Classes aninhadas: Introdução
Como não tenho certeza de como você se sente confortável com o OOP em Java, isso será fundamental. Uma classe aninhada é quando uma definição de classe está contida em outra classe. Existem basicamente dois tipos: Classes aninhadas estáticas e Classes internas. A diferença real entre estes são:
Coleta de Lixo e Classes Internas
A coleta de lixo é automática, mas tenta remover objetos com base no fato de achar que eles estão sendo usados. O Garbage Collector é bastante inteligente, mas não é perfeito. Ele só pode determinar se algo está sendo usado se existe ou não uma referência ativa ao objeto.
O verdadeiro problema aqui é quando uma classe interna é mantida viva por mais tempo que seu contêiner. Isso ocorre devido à referência implícita à classe que o contém. A única maneira de isso ocorrer é se um objeto fora da classe que contém uma referência ao objeto interno, sem levar em conta o objeto que o contém.
Isso pode levar a uma situação em que o objeto interno está ativo (via referência), mas as referências ao objeto que contém já foram removidas de todos os outros objetos. O objeto interno é, portanto, manter vivo o objeto que o contém, pois sempre terá uma referência a ele. O problema é que, a menos que esteja programado, não há como voltar ao objeto que o contém para verificar se ele está vivo.
O aspecto mais importante para essa realização é que não faz diferença se está em uma atividade ou é um drawable. Você sempre precisará ser metódico ao usar classes internas e garantir que elas nunca superem os objetos do contêiner. Felizmente, se não for um objeto principal do seu código, os vazamentos podem ser pequenos em comparação. Infelizmente, esses são alguns dos vazamentos mais difíceis de encontrar, porque provavelmente passarão despercebidos até que muitos deles vazem.
Soluções: Classes internas
Atividades e Visões: Introdução
As atividades contêm muitas informações para poder executar e exibir. As atividades são definidas pela característica de que elas devem ter uma Visualização. Eles também têm certos manipuladores automáticos. Se você especificar ou não, a Atividade terá uma referência implícita à Visualização que ela contém.
Para que uma Visualização seja criada, ele deve saber onde criá-la e se possui filhos para que possa ser exibida. Isso significa que toda visualização tem uma referência à atividade (via
getContext()
). Além disso, todo View mantém referências a seus filhos (iegetChildAt()
). Por fim, cada Visualização mantém uma referência ao Bitmap renderizado que representa sua exibição.Sempre que você tem uma referência a uma atividade (ou contexto de atividade), isso significa que você pode seguir a cadeia INTEIRA na hierarquia de layout. É por isso que vazamentos de memória em relação a Atividades ou exibições são um grande negócio. Pode haver uma tonelada de memória vazando ao mesmo tempo.
Atividades, vistas e classes internas
Dadas as informações acima sobre Classes internas, esses são os vazamentos de memória mais comuns, mas também os mais evitados. Embora seja desejável que uma classe interna tenha acesso direto aos membros de uma classe de Atividades, muitos desejam apenas torná-los estáticos para evitar possíveis problemas. O problema com atividades e visualizações é muito mais profundo que isso.
Atividades vazadas, exibições e contextos de atividades
Tudo se resume ao contexto e ao ciclo de vida. Existem certos eventos (como orientação) que matam um contexto de atividade. Como muitas classes e métodos requerem um Contexto, os desenvolvedores às vezes tentam salvar algum código, pegando uma referência a um Contexto e mantendo-o. Acontece que muitos dos objetos que temos que criar para executar nossa Atividade precisam existir fora do Ciclo de Vida da Atividade, a fim de permitir que a Atividade faça o que precisa. Se algum dos seus objetos tiver uma referência a uma Atividade, seu Contexto ou qualquer uma de suas Visualizações quando ela for destruída, você acabou de vazar essa Atividade e toda a sua árvore Ver.
Soluções: Atividades e Visões
getBaseContext()
ougetApplicationContext()
). Estes não mantêm referências implicitamente.Runnables: Introdução
Runnables não são tão ruins assim. Quero dizer, eles poderiam ser, mas realmente já atingimos a maioria das zonas de perigo. Um Runnable é uma operação assíncrona que executa uma tarefa independente do encadeamento em que foi criado. A maioria das executáveis é instanciada a partir do thread da interface do usuário. Em essência, o uso de um Runnable está criando outro encadeamento, apenas um pouco mais gerenciado. Se você classifica um Runnable como uma classe padrão e segue as diretrizes acima, você deve ter poucos problemas. A realidade é que muitos desenvolvedores não fazem isso.
Por facilidade, legibilidade e fluxo lógico do programa, muitos desenvolvedores utilizam Classes Internas Anônimas para definir suas Runnables, como o exemplo que você criou acima. Isso resulta em um exemplo como o que você digitou acima. Uma classe interna anônima é basicamente uma classe interna discreta. Você simplesmente não precisa criar uma definição totalmente nova e simplesmente substituir os métodos apropriados. Em todos os outros aspectos, é uma classe interna, o que significa que mantém uma referência implícita ao seu contêiner.
Runnables e Atividades / Views
Yay! Esta seção pode ser curta! Devido ao fato de os Runnables serem executados fora do encadeamento atual, o perigo com eles ocorre em operações assíncronas de longa execução. Se o executável for definido em uma Atividade ou Exibição como uma Classe Interna Anônima OU Classe Interna aninhada, existem alguns perigos muito sérios. Isso ocorre porque, como afirmado anteriormente, ele precisa saber quem é seu contêiner. Digite a mudança de orientação (ou interrupção do sistema). Agora basta voltar às seções anteriores para entender o que aconteceu. Sim, seu exemplo é bastante perigoso.
Soluções: Runnables
Respondendo à pergunta final Agora, para responder às perguntas que não foram abordadas diretamente pelas outras seções desta postagem. Você perguntou "Quando um objeto de uma classe interna pode sobreviver mais tempo do que sua classe externa?" Antes de chegarmos a isso, deixe-me enfatizar: embora você tenha razão em se preocupar com isso em Atividades, ele pode causar um vazamento em qualquer lugar. Fornecerei um exemplo simples (sem usar uma Atividade) apenas para demonstrar.
Abaixo está um exemplo comum de uma fábrica básica (sem o código).
Este não é um exemplo tão comum, mas simples o suficiente para demonstrar. A chave aqui é o construtor ...
Agora, temos vazamentos, mas nenhuma fábrica. Mesmo que tenhamos lançado a Fábrica, ela permanecerá na memória, pois cada Leak tem uma referência a ela. Nem importa que a classe externa não tenha dados. Isso acontece com muito mais frequência do que se imagina. Não precisamos do criador, apenas de suas criações. Então criamos uma temporariamente, mas usamos as criações indefinidamente.
Imagine o que acontece quando mudamos um pouco o construtor.
Agora, cada um desses novos LeakFactories acaba de vazar. O que você acha daquilo? Esses são dois exemplos muito comuns de como uma classe interna pode sobreviver a uma classe externa de qualquer tipo. Se essa classe externa tivesse sido uma Atividade, imagine quanto pior teria sido.
Conclusão
Eles listam os perigos principalmente conhecidos do uso inadequado desses objetos. Em geral, este post deve ter abordado a maioria das suas perguntas, mas entendo que foi um post muito longo; portanto, se você precisar de esclarecimentos, entre em contato. Contanto que você siga as práticas acima, você terá muito pouca preocupação com vazamentos.
fonte