Otimizar o uso da memória

A otimização de memória é crucial para garantir um bom desempenho, evitar falhas no app e manter a estabilidade do sistema e a integridade da plataforma. Embora o uso de memória precise ser monitorado e otimizado em todos os apps, os dispositivos de apps de conteúdo para TV têm desafios específicos que diferem dos apps Android típicos para dispositivos portáteis.

O alto consumo de memória pode causar problemas com o comportamento do app e do sistema, incluindo:

  • O próprio app pode ficar lento ou com atrasos, ou, na pior das hipóteses, ser interrompido.
  • Os serviços do sistema visíveis para o usuário (controle de volume, painel de configurações de imagem, assistente de voz etc.) ficam muito lentos ou podem não funcionar.
  • Os componentes do sistema podem ser encerrados e reiniciados, acionando picos de contenção extrema de recursos e afetando diretamente o app em primeiro plano.
  • A transição para o inicializador pode ser significativamente atrasada, deixando o app em primeiro plano sem resposta até que a transição seja concluída.
  • O sistema pode entrar em uma situação de recuperação direta, pausando temporariamente a execução de uma linha de execução enquanto aguarda a alocação de memória. Isso pode acontecer com qualquer linha de execução, como a principal ou as linhas de execução relacionadas ao codec, potencialmente causando quedas de frames de áudio e vídeo e falhas na interface.

Considerações sobre a memória em dispositivos de TV

Dispositivos de TV geralmente têm menos memória do que smartphones ou tablets. Por exemplo, uma configuração que podemos ver na TV é 1 GB de RAM e resolução de vídeo 1080p. Ao mesmo tempo, a maioria dos apps de TV tem recursos semelhantes, portanto, implementação semelhante e desafios comuns. Essas duas situações apresentam problemas que não são vistos em outros tipos de dispositivos e apps:

  • Os apps de mídia para TV geralmente são compostos por visualizações de imagem em grade e imagens de plano de fundo em tela cheia, que exigem o carregamento de muitas imagens na memória em um curto período.
  • Os apps de TV reproduzem transmissões multimídia, que exigem a alocação de uma certa quantidade de memória para reproduzir vídeo e áudio e precisam de buffers de mídia consideráveis para garantir uma reprodução suave.
  • Outros recursos de mídia (busca, mudança de episódio, mudança de faixa de áudio etc.) podem aumentar a pressão na memória se não forem implementados corretamente.

Entender os dispositivos de TV

Este guia se concentra principalmente no uso de memória do app e nas metas de memória para dispositivos com pouca RAM.

Em dispositivos de TV, considere estas características:

  • Memória do dispositivo: a quantidade de memória de acesso aleatório (RAM, na sigla em inglês) instalada no dispositivo.
  • Resolução da interface do dispositivo: a resolução que o dispositivo usa para renderizar a interface do SO e dos aplicativos. Normalmente, é menor que a resolução de vídeo do dispositivo.
  • Resolução do vídeo: a resolução máxima em que o dispositivo pode reproduzir vídeos.

Isso leva à categorização de diferentes tipos de dispositivos e como a memória deve ser usada por eles.

Resumo dos dispositivos de TV

Memória do dispositivo Resolução de vídeo do dispositivo Resolução da interface do dispositivo isLowRAMDevice()
1 GB 1080p 720p Sim
1,5 GB 2160p 1080p Sim
≥1,5 GB 1080p 720p ou 1080p Não*
≥2 GB 2160p 1080p Não*

Dispositivos de TV com pouca memória RAM

Esses dispositivos estão em uma situação de restrição de memória e vão informar ActivityManager.isLowRAMDevice() como verdadeiro. Os aplicativos que são executados em dispositivos de TV com pouca RAM precisam implementar outras medidas de controle de memória.

Consideramos que os dispositivos com as seguintes características se encaixam nessa categoria:

  • Dispositivos de 1 GB: 1 GB de RAM, resolução da interface de 720p/HD (1280 x 720), resolução de vídeo de 1080p/FullHD (1920 x 1080)
  • Dispositivos de 1,5 GB: 1,5 GB de RAM, resolução da interface 1080p/FullHD (1920 x 1080), resolução de vídeo 2160p/UltraHD/4K (3840 x 2160)
  • Outras situações em que o OEM definiu a flag ActivityManager.isLowRAMDevice() devido a outras restrições de memória.

Dispositivos de TV comuns

Esses dispositivos não sofrem uma pressão tão significativa na memória. Consideramos que esses dispositivos têm as seguintes características:

  • ≥1,5 GB de RAM, interface de 720p ou 1080p e resolução de vídeo de 1080p
  • ≥2 GB de RAM, interface de 1080p e resolução de vídeo de 1080p ou 2160p

Isso não significa que os apps não precisam se preocupar com o uso da memória nesses dispositivos, já que alguns casos específicos de uso indevido ainda podem esgotar a memória disponível e prejudicar o desempenho.

Metas de memória em dispositivos de TV com pouca RAM

Ao medir a memória nesses dispositivos, recomendamos monitorar cada seção da memória usando o Memory Profiler do Android Studio. Os apps de TV precisam criar um perfil do uso de memória e trabalhar para colocar as categorias abaixo dos limites definidos nesta seção.

Memory Profiler

Na seção Como a memória é contada, você vai encontrar uma explicação detalhada dos números de memória informados. Para a definição de limites para apps para TV, vamos nos concentrar em três categorias de memória:

  • Anônimo + troca: composto por Java + nativo + memória de alocação de pilha no Android Studio.
  • Gráficos: são informados diretamente na ferramenta de análise. Geralmente composto por texturas gráficas.
  • Arquivo: relatado como categorias "Code" e "Others" no Android Studio.

Com essas definições, a tabela a seguir indica o valor máximo que cada tipo de grupo de memória deve usar:

Tipo de memória Finalidade Metas de uso (1 GB)
Anônimo + troca (Java + nativo + pilha) Usado para alocações, buffers de mídia, variáveis e outras tarefas que exigem muita memória. < 160 MB
Gráficos Usado pela GPU para texturas e buffers relacionados à exibição 30 a 40 MB
Arquivo Usado para páginas de código e arquivos na memória. 60 a 80 MB

A memória total máxima (Anon+Swap + Graphics + File) não pode exceder o seguinte:

  • 280 MB de uso total de memória (Anon+Swap + Graphics + File) para dispositivos com 1 GB de RAM.

É altamente recomendado não exceder:

  • 200 MB de uso de memória em (Anon+Swap + Graphics).

Memória de arquivos

Como orientação geral para a memória de backup de arquivos, observe o seguinte:

  • Em geral, a memória de arquivos é bem gerenciada pelo gerenciamento de memória do SO.
  • Não achamos que ele seja uma das principais causas de pressão de memória no momento.

No entanto, ao lidar com a memória de arquivo em geral:

  • Não inclua bibliotecas não utilizadas no build e use pequenos subconjuntos de bibliotecas em vez de completos, quando possível.
  • Não mantenha arquivos grandes abertos na memória e libere-os assim que terminar de usá-los.
  • Minimize o tamanho do código compilado para classes Java e Kotlin. Consulte o guia Reduzir, ofuscar e otimizar o app.

Recomendações específicas de TV

Esta seção fornece recomendações específicas para otimizar o uso da memória em dispositivos de TV.

Memória gráfica

Use formatos e resoluções de imagem adequados.

  • Não carregue imagens com resolução maior que a da interface do dispositivo. Por exemplo, as imagens de 1080p precisam ser reduzidas para 720p em um dispositivo de interface de 720p.
  • Use bitmaps com suporte de hardware sempre que possível.
    • Em bibliotecas como o Glide, ative o recurso Downsampler.ALLOW_HARDWARE_CONFIG, que é desativado por padrão. Ativar isso evita a duplicação de bitmaps que, de outra forma, estariam na memória gráfica e anônima.
  • Evite renderizações intermediárias e re-renderizações
    • Eles podem ser identificados com o Android GPU Inspector:
    • Procure na seção "Textures" imagens que são etapas para a renderização final, em vez de serem apenas os elementos que as formam. Isso é comumente chamado de "renderização intermediária".
    • Para aplicativos do SDK do Android, geralmente é possível remover esses elementos usando a flag de layout forceHasOverlappedRendering:false para desativar renderizações intermediárias para esse layout.
    • Consulte Evitar renderizações sobrepostas para saber mais sobre renderizações sobrepostas.
  • Evite carregar imagens de marcador de posição sempre que possível. Use @android:color/ ou @color para texturas de marcador de posição.
  • Evite compor várias imagens no dispositivo quando a composição poder ser realizada off-line. Prefere carregar imagens independentes em vez de fazer a composição de imagens baixadas
  • Siga o guia Como gerenciar bitmaps para lidar melhor com eles.

Memória Anon+Swap

Anon+Swap é composto por alocações nativas + Java + pilha no Memory Profiler do Android Studio. Use ActivityManager.isLowMemoryDevice() para verificar se o dispositivo tem restrição de memória e se adaptar a essa situação seguindo estas diretrizes.

  • Mídia:
    • Especifique um tamanho variável para buffers de mídia, dependendo da RAM do dispositivo e da resolução de reprodução de vídeo. Isso deve representar 1 minuto de reprodução de vídeo:
      1. 40 a 60 MB para 1 GB / 1080p
      2. 60 a 80 MB para 1,5 GB / 1080p
      3. 80 a 100 MB para 1,5 GB / 2160p
      4. 100 a 120 MB para 2 GB / 2160p
    • Alocações de memória de mídia sem custo financeiro ao mudar um episódio para evitar aumentos na quantidade total de memória anônima.
    • Libere e pare os recursos de mídia imediatamente quando o app for interrompido: use os callbacks de ciclo de vida da atividade para processar recursos de áudio e vídeo. Se você não for um app de áudio, interrompa a reprodução quando onStop() ocorrer nas atividades, salve todo o trabalho que você está realizando e defina os recursos para serem liberados. Para programar o trabalho que você vai precisar mais tarde. Consulte a seção Jobs e alarmes.
    • Preste atenção à memória do buffer ao procurar vídeos: os desenvolvedores geralmente alocam de 15 a 60 segundos de conteúdo futuro ao tentar ter o vídeo pronto para o usuário, mas isso cria uma sobrecarga de memória extra. Em geral, não use mais de 5 segundos de buffer futuro até que o usuário selecione a nova posição do vídeo. Se você precisar de mais tempo de pré-buffer durante a busca, faça o seguinte:
      • Aloque o buffer de busca com antecedência e reutilize-o.
      • O tamanho do buffer não pode ser maior que 15 a 25 MB, dependendo da memória do dispositivo.
  • Alocações:
    • Use as orientações de memória gráfica para garantir que você não duplique imagens na memória anônima
      • As imagens geralmente são os maiores usuários de memória, então a duplicação delas pode sobrecarregar o dispositivo. Isso é especialmente verdadeiro durante a navegação pesada de grade de imagens.
    • Liberar alocações descartando as referências ao mover telas: verifique se não há referências a bitmaps e objetos.
  • Bibliotecas:
    • Perfil de alocações de memória de bibliotecas ao adicionar novas, já que elas podem carregar outras bibliotecas, o que também pode fazer alocações e criar vinculações.
  • Redes:
    • Não execute chamadas de rede de bloqueio durante a inicialização do app. Elas atrasam o tempo de inicialização do aplicativo e criam sobrecarga de memória adicional na inicialização, em que a memória é particularmente limitada pela carga do app. Mostre uma tela de carregamento ou de apresentação primeiro e faça solicitações de rede assim que a interface estiver em vigor.

Vinculações

Os vínculos introduzem uma sobrecarga de memória adicional, porque levam outros aplicativos à memória ou aumentam o consumo de memória do app vinculado (se ele já estiver na memória) para facilitar a chamada de API. Isso reduz a memória disponível para o app em primeiro plano. Ao vincular um serviço, observe quando e por quanto tempo você está usando a vinculação. Libere a vinculação assim que ela não for mais necessária.

Vinculações típicas e práticas recomendadas:

  • API Play Integrity: usada para verificar a integridade do dispositivo
    • Verificar a integridade do dispositivo após a tela de carregamento e antes da reprodução de mídia
    • Libere referências a PlayIntegrity StandardIntegrityManager antes de reproduzir o conteúdo.
  • Biblioteca do Play Faturamento: usada para gerenciar as assinaturas e compras usando o Google Play
  • GMS FontsProvider
    • Prefira usar fontes independentes em dispositivos com pouca RAM em vez de usar o provedor de fontes, já que o download das fontes é caro e o FontsProvider vincula serviços para fazer isso.
  • Biblioteca Google Assistente: às vezes usada para pesquisa e pesquisa no app. Se possível, substitua essa biblioteca.
    • Para apps leanback: use a conversão de texto em fala do Gboard ou a biblioteca androidx.leanback.
      • Siga as diretrizes de pesquisa para implementar a pesquisa.
      • Observação: o leanback foi descontinuado, e os apps precisam migrar para o TV Compose.
    • Para apps do Compose:
      • Use a conversão de texto em fala do Gboard para implementar a pesquisa por voz.
    • Implemente o recurso Assistir a seguir para tornar o conteúdo de mídia do seu app detectável.

Serviços em primeiro plano

Os serviços em primeiro plano são um tipo especial de serviço vinculado a uma notificação. Essa notificação é mostrada na bandeja de notificações em smartphones e tablets, mas os dispositivos de TV não têm uma bandeja de notificações no mesmo sentido. Mesmo que os serviços em primeiro plano sejam úteis porque podem ser mantidos em execução enquanto o aplicativo está em segundo plano, os apps de TV precisam seguir estas diretrizes:

No Android TV e no Google TV, os serviços em primeiro plano só podem continuar em execução quando o usuário sai do app:

  • Para apps de áudio:os serviços em primeiro plano só podem continuar em execução quando o usuário sai do app para continuar tocando a faixa de áudio. O serviço precisa ser interrompido imediatamente após o término da reprodução de áudio.
  • Para qualquer outro app:todos os serviços em primeiro plano precisam ser interrompidos quando o usuário sai do app, porque não há uma notificação para informar ao usuário que o app ainda está em execução e consumindo recursos.
  • Para trabalhos em segundo plano, como atualizar recomendações ou Assistir a seguir, use WorkManager.

Jobs e alarmes

WorkManager é a API do Android mais recente para programar jobs recorrentes em segundo plano. O WorkManager vai usar o novo JobScheduler quando disponível (SDK 23+), e o AlarmManager antigo quando não estiver. Para conferir as práticas recomendadas para a execução de trabalhos programados na TV, siga estas recomendações:

  • Evite usar as APIs AlarmManager no SDK 23 ou mais recente, principalmente AlarmManager.set(), AlarmManager.setExact() e métodos semelhantes, porque elas não permitem que o sistema decida o momento certo de executar os jobs, por exemplo, quando o dispositivo está ocioso.
  • Em dispositivos com pouca RAM, evite executar jobs, a menos que seja estritamente necessário. Se necessário, use o WorkRequest do WorkManager apenas para atualizar as recomendações após a reprodução e tente fazer isso enquanto o app ainda estiver aberto.
  • Defina o Constraints do WorkManager para permitir que o sistema execute seus jobs no momento apropriado:

Kotlin

Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.build()

Java

Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.build()
  • Se você precisar executar jobs regularmente (por exemplo, para atualizar o Watch Next com base na atividade de visualização de conteúdo de um usuário no app em outro dispositivo), mantenha o uso de memória baixo, mantendo o consumo de memória do job abaixo de 30 MB.

Outras diretrizes gerais

As diretrizes a seguir fornecem informações gerais sobre o desenvolvimento de apps Android:

  • Minimize as alocações de objetos, otimize a reutilização de objetos e remova imediatamente todos os objetos não usados.
    • Não mantenha referências a objetos, especialmente bitmaps.
    • Evite usar System.gc() e chamadas de liberação direta de memória porque elas interferem no processo de gerenciamento de memória do sistema. Por exemplo, em dispositivos que usam zRAM, uma chamada forçada para gc() pode aumentar temporariamente o uso de memória devido à compactação e descompactação da memória.
    • Use LazyList, como demonstrado em um navegador de catálogo no Compose ou RecyclerView no kit de ferramentas da interface Leanback, que agora está descontinuado, para reutilizar visualizações e não recriar elementos de lista.
    • Armazenar em cache elementos locais lidos de provedores de conteúdo externos que provavelmente não vão mudar e definir intervalos de atualização que impedem a alocação de memória externa adicional.
  • Verifique possíveis vazamentos de memória.
    • Cuidado com casos típicos de vazamento de memória, como referências em linhas de execução anônimas, realocações de buffers de vídeo que nunca são liberadas e outras situações semelhantes.
    • Use o heap dump para depurar vazamentos de memória.
  • Gere perfis de referência para minimizar a quantidade de compilação just-in-time necessária ao executar o app em uma inicialização a frio.

Resumo das ferramentas