Armazenamento em cache de apps

O Android 11 (nível 30 da API) ou versões mais recentes oferecem suporte ao congelador de apps em cache. Esse recurso interrompe a execução de processos em cache e reduz o uso de recursos por apps com comportamento inadequado que podem tentar operar enquanto estão em cache.

O congelador de apps em cache mantém os apps na RAM sem usar a CPU. Se o Android determinar que um app não deve estar fazendo trabalho, mas pode ser necessário no futuro, ele congela o processo do app em vez de encerrá-lo. Isso evita uma inicialização a frio quando o app for necessário novamente.

O Android congela os apps em cache migrando os processos deles para um cgroup congelado. Isso reduz o consumo ativo e ocioso da CPU na presença de apps ativos em cache. É possível ativar o congelador de apps usando uma flag de configuração do sistema ou uma opção de desenvolvedor.

No Android 14 (nível 34 da API) e versões mais recentes, o congelador de apps em cache inclui os seguintes comportamentos robustos:

  • Os processos de apps no estado armazenado em cache são congelados 10 segundos depois de entrar nesse estado.
  • O sistema descongelará imediatamente um processo de app congelado durante um evento do ciclo de vida. Esses eventos incluem o recebimento de uma intent, o início de um serviço de job ou a retomada de uma atividade pelo usuário.

O ActivityManagerService gerencia todos os processos do app e toma decisões sobre o ciclo de vida dele. O CachedAppOptimizer é responsável por congelar o processo do app.

Quando um processo de app é congelado, todas as linhas de execução são suspensas e não podem realizar trabalho da CPU até serem descongeladas. Como resultado, o app não pode realizar a coleta de lixo (GC, na sigla em inglês) e não pode responder a eventos de redução de memória. Para ver mais informações, consulte ComponentCallbacks2.onTrimMemory(int). Para acomodar isso, a partir do Android 14:

  • Os apps com uma instância Activity visível são notificados sobre TRIM_MEMORY_UI_HIDDEN assim que passam para segundo plano. Apps que permanecem em um ciclo de vida sem uma interface, como apps com um serviço em primeiro plano, podem receber TRIM_MEMORY_BACKGROUND. Outros eventos de corte<br>não são entregues, porque quando os apps são qualificados para esses eventos, espera-se que eles sejam<br>congelados.
  • Logo após entrar no estado armazenado em cache, o sistema pode solicitar que o tempo de execução do app realize uma GC em preparação para um possível congelamento.
  • Quando um processo de app é congelado, outras etapas de compactação de memória podem ocorrer, como gravar páginas sujas no armazenamento de apoio e trocar páginas anônimas para a ZRAM.
  • Se todos os processos de um app específico forem congelados, o sistema vai encerrar todos os sockets TCP ativos mantidos pelo app. Isso impede que o lado do servidor do socket envie pings de manutenção TCP que ativariam o modem do dispositivo.

Os processos de apps em cache são descongelados quando o estado deles passa de em cache para um estado de importância maior. Para reduzir eventos de descongelamento no Android 14 e em versões mais recentes, o sistema enfileira transmissões registradas em contexto enquanto o app está no estado em cache. As transmissões registradas em contexto são receptores que um app registra dinamicamente chamando Context.registerReceiver. O sistema entrega essas transmissões enfileiradas somente depois que o app é descongelado. Em contraste, o sistema não enfileira transmissões declaradas no manifesto. As transmissões declaradas no manifesto são receptores declarados de forma estática em AndroidManifest.xml usando o elemento <receiver>. O sistema imediatamente descongela o app em cache para enviar transmissões declaradas no manifesto.

Impacto na integridade do sistema

O Android encerra o processo de app em cache usado menos recentemente se houver mais de MAX_CACHED_PROCESSES processos de app em cache. Em dispositivos compatíveis com o Android 14 ou versões mais recentes, o MAX_CACHED_PROCESSES aumenta significativamente, permitindo que os dispositivos mantenham muito mais processos de apps em cache na RAM.

Manter mais apps armazenados em cache na RAM resulta em uma redução de até 30% nas inicializações a frio, com reduções dimensionadas com base na RAM total do dispositivo. Ao mesmo tempo, o consumo de CPU pelos apps em cache é minimizado, o que gera uma economia significativa de bateria.

Isenções de freezer

Em determinadas condições, um processo de app pode entrar no estado armazenado em cache, mas permanecer descongelado. Essas isenções são detalhes de implementação e podem mudar em versões futuras do Android:

  • Bloqueios de arquivo: se um processo em cache tiver um bloqueio de arquivo que impeça outros processos não armazenados em cache, o processo que mantém o bloqueio não será congelado.
  • Vinculações BIND_WAIVE_PRIORITY: processos de apps com vinculações de entrada criadas usando Context.BIND_WAIVE_PRIORITY podem entrar no estado em cache, mas permanecer descongelados até que todos os processos de cliente conectados também sejam armazenados em cache. Essa isenção é compatível com apps multiprocesso, como navegadores da Web que usam guias personalizadas.

Implementar o freezer de apps

O freezer de apps em cache usa o freezer cgroup v2 do kernel. Os dispositivos enviados com um kernel compatível podem ativá-lo. Ative a opção de desenvolvedor Suspender a execução de apps em cache ou defina a flag de configuração do dispositivo activity_manager_native_boot use_freezer como true. Exemplo:

adb shell device_config put activity_manager_native_boot use_freezer true && adb reboot

O freezer é desativado quando você define a flag use_freezer como false ou desativa a opção do desenvolvedor. Exemplo:

adb shell device_config put activity_manager_native_boot use_freezer false && adb reboot

É possível ativar ou desativar essa configuração mudando a configuração de um dispositivo em uma versão ou atualização de software.

Para substituir MAX_CACHED_PROCESSES, por exemplo, para definir o valor como 1024 para teste:

adb shell device_config put activity_manager max_cached_processes 1024
adb shell device_config set_sync_disabled_for_tests persistent

Para reverter a substituição de MAX_CACHED_PROCESSES:

adb shell device_config delete activity_manager max_cached_processes
adb shell device_config set_sync_disabled_for_tests none

No Android 16 (nível 36 da API) e versões mais recentes, o Android oferece APIs públicas oficiais, como IBinder.FrozenStateChangeCallback e IBinder.addFrozenStateChangeCallback, para observar quando processos remotos são congelados ou descongelados. Os componentes que interagem com apps que podem ser armazenados em cache podem usar essas APIs para rastrear o estado congelado de processos remotos.

Requisitos de dispositivo e kernel

O congelador de apps em cache exige suporte ao cgroup v2 do kernel. Além disso, as notificações de mudança de estado de congelamento usando IBinder.FrozenStateChangeCallback exigem suporte ao driver de vinculação do kernel, que é padrão nos kernels comuns do Android (ACK) e nas imagens genéricas do kernel (GKI) a partir do Android 14 (nível da API 34) e versões mais recentes.

É possível verificar se um dispositivo oferece suporte a esses recursos usando comandos padrão do adb, da seguinte maneira:

  • Verifique se o freezer está ativado no dispositivo (para builds de usuário ou de depuração):

    adb shell device_config get activity_manager_native_boot use_freezer

    Ou verifique se os processos estão sendo congelados ativamente pelo sistema:

    adb shell dumpsys activity | grep -A 20 "Apps frozen:"
  • Verifique o suporte do controlador de congelamento do cgroup v2 (para qualquer dispositivo):

    Verifique se freezer está listado entre os controladores cgroup v2 disponíveis:

    adb shell cat /sys/fs/cgroup/cgroup.controllers

    Como alternativa, em um dispositivo com acesso root ou build userdebug, verifique se o nó freezer do cgroup v2 está montado em um cgroup filho:

    adb root && adb shell ls /sys/fs/cgroup/uid_0/cgroup.freeze

    Se esse arquivo existir, o kernel vai oferecer suporte ao congelador cgroup v2.

  • Verificar a compatibilidade com notificações de mudança de estado de congelamento (para qualquer dispositivo):

    Em dispositivos com Android 14 e versões mais recentes com drivers de binder de imagem genérica do kernel (GKI) compatíveis, o IBinder.addFrozenStateChangeCallback registra callbacks com sucesso. Se o driver binder do kernel subjacente não for compatível com notificações de congelamento, o método vai gerar UnsupportedOperationException.

Processar recursos personalizados

Não é esperado que os processos de apps façam qualquer trabalho quando armazenados em cache, mas alguns apps podem ter recursos personalizados compatíveis com processos que precisam ser executados enquanto estão em cache. Quando o congelador de apps é ativado em um dispositivo que executa esses apps, os processos em cache são congelados e podem impedir que recursos personalizados funcionem.

Como solução alternativa, mude o status do processo para "não armazenado em cache" antes que ele precise fazer qualquer trabalho. Essa mudança permite que os apps permaneçam ativos. Exemplos de status ativos incluem um serviço em primeiro plano vinculado ou um status em primeiro plano.

Modos de falha comuns

Quando os processos do app são congelados, a comunicação entre processos (IPC) ou o agendamento de tarefas inadequados podem levar a encerramentos de apps ou comportamentos inesperados.

Transações de binder síncronas para processos congelados

Quando um processo de app cliente envia uma transação de binder síncrona para um processo de app servidor congelado, o sistema encerra imediatamente o processo de app servidor. Isso evita que a linha de execução do cliente fique bloqueada indefinidamente enquanto aguarda uma resposta do servidor congelado. Em seguida, a linha de execução do cliente recebe RemoteException, e todos os listeners registrados são acionados. Veja mais informações em IBinder.linkToDeath.

Causa principal: geralmente, essa falha é causada por um bug no app cliente. Quando um cliente se vincula a um serviço, o processo do servidor também se vincula ao cliente e é impedido de entrar no estado em cache antes do cliente. Para mais informações, consulte Context.bindService. No entanto, quando o cliente chama Context.unbindService, o processo do servidor pode ser armazenado em cache e congelado. Se o cliente continuar usando a referência IBinder em cache após a desvinculação, ele corre o risco de se comunicar com um processo congelado.

Para evitar esse problema, verifique se os apps clientes descartam as referências IBinder imediatamente após chamar Context.unbindService.

Gerenciar callbacks remotos para processos do cliente

Serviços e componentes do sistema que mantêm callbacks de binder de longa duração para processos do cliente podem evitar falhas síncronas e estouros de buffer assíncronos rastreando o estado congelado do cliente:

  • Registre um listener de mudança de estado: use IBinder.addFrozenStateChangeCallback em tokens de binder de cliente recebidos para receber notificações quando o processo do cliente entrar ou sair do freezer.
  • Pausar o envio durante o congelamento: quando um cliente entra em STATE_FROZEN, pause o envio de callbacks ou atualizações de estado para esse cliente.
  • Retomar e entregar após o descongelamento: quando o cliente faz a transição para STATE_UNFROZEN, retome o envio de callbacks e entregue as atualizações em lote ou consolidadas necessárias.
  • Usar RemoteCallbackList: os serviços do sistema que usam RemoteCallbackList podem configurar políticas de recebedor da chamada congelado para pausar e retomar automaticamente o envio de callbacks sem manter a lógica de rastreamento manual. Para mais informações, consulte Recomendações de congelamento de vinculadores para serviços do sistema.

Estouro de buffer de transação de binder assíncrono

Quando um processo de app de servidor recebe transações de binder assíncronas (oneway) enquanto está congelado, as transações são armazenadas em buffer por processo. Se o servidor receber muitas transações assíncronas enquanto estiver congelado, o buffer vai transbordar, e o sistema vai encerrar o processo do app do servidor.

Para evitar esse estouro de buffer, não envie transações de binder assíncronas em excesso para processos que possam ser armazenados em cache ou congelados.

Execução repetida de tarefas programadas após o descongelamento

Se um app executar tarefas repetitivas, elas serão suspensas enquanto o processo estiver congelado. Para mais informações, consulte ScheduledThreadPoolExecutor.scheduleAtFixedRate ou Timer.scheduleAtFixedRate. Quando o processo é descongelado, as execuções perdidas acumuladas podem ser executadas rapidamente, uma após a outra, sem praticamente nenhum atraso.

Para evitar um aumento repentino de execuções quando o app for descongelado, use scheduleWithFixedDelay em vez de scheduleAtFixedRate para tarefas em segundo plano. Você também pode usar o WorkManager.

Testar e resolver problemas do freezer de apps

Para verificar se o app freezer está funcionando conforme o esperado ou para resolver problemas relacionados a ele, use as seguintes ferramentas e comandos de diagnóstico:

Comandos do gerenciador de atividades

É possível usar comandos adb shell am para controlar manualmente o congelamento e a compactação de um processo específico:

  • Forçar o congelamento de um processo:

    adb shell am freeze <process>
  • Forçar o descongelamento de um processo:

    adb shell am unfreeze <process>
  • Forçar uma compactação completa da memória em um processo:

    adb shell am compact full <process>

Análise do Logcat

Confira o logcat para ver entradas congeladas e descongeladas sempre que um processo migra para dentro ou para fora do freezer:

adb logcat | grep -i "\(freezing\|froze\)"

Os registros de motivo de descongelamento geram valores enumerados do tipo enumerado de buffer de protocolo UnfreezeReason.

Inspeção do dumpsys

Verifique uma lista de processos congelados usando dumpsys activity:

adb shell dumpsys activity | grep -A 20 "Apps frozen:"

Verifique se o arquivo /sys/fs/cgroup/uid_0/cgroup.freeze está presente.

ApplicationExitInfo

Para consultar o motivo do encerramento de um processo anterior, consulte ActivityManager.getHistoricalProcessExitReasons. Se um processo de app for encerrado devido a um problema relacionado ao freezer, como receber uma transação de binder síncrona enquanto estiver congelado, o motivo da saída será definido como ApplicationExitInfo.REASON_FREEZER.

Rastreamento do Perfetto

Os eventos relacionados ao freezer são emitidos para uma faixa chamada Freezer no processo system_server nos rastreamentos do Perfetto:

  • As fatias Freeze e Unfreeze indicam quando um processo muda de estado.
  • Os eventos updateAppFreezeStateLSP mostram quando o servidor do sistema reexamina atributos de processo para tomar decisões de congelamento ou descongelamento.

É possível inspecionar esses eventos diretamente na interface do Perfetto ou analisá-los usando o PerfettoSQL:

INCLUDE PERFETTO MODULE slices.with_context;
SELECT *
FROM process_slice
WHERE process_name = "system_server"
AND track_name = "Freezer"
AND (name LIKE "Freeze %" OR name LIKE "Unfreeze %");

Na biblioteca padrão do PerfettoSQL, os eventos de congelamento também são resumidos na tabela android_freezer_events.