Congelador de apps almacenadas en caché

Android 11 (nivel de API 30) y versiones posteriores admiten el congelador de apps en caché. Esta función detiene la ejecución de los procesos almacenados en caché y reduce el uso de recursos por parte de las apps que se comportan de forma incorrecta y que podrían intentar operar mientras están almacenadas en caché.

El congelador de apps en caché mantiene las apps en la RAM y las quita de la CPU. Si Android determina que una app no debería realizar trabajo, pero podría ser necesaria en el futuro, congela el proceso de la app en lugar de finalizarlo. Esto evita un inicio en frío cuando se vuelve a necesitar la app.

Android congela las apps almacenadas en caché migrando sus procesos a un cgroup congelado. Esto reduce el consumo de CPU activa y en inactividad en presencia de apps activas en caché. Puedes habilitar el congelador de apps con una marca de configuración del sistema o una opción para desarrolladores.

En Android 14 (nivel de API 34) y versiones posteriores, el congelador de apps en caché incluye los siguientes comportamientos sólidos:

  • Los procesos de la app en estado almacenado en caché se congelan 10 segundos después de entrar en ese estado.
  • El sistema descongela de inmediato un proceso de app congelado durante un evento del ciclo de vida. Estos eventos incluyen la recepción de un intent, el inicio de un servicio de trabajo o la reanudación de una actividad por parte del usuario.

ActivityManagerService administra todos los procesos de la app y toma decisiones sobre el ciclo de vida de la app. CachedAppOptimizer es responsable de congelar el proceso de la app.

Cuando se inmoviliza un proceso de la app, se suspenden todos sus subprocesos y no pueden realizar trabajo de CPU hasta que se descongelen. Como resultado, la app no puede realizar la recolección de basura (GC) ni responder a los eventos de reducción de memoria. Para obtener más información, consulta ComponentCallbacks2.onTrimMemory(int). Para adaptarse a este cambio, a partir de Android 14, se implementaron las siguientes medidas:

  • Las apps con una instancia de Activity visible reciben una notificación de TRIM_MEMORY_UI_HIDDEN en cuanto pasan a segundo plano. Las apps que permanecen en un ciclo de vida sin una IU, como las apps con un servicio en primer plano, pueden recibir TRIM_MEMORY_BACKGROUND. No se entregan otros eventos de recorte, ya que se espera que las apps estén congeladas cuando sean aptas para esos eventos.
  • Poco después de entrar en el estado almacenado en caché, es posible que el sistema solicite al tiempo de ejecución de la app que realice una GC en preparación para una posible congelación.
  • Cuando se inmoviliza un proceso de la app, pueden ocurrir pasos adicionales de compactación de memoria, como escribir páginas no sincronizadas en el almacenamiento de respaldo y cambiar páginas anónimas a ZRAM.
  • Si se congelan todos los procesos de una app en particular, el sistema finaliza todos los sockets TCP activos que mantiene la app. Esto evita que el servidor del socket envíe pings de TCP keepalive que activarían el módem del dispositivo.

Los procesos de la app almacenados en caché se descongelan cuando su estado de proceso se eleva de almacenado en caché a un estado de mayor importancia. Para reducir los eventos de descongelamiento en Android 14 y versiones posteriores, el sistema pone en cola las transmisiones registradas en el contexto mientras la app está en el estado almacenado en caché. Las transmisiones registradas en el contexto son receptores que una app registra de forma dinámica llamando a Context.registerReceiver. El sistema entrega estas transmisiones en cola solo después de que se descongele la app. En cambio, el sistema no pone en cola las transmisiones declaradas en el manifiesto. Las transmisiones declaradas en el manifiesto son receptores declarados de forma estática en AndroidManifest.xml con el elemento <receiver>. El sistema descongela de inmediato la app almacenada en caché para entregar las transmisiones declaradas en el manifiesto.

Impacto en el estado del sistema

Android finaliza el proceso de la app almacenada en caché que se usó menos recientemente si hay más de MAX_CACHED_PROCESSES procesos de apps almacenados en caché. En los dispositivos compatibles que ejecutan Android 14 o versiones posteriores, MAX_CACHED_PROCESSES aumenta significativamente, lo que permite que los dispositivos mantengan muchos más procesos de apps almacenados en caché en la RAM.

Mantener más apps almacenadas en caché en la RAM genera una reducción de hasta el 30% en los inicios en frío, y las reducciones se ajustan según la RAM total del dispositivo. Al mismo tiempo, se minimiza el consumo de CPU de las apps almacenadas en caché, lo que genera ahorros significativos de batería.

Exenciones de congeladores

En ciertas condiciones, un proceso de la app puede entrar en el estado almacenado en caché, pero permanecer sin congelar. Estas exenciones son detalles de implementación y podrían cambiar en futuras versiones de Android:

  • Bloqueos de archivos: Si un proceso almacenado en caché mantiene un bloqueo de archivos que bloquea otros procesos no almacenados en caché, el proceso que mantiene el bloqueo no se congela.
  • Vinculaciones de BIND_WAIVE_PRIORITY: Los procesos de la app con vinculaciones entrantes creadas con Context.BIND_WAIVE_PRIORITY pueden ingresar al estado almacenado en caché, pero permanecen sin congelar hasta que también se almacenen en caché todos los procesos de cliente conectados. Esta exención admite apps de varios procesos, como los navegadores web que usan pestañas personalizadas.

Implementa el congelador de apps

El congelador de apps almacenadas en caché usa el congelador cgroup v2 del kernel. Los dispositivos que se envían con un kernel compatible pueden habilitarlo. Habilita la opción para desarrolladores Suspender ejecución de apps en caché o establece el parámetro de configuración del dispositivo activity_manager_native_boot use_freezer en true. Por ejemplo:

adb shell device_config put activity_manager_native_boot use_freezer true && adb reboot

El congelador se inhabilita cuando configuras la marca use_freezer en false o inhabilitas la opción para desarrolladores. Por ejemplo:

adb shell device_config put activity_manager_native_boot use_freezer false && adb reboot

Puedes activar o desactivar este parámetro de configuración cambiando la configuración del dispositivo en una versión o actualización de software.

Para anular MAX_CACHED_PROCESSES, por ejemplo, para establecer el valor en 1,024 para realizar pruebas, haz lo siguiente:

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

Para revertir la anulación de MAX_CACHED_PROCESSES, haz lo siguiente:

adb shell device_config delete activity_manager max_cached_processes
adb shell device_config set_sync_disabled_for_tests none

A partir de Android 16 (nivel de API 36) y versiones posteriores, Android proporciona APIs públicas oficiales, como IBinder.FrozenStateChangeCallback y IBinder.addFrozenStateChangeCallback, para observar cuándo se congelan o descongelan los procesos remotos. Los componentes que interactúan con apps que podrían almacenarse en caché pueden usar estas APIs para hacer un seguimiento del estado inactivo de los procesos remotos.

Requisitos del dispositivo y el kernel

El congelador de apps en caché requiere compatibilidad con cgroup v2 del kernel. Además, las notificaciones de cambio de estado de congelación con IBinder.FrozenStateChangeCallback requieren compatibilidad con el controlador del binder del kernel, que es estándar en los kernels comunes de Android (ACK) y las imágenes genéricas del kernel (GKI) a partir de Android 14 (nivel de API 34) y versiones posteriores.

Puedes verificar si un dispositivo admite estas capacidades con los comandos adb estándar, de la siguiente manera:

  • Verifica si el congelador está habilitado en el dispositivo (para compilaciones de usuario o de depuración):

    adb shell device_config get activity_manager_native_boot use_freezer

    También puedes verificar que el sistema esté congelando los procesos de forma activa:

    adb shell dumpsys activity | grep -A 20 "Apps frozen:"
  • Verifica la compatibilidad del controlador de congelación de cgroup v2 (para cualquier dispositivo):

    Verifica que freezer aparezca en la lista de controladores de cgroup v2 disponibles:

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

    Como alternativa, en un dispositivo con acceso de administrador o en una compilación de userdebug, verifica que el nodo de congelación de cgroup v2 esté montado en un cgroup secundario:

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

    Si este archivo existe, el kernel admite el congelador de cgroup v2.

  • Verifica la compatibilidad con las notificaciones de cambio de estado de congelación (para cualquier dispositivo):

    En dispositivos que ejecutan Android 14 y versiones posteriores con controladores de Binder de la imagen genérica del kernel (GKI) compatibles, IBinder.addFrozenStateChangeCallback registra las devoluciones de llamada correctamente. Si el controlador subyacente del kernel de Binder no admite notificaciones de congelamiento, el método arroja UnsupportedOperationException.

Cómo controlar funciones personalizadas

No se espera que los procesos de la app realicen ningún trabajo cuando se almacenan en caché, pero algunas apps pueden tener funciones personalizadas compatibles con procesos que se espera que se ejecuten mientras se almacenan en caché. Cuando el congelador de apps está habilitado en un dispositivo que ejecuta este tipo de apps, los procesos almacenados en caché se congelan y pueden impedir que funcionen las funciones personalizadas.

Como solución alternativa, puedes cambiar el estado del proceso a no almacenado en caché antes de que el proceso necesite realizar cualquier trabajo. Este cambio permite que las apps permanezcan activas. Entre los ejemplos de estados activos, se incluyen un servicio en primer plano vinculado o un estado en primer plano.

Modos de falla comunes

Cuando los procesos de la app se congelan, la comunicación entre procesos (IPC) o la programación de tareas inadecuadas pueden provocar la finalización de la app o un comportamiento inesperado.

Transacciones de vinculación síncronas a procesos congelados

Cuando un proceso de una app cliente envía una transacción de Binder síncrona a un proceso de una app de servidor que está inactivo, el sistema finaliza de inmediato el proceso de la app de servidor. Esto evita que el subproceso del cliente se bloquee de forma indefinida mientras espera una respuesta del servidor inactivo. Luego, el subproceso del cliente recibe RemoteException y se activan los objetos de escucha registrados. Para obtener más información, consulta: IBinder.linkToDeath.

Causa raíz: Por lo general, esta falla se debe a un error en la app cliente. Cuando un cliente se vincula a un servicio, el proceso del servidor se vincula al cliente y no puede ingresar al estado almacenado en caché antes de que lo haga el cliente. Para obtener más información, consulta Context.bindService. Sin embargo, una vez que el cliente llama a Context.unbindService, el proceso del servidor puede almacenarse en caché y congelarse. Si el cliente sigue usando la referencia IBinder almacenada en caché después de desvincularse, corre el riesgo de comunicarse con un proceso inactivo.

Para evitar este problema, asegúrate de que las apps cliente descarten las referencias de IBinder inmediatamente después de llamar a Context.unbindService.

Administra devoluciones de llamada remotas a procesos del cliente

Los servicios y los componentes del sistema que mantienen devoluciones de llamada de Binder de larga duración a los procesos del cliente pueden evitar fallas síncronas y desbordamientos asíncronos del búfer haciendo un seguimiento del estado inactivo del cliente:

  • Registra un objeto de escucha de cambios de estado: Usa IBinder.addFrozenStateChangeCallback en los tokens de vinculador del cliente entrantes para recibir notificaciones cuando el proceso del cliente entra o sale del congelador.
  • Pausar el envío mientras está inactivo: Cuando un cliente ingresa a STATE_FROZEN, pausa las devoluciones de llamada de envío o las actualizaciones de estado a ese cliente.
  • Resume and deliver upon unfreezing: Cuando el cliente pasa a STATE_UNFROZEN, se reanuda el envío de devoluciones de llamada y se entregan las actualizaciones necesarias por lotes o consolidadas.
  • Usa RemoteCallbackList: Los servicios del sistema que usan RemoteCallbackList pueden configurar políticas de destinatarios inmovilizados para pausar y reanudar automáticamente el envío de devoluciones de llamada sin mantener la lógica de seguimiento manual. Para obtener más información, consulta Recomendaciones del congelador de Binder para servicios del sistema.

Desbordamiento del búfer de transacciones del vinculador asíncrono

Cuando un proceso de app del servidor recibe transacciones de Binder asíncronas (oneway) mientras está inactivo, las transacciones se almacenan en un búfer por proceso. Si el servidor recibe demasiadas transacciones asíncronas mientras está inactivo, se desborda el búfer y el sistema finaliza el proceso de la app del servidor.

Para evitar este desbordamiento del búfer, no envíes transacciones de Binder asíncronas excesivas a procesos que podrían estar almacenados en caché o inactivos.

Ejecución repetida de tareas programadas al descongelar

Si una app ejecuta tareas repetitivas, estas se suspenden mientras el proceso está inactivo. Para obtener más información, consulta ScheduledThreadPoolExecutor.scheduleAtFixedRate o Timer.scheduleAtFixedRate. Cuando el proceso se desbloquea, las ejecuciones perdidas acumuladas pueden ejecutarse rápidamente de forma consecutiva sin prácticamente ninguna demora.

Para evitar una oleada de ejecuciones cuando la app se descongele, usa scheduleWithFixedDelay en lugar de scheduleAtFixedRate para las tareas en segundo plano. También puedes usar WorkManager.

Prueba y soluciona problemas del congelador de apps

Para verificar que el congelador de apps funcione según lo previsto o solucionar problemas relacionados con el congelador, usa las siguientes herramientas y comandos de diagnóstico:

Comandos del administrador de actividades

Puedes usar los comandos adb shell am para controlar manualmente la inmovilización y la compactación de un proceso específico:

  • Cómo forzar la detención de un proceso:

    adb shell am freeze <process>
  • Cómo forzar la reactivación de un proceso:

    adb shell am unfreeze <process>
  • Forzar una compactación de memoria completa en un proceso:

    adb shell am compact full <process>

Examen de Logcat

Consulta logcat para ver las entradas congeladas y descongeladas cada vez que un proceso migra dentro o fuera del congelador:

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

Los registros de motivos de descongelamiento generan valores enumerados del enum del búfer de protocolo UnfreezeReason.

Inspección de dumpsys

Verifica si hay una lista de procesos congelados con dumpsys activity:

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

Verifica la presencia del archivo /sys/fs/cgroup/uid_0/cgroup.freeze.

ApplicationExitInfo

Para consultar el motivo del cierre de un proceso anterior, consulta ActivityManager.getHistoricalProcessExitReasons. Si se finalizó un proceso de la app debido a un problema relacionado con el congelador, como recibir una transacción de Binder síncrona mientras está congelado, el motivo de salida se establece en ApplicationExitInfo.REASON_FREEZER.

Registro de Perfetto

Los eventos relacionados con el congelador se emiten a un registro llamado Freezer en el proceso system_server en los registros de Perfetto:

  • Los segmentos Freeze y Unfreeze indican cuándo cambia el estado de un proceso.
  • Los eventos updateAppFreezeStateLSP muestran cuándo el servidor del sistema vuelve a examinar los atributos del proceso para tomar decisiones de congelación o descongelación.

Puedes inspeccionar estos eventos directamente en la IU de Perfetto o analizarlos con 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 %");

En la biblioteca estándar de PerfettoSQL, los eventos de congelación también se resumen en la tabla android_freezer_events.