Congelatore delle app memorizzate nella cache

Android 11 (livello API 30) o versioni successive supporta il blocco delle app memorizzate nella cache. Questa funzionalità interrompe l'esecuzione per i processi memorizzati nella cache e riduce l'utilizzo delle risorse da parte di app che potrebbero tentare di funzionare mentre sono memorizzate nella cache.

Il congelatore delle app memorizzate nella cache mantiene le app nella RAM tenendole lontane dalla CPU. Se Android stabilisce che un'app non deve eseguire operazioni, ma potrebbe essere necessaria in futuro, blocca il processo dell'app anziché terminarlo. In questo modo si evita un avvio a freddo quando l'app è di nuovo necessaria.

Android blocca le app memorizzate nella cache eseguendo la migrazione dei relativi processi in un cgroup bloccato. In questo modo si riduce il consumo di CPU attivo e inattivo in presenza di app memorizzate nella cache attive. Puoi attivare il blocco delle app utilizzando un flag di configurazione del sistema o un'opzione sviluppatore.

In Android 14 (livello API 34) e versioni successive, il freezer per le app memorizzate nella cache include i seguenti comportamenti robusti:

  • I processi dell'app nello stato memorizzato nella cache vengono bloccati 10 secondi dopo l'inserimento nello stato memorizzato nella cache.
  • Il sistema scongela immediatamente un processo dell'app bloccato durante un evento del ciclo di vita. Questi eventi includono la ricezione di un intent, l'avvio di un servizio di lavoro o la ripresa di un'attività da parte dell'utente.

ActivityManagerService gestisce tutti i processi delle app e prende decisioni sul ciclo di vita delle app. CachedAppOptimizer è responsabile del blocco del processo dell'app.

Quando un processo dell'app viene bloccato, tutti i relativi thread vengono sospesi e non possono eseguire operazioni della CPU finché non vengono sbloccati. Di conseguenza, l'app non può eseguire la garbage collection (GC) e non può rispondere agli eventi di riduzione della memoria. Per saperne di più, vedi ComponentCallbacks2.onTrimMemory(int). Per far fronte a questa situazione, a partire da Android 14:

  • Le app con un'istanza Activity visibile vengono informate di TRIM_MEMORY_UI_HIDDEN non appena passano in background. Le app che rimangono in un ciclo di vita senza un'interfaccia utente, ad esempio le app con un servizio in primo piano, potrebbero ricevere TRIM_MEMORY_BACKGROUND. Gli altri eventi di taglio non vengono pubblicati perché, quando le app sono idonee per questi eventi, si prevede che vengano bloccate.
  • Poco dopo l'inserimento dello stato memorizzato nella cache, il sistema potrebbe richiedere al runtime dell'app di eseguire una GC in preparazione a un potenziale blocco.
  • Quando un processo dell'app viene bloccato, potrebbero verificarsi ulteriori passaggi di compattazione della memoria, ad esempio la scrittura di pagine modificate nell'archivio di backup e lo scambio di pagine anonime con ZRAM.
  • Se tutti i processi per una determinata app sono bloccati, il sistema termina tutti i socket TCP attivi gestiti dall'app. In questo modo, il lato server del socket non invia ping keepalive TCP che riattiverebbero il modem del dispositivo.

I processi delle app memorizzati nella cache vengono riattivati quando il loro stato passa da memorizzato nella cache a uno stato di importanza superiore. Per ridurre gli eventi di riattivazione in Android 14 e versioni successive, il sistema mette in coda le trasmissioni registrate nel contesto mentre l'app è nello stato memorizzato nella cache. I broadcast registrati nel contesto sono ricevitori che un'app registra dinamicamente chiamando Context.registerReceiver. Il sistema distribuisce queste trasmissioni in coda solo dopo che l'app è stata riattivata. Al contrario, il sistema non mette in coda le trasmissioni dichiarate nel file manifest. I broadcast dichiarati nel manifest sono ricevitori dichiarati staticamente in AndroidManifest.xml utilizzando l'elemento <receiver>. Il sistema scongela immediatamente l'app memorizzata nella cache per distribuire le trasmissioni dichiarate nel manifest.

Impatto sull'integrità del sistema

Android termina il processo dell'app memorizzata nella cache meno recente se sono presenti più di MAX_CACHED_PROCESSES processi dell'app memorizzati nella cache. Sui dispositivi supportati con Android 14 o versioni successive, MAX_CACHED_PROCESSES è aumentata in modo significativo, consentendo ai dispositivi di mantenere in RAM un numero maggiore di processi delle app memorizzati nella cache.

Il mantenimento di un maggior numero di app memorizzate nella cache della RAM comporta una riduzione fino al 30% degli avvii a freddo, con riduzioni scalabili in base alla RAM totale del dispositivo. Allo stesso tempo, il consumo di CPU da parte delle app memorizzate nella cache viene ridotto al minimo, con conseguente risparmio significativo della batteria.

Esenzioni per i congelatori

In determinate condizioni, un processo dell'app potrebbe entrare nello stato memorizzato nella cache, ma rimanere sbloccato. Queste esenzioni sono dettagli di implementazione e potrebbero cambiare nelle future versioni di Android:

  • Blocchi dei file: se un processo memorizzato nella cache mantiene un blocco dei file che blocca altri processi non memorizzati nella cache, il processo che mantiene il blocco non viene bloccato.
  • Binding BIND_WAIVE_PRIORITY: i processi dell'app con binding in entrata creati utilizzando Context.BIND_WAIVE_PRIORITY possono entrare nello stato memorizzato nella cache, ma rimangono scongelati finché anche tutti i processi client connessi non vengono memorizzati nella cache. Questa esenzione supporta le app multiprocesso, ad esempio i browser web che utilizzano le schede personalizzate.

Implementare il blocco delle app

Il freezer delle app memorizzate nella cache utilizza il freezer cgroup v2 del kernel. I dispositivi spediti con un kernel compatibile possono attivarlo. Attiva l'opzione sviluppatore Sospendi l'esecuzione per le app memorizzate nella cache o imposta il flag di configurazione del dispositivo activity_manager_native_boot use_freezer su true. Ad esempio:

adb shell device_config put activity_manager_native_boot use_freezer true && adb reboot

Il freezer viene disattivato quando imposti il flag use_freezer su false o disattivi l'opzione sviluppatore. Ad esempio:

adb shell device_config put activity_manager_native_boot use_freezer false && adb reboot

Puoi attivare o disattivare questa impostazione modificando la configurazione di un dispositivo in una release o un aggiornamento software.

Per ignorare MAX_CACHED_PROCESSES, ad esempio per impostare il valore su 1024 per i test:

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

Per ripristinare l'override di MAX_CACHED_PROCESSES:

adb shell device_config delete activity_manager max_cached_processes
adb shell device_config set_sync_disabled_for_tests none

A partire da Android 16 (livello API 36) e versioni successive, Android fornisce API pubbliche ufficiali, come IBinder.FrozenStateChangeCallback e IBinder.addFrozenStateChangeCallback, per osservare quando i processi remoti vengono bloccati o sbloccati. I componenti che interagiscono con le app che potrebbero essere memorizzate nella cache possono utilizzare queste API per monitorare lo stato di blocco dei processi remoti.

Requisiti del dispositivo e del kernel

Il congelamento delle app memorizzate nella cache richiede il supporto di cgroup v2 del kernel. Inoltre, le notifiche di modifica dello stato di blocco che utilizzano IBinder.FrozenStateChangeCallback richiedono il supporto del driver binder del kernel, che è standard in Android Common Kernels (ACK) e Generic Kernel Images (GKI) a partire da Android 14 (livello API 34) e versioni successive.

Puoi verificare se un dispositivo supporta queste funzionalità utilizzando i comandi adb standard, come segue:

  • Controlla se il freezer è attivo sul dispositivo (per le build utente o di debug):

    adb shell device_config get activity_manager_native_boot use_freezer

    In alternativa, verifica che i processi vengano attivamente bloccati dal sistema:

    adb shell dumpsys activity | grep -A 20 "Apps frozen:"
  • Controlla il supporto del controller freezer cgroup v2 (per qualsiasi dispositivo):

    Verifica che freezer sia elencato tra i controller cgroup v2 disponibili:

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

    In alternativa, su un dispositivo rooted o una build userdebug, verifica che il nodo freezer cgroup v2 sia montato in un cgroup secondario:

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

    Se questo file esiste, il kernel supporta il freezer cgroup v2.

  • Controlla il supporto delle notifiche di modifica dello stato di blocco (per qualsiasi dispositivo):

    Sui dispositivi con Android 14 e versioni successive con driver binder Generic Kernel Image (GKI) compatibili, IBinder.addFrozenStateChangeCallback registra correttamente i callback. Se il driver binder del kernel sottostante non supporta le notifiche di blocco, il metodo genera UnsupportedOperationException.

Gestire funzionalità personalizzate

Non è previsto che i processi delle app eseguano operazioni quando sono memorizzati nella cache, ma alcune app potrebbero avere funzionalità personalizzate supportate da processi che devono essere eseguiti mentre sono memorizzati nella cache. Quando il blocco delle app è attivato su un dispositivo che esegue queste app, i processi memorizzati nella cache vengono bloccati e potrebbero impedire il funzionamento delle funzionalità personalizzate.

Come soluzione alternativa, puoi impostare lo stato del processo su non memorizzato nella cache prima che il processo debba svolgere qualsiasi attività. Questa modifica consente alle app di rimanere attive. Esempi di stati attivi includono un servizio in primo piano associato o uno stato in primo piano.

Modalità di errore comuni

Quando i processi delle app sono bloccati, una comunicazione interprocesso (IPC) o una pianificazione delle attività impropria può causare la chiusura delle app o un comportamento imprevisto.

Transazioni del raccoglitore sincrone ai processi bloccati

Quando un processo di app client invia una transazione binder sincrona a un processo di app server bloccato, il sistema termina immediatamente il processo di app server. In questo modo, il thread client non viene bloccato indefinitamente durante l'attesa di una risposta dal server bloccato. Il thread client riceve quindi RemoteException e vengono attivati tutti i listener registrati. Per saperne di più, vedi IBinder.linkToDeath.

Causa principale: questo errore è in genere causato da un bug nell'app client. Quando un client si associa a un servizio, il processo server è associato al client e non può entrare nello stato memorizzato nella cache prima del client. Per saperne di più, consulta Context.bindService. Tuttavia, una volta che il client chiama Context.unbindService, il processo del server può essere memorizzato nella cache e bloccato. Se il client continua a utilizzare il riferimento IBinder memorizzato nella cache dopo l'annullamento del binding, rischia di comunicare con un processo bloccato.

Per evitare questo problema, assicurati che le app client eliminino i riferimenti IBinder immediatamente dopo aver chiamato Context.unbindService.

Gestire i callback remoti ai processi client

I servizi e i componenti di sistema che mantengono callback binder di lunga durata per i processi client possono prevenire errori sincroni e overflow del buffer asincrono monitorando lo stato di blocco del client:

  • Registra un listener di cambio di stato: utilizza IBinder.addFrozenStateChangeCallback sui token binder client in entrata per ricevere notifiche quando il processo client entra o esce dal freezer.
  • Metti in pausa l'invio durante il blocco: quando un client entra in STATE_FROZEN, metti in pausa l'invio di callback o aggiornamenti di stato a quel client.
  • Riprendi e invia dopo lo scongelamento: quando il client esegue la transizione a STATE_UNFROZEN, riprendi l'invio dei callback e invia gli aggiornamenti batch o consolidati necessari.
  • Utilizza RemoteCallbackList: i servizi di sistema che utilizzano RemoteCallbackList possono configurare le policy del chiamante bloccato per mettere in pausa e riprendere automaticamente l'invio dei callback senza mantenere la logica di monitoraggio manuale. Per saperne di più, consulta Consigli per il congelamento dei binder per i servizi di sistema.

Overflow del buffer delle transazioni del binder asincrono

Quando un processo dell'app server riceve transazioni binder asincrone (oneway) mentre è bloccato, le transazioni vengono memorizzate nel buffer per processo. Se il server riceve troppe transazioni asincrone durante il blocco, il buffer va in overflow e il sistema termina il processo dell'app server.

Per evitare questo overflow del buffer, evita di inviare transazioni binder asincrone eccessive a processi che potrebbero essere memorizzati nella cache o bloccati.

Esecuzione ripetuta delle attività pianificate dopo lo scongelamento

Se un'app esegue attività ripetitive, queste vengono sospese mentre il processo è bloccato. Per saperne di più, consulta ScheduledThreadPoolExecutor.scheduleAtFixedRate o Timer.scheduleAtFixedRate. Quando il processo viene sbloccato, le esecuzioni mancate accumulate potrebbero essere eseguite rapidamente in sequenza senza praticamente alcun ritardo.

Per evitare un picco di esecuzioni quando l'app viene riattivata, utilizza scheduleWithFixedDelay anziché scheduleAtFixedRate per le attività in background. Puoi anche utilizzare WorkManager.

Testare e risolvere i problemi relativi al blocco delle app

Per verificare che il blocco delle app funzioni come previsto o per risolvere i problemi correlati, utilizza i seguenti comandi e strumenti di diagnostica:

Comandi di Gestione attività

Puoi utilizzare i comandi adb shell am per controllare manualmente il blocco e la compattazione per un processo specifico:

  • Forza il blocco di un processo:

    adb shell am freeze <process>
  • Forzare lo sblocco di un processo:

    adb shell am unfreeze <process>
  • Forza una compattazione completa della memoria su un processo:

    adb shell am compact full <process>

Esame di Logcat

Visualizza logcat per vedere le voci bloccate e sbloccate ogni volta che un processo viene spostato dentro o fuori dal freezer:

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

L'output dei log del motivo di scongelamento enumera i valori del buffer enum del protocollo UnfreezeReason.

Ispezione dumpsys

Controlla l'elenco dei processi bloccati utilizzando dumpsys activity:

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

Verifica la presenza del file /sys/fs/cgroup/uid_0/cgroup.freeze.

ApplicationExitInfo

Per eseguire una query sul motivo della chiusura di un processo precedente, consulta ActivityManager.getHistoricalProcessExitReasons. Se un processo dell'app è stato interrotto a causa di un problema relativo al blocco, ad esempio la ricezione di una transazione binder sincrona durante il blocco, il motivo dell'uscita è impostato su ApplicationExitInfo.REASON_FREEZER.

Perfetto tracing

Gli eventi relativi al congelamento vengono emessi in una traccia denominata Freezer nel processo system_server nelle tracce Perfetto:

  • Le sezioni Freeze e Unfreeze indicano quando lo stato di un processo cambia.
  • Gli eventi updateAppFreezeStateLSP mostrano quando il server di sistema riesamina gli attributi del processo per prendere decisioni di blocco o sblocco.

Puoi esaminare questi eventi direttamente nell'UI di Perfetto o analizzarli utilizzando 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 %");

Nella libreria standard PerfettoSQL, gli eventi di blocco vengono riepilogati anche nella tabella android_freezer_events.