Fuga de memória do Android

Como a gestão adequada do ciclo de vida pode evitar fugas de memória do Android

OutOfMemoryException é um bug comum e frustrante, e uma das principais causas de encerramento inesperado da aplicação.

“Porque é que isto está a acontecer agora, se a aplicação estava a funcionar perfeitamente ontem? É uma pergunta que deixa perplexo tanto o novato quanto os desenvolvedores Android avançados do mundo inteiro.

Existe uma variedade de causas potenciais de OutOfMemory Exceptions, mas uma das mais comuns é o vazamento de memória – a alocação de memória no aplicativo que nunca é lançado. Este artigo irá explicar como minimizar este risco através de um gerenciamento eficaz do ciclo de vida – uma parte crucial mas muitas vezes negligenciada do processo de desenvolvimento.

TL;DR

Neste artigo vamos discutir:

  • As razões para o vazamento de memória do Android
  • Passos básicos para evitá-lo
  • Formas mais avançadas de gerenciamento do ciclo de vida
  • Ferramentas que podem gerenciar o ciclo de vida do seu produto

Por que o vazamento de memória RAM acontece no Android?

O problema é simples. Certos objetos só devem ter uma vida útil fixa, e quando sua vida útil termina, eles precisam ser removidos.

Em teoria, esta memória deve ser descartada quando um processo é morto usando onStop ou onDestroy. No entanto, o mau uso do objeto de referência pode impedir que o coletor de lixo desloque os objetos não utilizados. Por exemplo: se o objeto A não usado referencia o objeto B não usado, você acabará com dois objetos desnecessários que o coletor de lixo nunca irá desalocar, pois eles estão referenciando um ao outro.

Truques Comuns para Impedir que Isso Aconteça

Desenvolvedores podem tomar uma série de passos para impedir que atividades mortas fiquem presas na memória.

  1. Registar/Enviar TransmissõesReceptores no Resume()/onPause() ou onStart()/onStop()
  2. NUNCA use variáveis estáticas para Views/Activities/Contexts
  3. Singletons que precisam manter referências ao Contexto devem usar o applicationContext() ou envolvê-lo em WeakReference
  4. Tenha cuidado com o anônimo e nãoestáticas, uma vez que têm uma referência implícita à sua classe envolvente.
    1. Utilizar classes internas estáticas em vez de anônimas se elas vão sobreviver à classe pai (como Handlers).
    2. Se a classe interna ou anônima for cancelável (como AsyncTask, Thread, RxSubscriptions), cancele-a quando a atividade for destruída.

O Ciclo de Vida do Android Components Aware

Após ter completado os passos básicos acima, é hora de algo muito mais importante: O Ciclo de Vida das atividades do aplicativo. Se não gerirmos correctamente o ciclo de vida, acabaremos por nos agarrar à memória quando já não for necessário.

Existem muitas tarefas diferentes envolvidas nisto. Para cada atividade, precisamos interromper threads, nos livrar de assinaturas em RxJava, cancelar referências AsyncTask e garantir que a referência desta atividade (e qualquer outra atividade ligada a ela) seja removida corretamente. Todas estas tarefas podem drenar uma enorme quantidade de tempo do desenvolvedor.

As coisas são ainda mais complicadas pelo Model View Presenter (MVP), o padrão arquitetônico frequentemente empregado para construir a interface do usuário no Android. MVP, entretanto, é útil para desacoplar a lógica de negócios do View.

No padrão MVP, ambos View e Presenter são implementações abstratas de um contrato para um comportamento entre eles. A forma mais comum de implementar MVP é usando uma Activity/Fragment como uma implementação para a View e uma implementação simples para o apresentador que está acostumado a ter uma referência da View.

Então acabamos tendo uma View com uma referência do Apresentador e um Apresentador com uma Referência de View (Dica: Temos um vazamento potencial aqui).

Dadas estas potenciais dificuldades, é essencial que nós coloquemos uma estrutura de gestão adequada para remover o excesso de memória criado durante o ciclo de vida. Existem várias formas comprovadas de o fazer:

Criar componentes conscientes do ciclo de vida utilizando o Android Arch Lifecycle no Android Studio

Os componentes conscientes do ciclo de vida são inteligentes. Eles podem reagir a uma mudança no status do ciclo de vida de outro componente, como atividades ou fragmentos, livrando-se da memória, por exemplo. Isto significa que o código é mais leve e muito mais eficiente em termos de memória.

Arch Lifecycle é uma nova biblioteca do Android que oferece um conjunto de ferramentas para construir componentes com consciência do ciclo de vida. A biblioteca funciona de uma forma abstrata, o que significa que os proprietários do ciclo de vida não precisam mais se preocupar em gerenciar o ciclo de vida de tarefas e atividades específicas.

As principais ferramentas e definições do Arch Lifecycle são as seguintes:

  • LifeCycle: Um sistema de classificação que define quais objetos têm um ciclo de vida Android e permite que eles sejam monitorados.
  • LifecycleObserver: Uma interface regular que monitora cada objeto identificado como tendo um ciclo de vida Android, usando uma fórmula simples para lidar com cada evento chave do ciclo de vida.
  • @OnLifecycleEvent: Uma anotação que pode ser usada em classes que implementam a interface LifecycleObserver. Permite-nos definir eventos chave do ciclo de vida que irão acionar o método anotado sempre que for lançado. Aqui está uma lista de todos os eventos que podem ser definidos:
      >

    • EM_UM_UM
    • EM_CRIAR
    • EM_DESTROY
    • EM_PAUSA
    • EM_RESUMIR
    • EM_INICIAR
    • ON_STOP
  • LifecycleOwner é implementado por padrão para cada componente Android cujo ciclo de vida pode ser gerenciado, e dá ao desenvolvedor o controle de cada evento.

Usando estas ferramentas, somos capazes de enviar todas as tarefas limpas para os seus proprietários (apresentadores no nosso caso), por isso temos um código limpo, desacoplado e livre de fugas (pelo menos na camada de apresentador).

Aqui está uma implementação super-básica para mostrar o que estamos falando:

interface View: MVPView, LifecycleOwnerclass RandomPresenter : Presenter<View>, LifecycleObserver { private lateinit var view: View override fun attachView(view: View) { this.view = view view.lifecycle.addObserver(this) } @OnLifecycleEvent(Lifecycle.Event.On_DESTROY) fun onClear() {//TODO: clean }

Utilizando modelos de visualização de arquivos Android como apresentadores e LiveData

Outra forma de evitar vazamento de memória através da má gestão do ciclo de vida é usando os novos ViewModels da biblioteca de componentes do Arch.

A ViewModel é uma classe Abstract que implementa uma única função conhecida como onClear, que é chamada automaticamente quando um determinado objeto tem que ser removido. O ViewModel é gerado pelo framework e é anexado ao ciclo de vida do criador (como um bônus adicional, é super fácil de injetar com Dagger.)

Como também usa ViewModel, LiveData também fornece um canal vital de comunicação. O produto segue um padrão reativo e fácil de observar, o que significa que objetos individuais podem observá-lo (criando menos código acoplado).

O grande ponto aqui é que o LiveData pode ser observado por um proprietário do ciclo de vida, assim a transferência de dados é sempre gerenciada pelo ciclo de vida – e podemos garantir que qualquer referência seja retida enquanto os utilizamos.

Using LeakCanary and Bugfender

Além dos passos acima mencionados, queríamos recomendar duas peças importantes do kit: LeakCanary, uma ferramenta popular para monitorar vazamentos, e o nosso próprio Bugfender.

LeakCanary é uma biblioteca de detecção de memória para Android e Java. É de código aberto, então há uma enorme comunidade por trás dela, e não apenas lhe conta sobre um vazamento – ela o informa sobre a causa provável.

Bugfender, nossa ferramenta de registro remoto, permite que você depure individualmente o LeakTraces e estenda uma classe chamada DisplayLeakService, que nos permite saber quando um vazamento é levantado. Então nós podemos registrá-lo facilmente com o Bugfender.

public class LeakUploadService extends DisplayLeakService { override fun afterDefaultHandling(heapDump: HeapDump, result: AnalysisResult, leakInfo: String) { if (result.leakFound) { Bugfender.d("LeakCanary", result.toString()) } }}

Além disso, os usuários obtêm todos os outros benefícios do Bugfender incluindo gravação de log 24/7 (mesmo quando o dispositivo está offline), relatórios de falhas embutidos e um console web fácil de usar, o que permite drill-down em dispositivos individuais para melhor atendimento ao cliente.

Para mais sobre o Bugfender, por favor clique aqui.

Leave a Reply