Gerenciar a memória em jogos de forma eficiente

Na plataforma Android, o sistema tenta usar o máximo de memória do sistema (RAM, na sigla em inglês) possível e executa várias otimizações de memória para liberar espaço quando necessário. Essas otimizações podem ter um efeito negativo no jogo, na lentidão ou à disposição total. Saiba mais sobre essas otimizações no tópico Alocação de memória entre processos.

Para garantir a estabilidade do dispositivo, a partir do Android 17 (nível 37 da API), o sistema começa a aplicar limites de memória do app com base na RAM total do dispositivo. Se um app exceder esses limites, o Android vai encerrar o processo sem um stack trace associado.

Esta página explica as etapas que você pode seguir para evitar que condições de pouca memória afetem seu jogo.

Responda ao onTrimMemory()

O sistema usa onTrimMemory() para notificar seu app sobre eventos de ciclo de vida que apresentam uma boa oportunidade para que seu app reduza voluntariamente o uso da memória e evite ser encerrado pelo LMK para liberar memória para outros apps.

Se o app for encerrado em segundo plano, na próxima vez que o usuário iniciar o app, ele vai passar por uma inicialização a frio lenta . É menos provável que os apps que reduzem o uso da memória ao passar para o segundo plano sejam encerrados.

Ao responder a eventos de corte, é melhor liberar grandes alocações de memória que não são necessárias imediatamente e podem ser reconstruídas sob demanda. Por exemplo, se o app tiver um cache de bitmaps decodificados de imagens compactadas armazenadas localmente, geralmente é uma boa ideia cortar ou limpar este cache em resposta a TRIM_MEMORY_UI_HIDDEN.

Kotlin

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
    override fun onTrimMemory(level: Int) {
        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }
        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Java

public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
    public void onTrimMemory(int level) {
        switch (level) {
            if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
                // Release memory related to UI elements, such as bitmap caches.
            }
            if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
                // Release memory related to background processing, such as by
                // closing a database connection.
            }
        }
    }
}

Conservar a capacidade da memória

Gerencie a memória de maneira conservadora para evitar a falta de memória. Veja alguns itens a serem considerados:

  • Tamanho da RAM física: os jogos geralmente usam entre ¼ e ½ do valor da RAM física no dispositivo.
  • Tamanho máximo da zRAM: mais zRAM significa que o jogo pode ter mais memória para alocar. Esse valor pode variar de acordo com o dispositivo. Procure SwapTotal em /proc/meminfo para encontrar esse valor.
  • Uso da memória do SO: os dispositivos que designam mais RAM para os processos do sistema deixam menos memória para o jogo. O sistema encerra o processo do jogo antes de encerrar processos do sistema.
  • Uso de memória dos apps instalados: teste seu jogo em dispositivos que têm muitos apps instalados. Os apps de redes sociais e chat precisam ser executados constantemente e afetar a quantidade de memória livre.

Se não for possível usar uma capacidade de memória conservadora, tente uma abordagem mais flexível. Se o sistema tiver problemas de pouca memória, reduza a quantidade de memória que o jogo está usando. Por exemplo, aloque texturas de baixa resolução ou armazene menos sombreadores em resposta a onTrimMemory(). Essa abordagem dinâmica para a alocação de memória requer mais trabalho do desenvolvedor, especialmente na fase de design do jogo.

Evite a sobrecarga

A sobrecarga ocorre quando a memória livre é baixa, mas não o suficiente para encerrar o jogo. Nessa situação, kswapd recuperou páginas de que o jogo ainda precisa, por isso, tenta recarregar as páginas da memória. Não há espaço suficiente, então as páginas continuam sendo trocadas (troca contínua). O rastreamento do sistema informa essa situação como uma linha de execução em que kswapd é executado continuamente.

Um sintoma de sobrecarga é um tempo longo para a renderização do frame, possivelmente um segundo ou mais. Reduza o consumo de memória do jogo para resolver essa situação.

Use as ferramentas disponíveis

O Android tem uma coleção de ferramentas que ajudam a entender como o sistema gerencia a memória.

Meminfo

Essa ferramenta coleta estatísticas sobre a memória para mostrar quanta memória PSS foi alocada e as categorias para as quais ela foi usada.

Veja as estatísticas do meminfo de uma das seguintes maneiras:

  • Use o comando adb shell dumpsys meminfo package-name;
  • Use a chamada MemoryInfo da API Android Debug.

A estatística PrivateDirty mostra a quantidade de RAM dentro do processo que não pode ser paginada no disco e não é compartilhada com outros processos. A maior parte desse valor fica disponível para o sistema quando esse processo é encerrado.

Tracepoints de memória

Os tracepoints de memória rastreiam a quantidade de memória RSS que seu jogo está usando. Calcular o uso de memória RSS é muito mais rápido do que calcular o uso do PSS. Como é mais rápido de calcular, o RSS mostra uma granularidade mais refinada nas alterações no tamanho da memória para medições mais precisas do uso de memória de pico. Portanto, é mais fácil observar picos que possam fazer com que o jogo fique sem memória.

Perfetto e rastreamentos longos

O Perfetto (link em inglês) é um pacote de ferramentas para coletar informações de desempenho e memória de um dispositivo e exibi-lo em uma IU baseada na Web. É compatível com rastreamentos arbitrariamente longos para que você possa visualizar como o RSS muda ao longo do tempo. Também é possível emitir consultas SQL dos dados produzidos para processamento off-line. Ative os rastreamentos longos no app Rastreamento do sistema. Verifique se a categoria memory:Memory está ativada para o rastreamento. Para instrumentação de memória personalizada no desenvolvimento e teste, também é possível usar a API (Beta)heapprofd.

Heapprofd

heapprofd é uma ferramenta de rastreamento de memória que faz parte do Perfetto. Essa ferramenta pode ajudar a encontrar vazamentos de memória ao mostrar onde a memória foi alocada usando malloc. O heapprofd pode ser iniciado usando um script Python. Como a ferramenta tem baixa sobrecarga, isso não afeta o desempenho como outras ferramentas, como a Malloc Debug.

Relatório de bugs

bugreport é uma ferramenta de geração de registros para descobrir se o jogo falhou porque ficou sem memória. A saída da ferramenta é muito mais detalhada do que a do logcat. Ele é útil para depuração de memória porque mostra se o jogo falhou por falta de memória ou se foi encerrado pelo LMK.

Para mais informações, consulte Capturar e ler relatórios de bugs.