Congelatore delle app memorizzate nella cache

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

Il freezer per le app memorizzate nella cache mantiene le app nella RAM, ma non nella CPU. Se Android determina 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 migrando i relativi processi in un cgroup bloccato. In questo modo si riduce il consumo di CPU attiva e inattiva in presenza di app memorizzate nella cache attive. Puoi attivare il freezer per le 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 delle app nello stato memorizzato nella cache vengono bloccati 10 secondi dopo essere entrati in questo stato.
  • Il sistema sblocca 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 job 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 è bloccato, tutti i relativi thread vengono sospesi e non possono eseguire operazioni della CPU fino a quando 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 maggiori dettagli, consulta ComponentCallbacks2.onTrimMemory(int). Per risolvere questo problema, a partire da Android 14:

  • Le app con un'istanza Activity visibile ricevono una notifica di TRIM_MEMORY_UI_HIDDEN non appena passano in background. Le app che rimangono in un ciclo di vita senza UI, ad esempio le app con un servizio in primo piano, potrebbero ricevere TRIM_MEMORY_BACKGROUND. Gli altri eventi di riduzione non vengono inviati perché, quando le app sono idonee per questi eventi, è previsto che vengano bloccate.
  • Poco dopo essere entrato nello 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 è bloccato, potrebbero verificarsi ulteriori passaggi di compattazione della memoria, ad esempio la scrittura di pagine modificate nell'archiviazione di backup e lo scambio di pagine anonime con ZRAM.
  • Se tutti i processi di una determinata app sono bloccati, il sistema termina tutti i socket TCP attivi gestiti dall'app. In questo modo si impedisce al lato server del socket di inviare ping TCP keepalive che riattiverebbero il modem del dispositivo.

I processi delle app memorizzate nella cache vengono sbloccati quando il relativo stato del processo passa da memorizzato nella cache a uno stato di importanza superiore. Per ridurre gli eventi di sblocco in Android 14 e versioni successive, il sistema mette in coda le trasmissioni registrate nel contesto mentre l'app è nello stato memorizzato nella cache. Le trasmissioni registrate nel contesto sono ricevitori che un'app registra dinamicamente chiamando Context.registerReceiver. Il sistema invia queste trasmissioni in coda solo dopo che l'app è stata sbloccata. Al contrario, il sistema non mette in coda le trasmissioni dichiarate nel file manifest. Le trasmissioni dichiarate nel file manifest sono ricevitori dichiarati staticamente in AndroidManifest.xml utilizzando l'elemento <receiver>. Il sistema sblocca immediatamente l'app memorizzata nella cache per inviare le trasmissioni dichiarate nel file manifest.

Impatto sull'integrità del sistema

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

Il mantenimento di un numero maggiore di app memorizzate nella cache nella RAM comporta una riduzione fino al 30% degli avvii a freddo, con riduzioni che aumentano 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 conseguenti risparmi significativi della batteria.

Esenzioni dal freezer

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 versioni future di Android:

  • Blocchi di file: se un processo memorizzato nella cache contiene un blocco di file che blocca altri processi non memorizzati nella cache, il processo che contiene il blocco non viene bloccato.
  • BIND_WAIVE_PRIORITY binding: i processi delle app con binding in entrata creati utilizzando Context.BIND_WAIVE_PRIORITY possono entrare nello stato memorizzato nella cache, ma rimangono sbloccati finché anche tutti i processi client connessi non vengono memorizzati nella cache. Questa esenzione supporta le app multi-processo, come i browser web che utilizzano le schede personalizzate.

Implementa il freezer per le app

Il freezer per le app memorizzate nella cache sfrutta il freezer cgroup v2 del kernel. I dispositivi forniti con un kernel compatibile possono attivarlo. Attiva l'opzione sviluppatore Sospendi esecuzione 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 in un aggiornamento software.

Per sostituire 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 la sostituzione di MAX_CACHED_PROCESSES:

adb shell device_config delete activity_manager max_cached_processes
adb shell device_config set_sync_disabled_for_tests none

Il freezer per le app non espone API ufficiali e non ha un client di implementazione di riferimento, ma utilizza le API di sistema nascoste setProcessFrozen per bloccare un singolo processo e enableFreezer per attivare o disattivare il blocco a livello globale.

Gestisci le 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 freezer per le 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 modificare lo stato del processo in non memorizzato nella cache prima che il processo debba eseguire qualsiasi operazione. 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à non corretta può causare la terminazione delle app o un comportamento imprevisto.

Transazioni binder sincrone per i processi bloccati

Quando un processo dell'app client invia una transazione binder sincrona a un processo dell'app server bloccato, il sistema termina immediatamente il processo dell'app server. In questo modo si impedisce al thread client di bloccarsi indefinitamente durante l'attesa di una risposta dal server bloccato. Il thread client riceve quindi RemoteException e vengono attivati tutti i listener registrati. Per maggiori dettagli, consulta 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 maggiori dettagli, consulta Context.bindService. Tuttavia, una volta che il client chiama Context.unbindService, il processo server può diventare memorizzato nella cache e bloccato. Se il client continua a utilizzare il riferimento memorizzato nella cache IBinder dopo l'annullamento dell'associazione, rischia di comunicare con un processo bloccato.

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

Overflow del buffer delle transazioni binder asincrone

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

Per evitare questo overflow del buffer, evita di inviare un numero eccessivo di transazioni binder asincrone ai processi che potrebbero essere memorizzati nella cache o bloccati.

Esecuzione ripetuta delle attività pianificate dopo lo sblocco

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

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

Testa e risolvi i problemi del freezer per le app

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

Comandi di Activity Manager

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

  • Forza il blocco di un processo:

    adb shell am freeze <process>
  • Forza 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 esegue la migrazione all'interno o all'esterno del freezer:

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

I log dei motivi di sblocco restituiscono valori enumerati dall'enum del buffer del protocollo UnfreezeReason.

Ispezione di 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 terminazione di un processo precedente, consulta ActivityManager.getHistoricalProcessExitReasons. Se un processo dell'app è stato terminato a causa di un problema correlato al freezer, ad esempio la ricezione di una transazione binder sincrona mentre è bloccato, il motivo di uscita è impostato su ApplicationExitInfo.REASON_FREEZER.

Tracciamento di Perfetto

Gli eventi correlati al freezer vengono emessi in una traccia denominata Freezer nel processo system_server nelle tracce di Perfetto:

  • Le sezioni Freeze e Unfreeze indicano quando un processo cambia stato.
  • 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 di PerfettoSQL, gli eventi del freezer vengono riepilogati anche nella tabella android_freezer_events.