Práticas recomendadas para o gerenciamento de memória

Neste documento, consideramos que você aplicou as recomendações para apps Android com relação ao gerenciamento de memória, por exemplo, Gerenciar a memória do seu app.

Introdução

Vazamento de memória é quando um programa de computador não libera a memória alocada que não é mais necessária. Nesse cenário, o aplicativo pode travar porque vai solicitar ao SO mais memória do que há disponível. Algumas práticas inadequadas resultam em vazamento de memória nos apps Android, por exemplo, deixar de liberar recursos ou não cancelar a inscrição de listeners que não são mais necessários.

Neste documento, você encontra práticas recomendadas para evitar, detectar e resolver vazamentos de memória no seu código. Se mesmo assim você ainda suspeitar desse problema nos seus SDKs, consulte Como reportar problemas nos SDKs do Google.

Antes de contatar o suporte

Antes de informar o Google sobre um vazamento de memória, siga as práticas recomendadas e as etapas de depuração descritas neste documento para ter certeza de que não há erros no seu código. Se o problema persistir, as informações geradas nestes processos vão ajudar a Equipe de suporte do Google a entender a situação.

Evitar vazamentos de memória

Siga as práticas recomendadas e evite algumas das principais causas de vazamento de memória em códigos que usam os SDKs do Google.

Práticas recomendadas para apps Android

Verifique se você seguiu todas as etapas abaixo no seu app Android:

  1. Liberar recursos não utilizados
  2. Cancelar inscrição de listeners que não são mais necessários
  3. Cancelar tarefas desnecessárias
  4. Encaminhar métodos do ciclo de vida para liberar recursos
  5. Usar as versões mais recentes dos SDKs

Para detalhes específicos, consulte as próximas seções.

Liberar recursos não utilizados

Libere os recursos que deixaram de ser necessários depois que seu app Android terminou de usá-los. Caso contrário, o aplicativo continua consumindo memória sem precisar. Para mais informações, consulte O ciclo de vida da atividade na documentação do Android.

Liberar referências desatualizadas do GoogleMap nos GeoSDKs

Um erro comum é quando um GoogleMap causa um vazamento de memória por ter sido armazenado em cache usando NavigationView ou MapView. Um GoogleMap tem uma relação individual com a classe NavigationView ou MapView da qual ele é recuperado. Você precisa garantir que um GoogleMap não seja armazenado em cache ou que a referência seja liberada quando o objeto NavigationView#onDestroy ou MapView#onDestroy é chamado. Se você usa o componente NavigationSupportFragment, MapSupportFragment ou seu próprio fragmento, a referência precisa ser liberada em Fragment#onDestroyView.

class NavFragment : SupportNavigationFragment() {

  var googleMap: GoogleMap?

  override fun onCreateView(
    inflater: LayoutInflater,
    parent: ViewGroup?,
    savedInstanceState: Bundle?,
  ): View  {
    super.onCreateView(inflater,parent,savedInstanceState)
    getMapAsync{map -> googleMap = map}
  }

  override fun onDestroyView() {
    googleMap = null
  }
}

Cancelar inscrição de listeners que não são mais necessários

Depois que um app Android inscreve um listener para um evento, como um botão de clique ou uma mudança no estado de uma visualização, ele precisa cancelar essa inscrição quando a necessidade de monitorar o evento deixa de existir. Caso contrário, os listeners continuam consumindo memória sem precisar.

Vamos supor que seu aplicativo use o SDK do Navigation e solicite que o listener addArrivalListener acompanhe os eventos de chegada. Ele também precisará chamar removeArrivalListener quando não for mais necessário monitorar esses eventos.

var arrivalListener: Navigator.ArrivalListener? = null

fun registerNavigationListeners() {
  arrivalListener =
    Navigator.ArrivalListener {
      ...
    }
  navigator.addArrivalListener(arrivalListener)
}

override fun onDestroy() {
  navView.onDestroy()
  if (arrivalListener != null) {
    navigator.removeArrivalListener(arrivalListener)
  }

  ...
  super.onDestroy()
}

Cancelar tarefas desnecessárias

Seu app Android precisa cancelar tarefas assíncronas, como fazer download ou solicitar rede, quando a atividade é concluída. De outra forma, ela continua sendo executada em segundo plano.

Para mais detalhes sobre práticas recomendadas, consulte Gerenciar a memória do seu app na documentação do Android.

Encaminhar métodos do ciclo de vida para liberar recursos

Se o seu app usa o SDK do Navigation ou do Maps, encaminhe os métodos do ciclo de vida (em negrito) ao navView para liberar os recursos. Você pode fazer isso utilizando NavigationView no SDK do Navigation ou MapView no SDK do Maps. Outra opção é usar SupportNavigationFragment ou SupportMapFragment em vez de NavigationView e MapView, respectivamente. Os fragmentos de suporte fazem o encaminhamento dos métodos do ciclo de vida.

class NavViewActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    navView = ...
    navView.onCreate(savedInstanceState)
    ...
  }

  override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)
    navView.onSaveInstanceState(savedInstanceState)
  }

  override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    navView.onTrimMemory(level)
  }

  /* Same with
    override fun onStart()
    override fun onResume()
    override fun onPause()
    override fun onConfigurationChanged(...)
    override fun onStop()
    override fun onDestroy()
  */
}

Usar as versões mais recentes dos SDKs

Os SDKs do Google são constantemente atualizados com novos recursos, correções de bugs e melhorias de performance. Mantenha os SDKs do seu app atualizados para receber essas correções.

Depurar vazamentos de memória

Se o vazamento de memória persistir mesmo depois de você implementar todas as sugestões relevantes deste documento, siga o processo de depuração abaixo.

Antes de começar, você precisa conhecer bem o processo de gerenciamento de memória no Android. Confira mais informações na Visão geral do gerenciamento de memória do Android.

Para depurar vazamentos de memória, faça o seguinte:

  1. Recrie o problema. Para a depuração, isso é essencial.
  2. Verifique se a memória está sendo usada conforme o esperado. É possível que o aumento no uso, apesar de parecer um vazamento, seja, na verdade, a memória necessária para executar seu app.
  3. Depure em um alto nível. Você pode usar vários utilitários para fazer a depuração. Entre eles, estão estas três ferramentas padrão que resolvem problemas de memória no Android: Android Studio, Perfetto e os utilitários da linha de comando do Android Debug Bridge (adb).
  4. Verifique o uso da memória pelo app. Utilize um heap dump e o rastreamento de alocação para fazer suas análises.
  5. Corrija vazamentos de memória.

As seções abaixo trazem mais detalhes sobre essas etapas.

1ª etapa: recriar o problema

Se isso não for possível, considere o que poderia ter levado ao vazamento de memória. Pode ser útil ir direto para a análise do heap dump se você tiver certeza de que o problema foi recriado. No entanto, se o heap dump só é referente à inicialização do app ou outro momento aleatório, talvez você ainda não tenha as condições necessárias para gerar um vazamento. Na hora de recriar o problema, considere trabalhar com várias situações:

  • Quais recursos foram ativados?

  • Qual sequência específica de ações do usuário gerou o vazamento?

    • Você testou várias iterações relacionadas à ativação dessa sequência?
  • Que estados do ciclo de vida o app percorreu?

    • Você testou várias interações em diferentes estados do ciclo de vida?

Confirme se você pode recriar o problema na versão mais recente dos SDKs. O problema de uma versão anterior pode já ter sido corrigido.

2ª etapa: verificar se o app está fazendo o uso esperado da memória

Todo recurso precisa de mais memória. Quando estiver depurando cenários diferentes, convém analisar se o uso é esperado ou se é mesmo um vazamento de memória. Por exemplo, considere as possibilidades abaixo para diferentes recursos ou tarefas do usuário:

  • Provável vazamento: a ativação do cenário com várias iterações resulta em um aumento no uso da memória com o tempo.

  • Provável uso esperado da memória: a memória é recuperada quando o cenário é interrompido.

  • Possível uso esperado da memória: há um aumento no uso da memória apenas por um tempo, que pode ser devido a um cache limitado ou outra utilização esperada.

Se o comportamento do app for um provável uso esperado da memória, uma possível solução é gerenciar a memória dele. Para mais informações, consulte Gerenciar a memória do seu app.

3ª etapa: depurar em um nível alto

Quando for depurar um vazamento de memória, comece de um nível alto e limite o escopo à medida que reduz as possibilidades. Use uma destas ferramentas de depuração de alto nível para analisar se há vazamentos:

Memory Profiler do Android Studio

Essa ferramenta cria um histograma visual do consumo de memória. Nessa interface também é possível acionar heap dumps e o rastreamento de alocação. Essa ferramenta é a recomendação padrão. Para mais informações, consulte Memory Profiler do Android Studio.

Contadores de memória do Perfetto

O Perfetto oferece controle preciso no rastreamento de diversas métricas e as apresenta em um único histograma. Para mais informações, consulte Contadores de memória do Perfetto.

Interface do usuário do Perfetto

Utilitários da linha de comando do Android Debug Bridge (adb)

Muito do que é possível rastrear com o Perfetto também está disponível como um utilitário de linha de comando do adb, que você pode consultar de forma direta. Estes são alguns exemplos importantes:

  • Com o Meminfo, você consulta informações detalhadas da memória em um momento específico.

  • O Procstats disponibiliza algumas estatísticas agregadas importantes ao longo do tempo.

Uma estatística essencial que precisa ser analisada é o consumo máximo de memória física (maxRSS) que o app exige com o tempo. O MaxPSS não tem tanta precisão. Para gerar resultados precisos, consulte a flag adb shell dumpsys procstats --help –start-testing.

Rastreamento de alocação

Esse recurso identifica o stack trace da alocação de memória e verifica se ele foi liberado ou não. Essa etapa é bastante útil na hora de encontrar vazamentos em código nativo. A ferramenta identifica o stack trace e ajuda a depurar a causa raiz ou a descobrir como recriar o problema rapidamente. Consulte Depurar a memória em código nativo com o rastreamento de alocação para saber como usar esse recurso.

4ª etapa: verificar o uso de memória pelo app com um heap dump

É possível detectar vazamentos de memória analisando um heap dump do seu app. Um heap dump é um resumo de todos os objetos na memória de um aplicativo. Use esse recurso para diagnosticar vazamentos e outros problemas de memória.

O Android Studio consegue detectar vazamentos de memória que não podem ser corrigidos pelo GC. Quando você gerar um heap dump, o Android Studio verifica se há alguma atividade ou fragmento que ainda pode ser alcançado, mas que já foi destruído.

  1. Gerar um heap dump
  2. Analisar um heap dump para encontrar vazamentos de memória
  3. Corrigir vazamentos de memória

Confira mais detalhes nas próximas seções.

Gerar um heap dump

Para criar um heap dump, você pode usar o Android Debug Bridge (adb) ou o Memory Profiler do Android Studio.

Usar o adb para gerar um heap dump

Para criar um heap dump usando o adb, siga estas etapas:

  1. Conecte o dispositivo Android ao computador.
  2. Abra um prompt de comando e acesse o diretório em que as ferramentas do adb estão.
  3. Para gerar um heap dump, execute este comando:

    adb shell am dumpheap my.app.name $PHONE_FILE_OUT

  4. Para recuperar o heap dump, execute este comando:

    adb pull $PHONE_FILE_OUT $LOCAL_FILE.

Usar o Android Studio para gerar um heap dump

Para criar um heap dump usando o Memory Profiler do Android Studio, siga as etapas na seção Gerar um heap dump do Android.

Analisar o heap dump para encontrar vazamentos de memória

Depois de gerar um heap dump, é possível fazer a análise dele usando o Memory Profiler do Android Studio. Para isso, siga estas etapas:

  1. Abra seu projeto do Android no Android Studio.

  2. Selecione Executar e depois a configuração Depurar.

  3. Abra a guia Android Profiler.

  4. Selecione Memória.

  5. Selecione Abrir heap dump e escolha o arquivo gerado. O Memory Profiler vai mostrar um gráfico do uso da memória do seu app.

  6. Use o gráfico para analisar o heap dump:

    • Identifique objetos que não estão mais sendo usados.

    • Identifique objetos que usam muita memória.

    • Consulte quanta memória cada objeto está usando.

  7. Use essas informações para restringir a análise ou encontrar a origem do vazamento de memória e corrigir o problema.

5ª etapa: corrigir vazamentos de memória

Para corrigir um vazamento de memória, primeiro é preciso identificar a origem dele. Isso ajuda a melhorar a performance e a estabilidade de apps Android. Dependendo do cenário, os detalhes variam. No entanto, as sugestões a seguir podem ajudar:

Outras ferramentas de depuração

Se, após concluir essas etapas, você ainda não tiver encontrado e corrigido o vazamento de memória, tente fazer o seguinte:

Depurar a memória no código nativo com o rastreamento de alocação

Mesmo que você não esteja usando o código nativo, várias bibliotecas comuns do Android usam, incluindo os SDKs do Google. Se você acredita que o vazamento de memória está no código nativo, há várias ferramentas que podem ser usadas para a depuração. O rastreamento de alocação com o Android Studio ou com o perfilador heapprofd (compatível também com o Perfetto) é uma ótima maneira de identificar possíveis causas de vazamento de memória e costuma ser a forma mais rápida de depuração.

Esse recurso também tem a vantagem exclusiva de permitir que você compartilhe os resultados sem incluir informações sensíveis que podem ser encontradas em um heap.

Identificar vazamentos usando o LeakCanary

O LeakCanary é uma ferramenta eficiente para identificar vazamentos de memória em apps Android. Para saber mais sobre como usar o recurso no seu app, acesse o site do LeakCanary (em inglês).

Como reportar problemas nos SDKs do Google

Se você testou os métodos descritos neste documento e suspeita de vazamento de memória nos nossos SDKs, entre em contato com o suporte ao cliente com o máximo possível das informações a seguir:

  • Etapas para recriar o vazamento de memória. Se as etapas exigirem uma codificação complexa, pode ser útil copiar o código que replica o problema no nosso app de exemplo e disponibilizar as instruções que precisam ser seguidas na interface para acionar o vazamento.

  • Heap dumps gerados do seu app com o problema recriado. Gere heap dumps em dois momentos diferentes que demonstrem que o uso da memória aumentou de forma substancial.

  • Se um vazamento de memória nativo for esperado, compartilhe o resultado do rastreamento da alocação do perfilador heapprofd.

  • Um relatório do bug gerado após a recriação da condição de vazamento.

  • Stack traces de qualquer falha relacionada à memória.

    Observação importante: os stack traces geralmente não são suficientes para depurar um problema de memória. Portanto, use uma das outras formas de levantamento de informações.