Cache für Apps

Der Freezer für im Cache gespeicherte Apps wird ab Android 11 (API‑Level 30) unterstützt. Diese Funktion beendet die Ausführung für im Cache gespeicherte Prozesse und reduziert die Ressourcennutzung durch Apps, die sich möglicherweise im Cache nicht ordnungsgemäß verhalten.

Der Cache-Apps-Freezer hält Apps im RAM, ohne die CPU zu belasten. Wenn Android feststellt, dass eine App keine Aufgaben ausführen sollte, aber möglicherweise in Zukunft benötigt wird, wird der App-Prozess eingefroren, anstatt beendet. So wird ein Kaltstart verhindert, wenn die App wieder benötigt wird.

Android friert im Cache gespeicherte Apps ein, indem die zugehörigen Prozesse in eine eingefrorene Cgroup migriert werden. Dadurch wird die CPU-Nutzung im aktiven und im Leerlaufmodus bei aktiven gecachten Apps reduziert. Sie können die App-Einfrierung über ein Systemkonfigurationsflag oder eine Entwickleroption aktivieren.

In Android 14 (API-Level 34) und höher bietet der Cache-Apps-Freezer die folgenden robusten Verhaltensweisen:

  • App-Prozesse im Cache-Zustand werden 10 Sekunden nach dem Wechsel in den Cache-Zustand eingefroren.
  • Das System reaktiviert einen eingefrorenen App-Prozess sofort bei einem Lebenszyklusereignis. Zu diesen Ereignissen gehören der Empfang eines Intents, der Start eines Job-Dienstes oder die Wiederaufnahme einer Aktivität durch den Nutzer.

ActivityManagerService verwaltet alle App-Prozesse und trifft Entscheidungen zum App-Lebenszyklus. CachedAppOptimizer ist für das Einfrieren des App-Prozesses verantwortlich.

Wenn ein App-Prozess eingefroren wird, werden alle seine Threads angehalten und können erst wieder CPU-Arbeit ausführen, wenn sie wieder aktiviert werden. Daher kann die App keine Garbage Collection (GC) durchführen und nicht auf Ereignisse zum Kürzen des Arbeitsspeichers reagieren. Weitere Informationen finden Sie unter ComponentCallbacks2.onTrimMemory(int). Ab Android 14 gilt Folgendes:

  • Apps mit einer sichtbaren Activity-Instanz werden über TRIM_MEMORY_UI_HIDDEN benachrichtigt, sobald sie in den Hintergrund verschoben werden. Apps, die sich in einem Lebenszyklus ohne Benutzeroberfläche befinden, z. B. Apps mit einem Dienst im Vordergrund, erhalten möglicherweise TRIM_MEMORY_BACKGROUND. Andere Ereignisse zum Kürzen werden nicht gesendet, da Apps, die für diese Ereignisse infrage kommen, eingefroren werden sollen.
  • Kurz nach dem Eintreten des gecachten Status fordert das System möglicherweise die App-Laufzeit auf, eine Garbage Collection durchzuführen, um sich auf das Einfrieren vorzubereiten.
  • Wenn ein App-Prozess eingefroren wird, können zusätzliche Schritte zur Speicherverdichtung erfolgen, z. B. das Schreiben von „dirty“ Pages in den Sicherungsspeicher und das Auslagern anonymer Pages in ZRAM.
  • Wenn alle Prozesse für eine bestimmte App eingefroren sind, beendet das System alle aktiven TCP-Sockets, die von der App verwaltet werden. Dadurch wird verhindert, dass die Serverseite des Sockets TCP-Keepalive-Pings sendet, die das Gerätemodem aktivieren würden.

Prozesse von Apps im Cache werden reaktiviert, wenn ihr Prozessstatus von „im Cache“ zu einem Status mit höherer Priorität wechselt. Um das Einfrieren von Apps in Android 14 und höher zu reduzieren, stellt das System kontextregistrierte Broadcasts in die Warteschlange, während sich die App im Cache-Zustand befindet. Kontextregistrierte Broadcasts sind Empfänger, die von einer App dynamisch durch Aufrufen von Context.registerReceiver registriert werden. Das System stellt diese in die Warteschlange gestellten Broadcasts erst zu, wenn die App wieder aktiviert wurde. Im Gegensatz dazu werden im Manifest deklarierte Broadcasts nicht in die Warteschlange gestellt. In Manifesten deklarierte Broadcasts sind Empfänger, die statisch in AndroidManifest.xml mit dem Element <receiver> deklariert werden. Das System reaktiviert die im Cache gespeicherte App sofort, um im Manifest deklarierte Broadcasts zu senden.

Auswirkungen auf den Systemzustand

Android beendet den am längsten nicht verwendeten gecachten App-Prozess, wenn mehr als MAX_CACHED_PROCESSES gecachte App-Prozesse vorhanden sind. Auf unterstützten Geräten mit Android 14 oder höher ist MAX_CACHED_PROCESSES deutlich höher, sodass Geräte wesentlich mehr zwischengespeicherte App-Prozesse im RAM behalten können.

Wenn mehr Apps im RAM zwischengespeichert werden, kann die Anzahl der Kaltstarts um bis zu 30% reduziert werden. Die Reduzierung hängt vom gesamten RAM des Geräts ab. Gleichzeitig wird der CPU-Verbrauch durch im Cache gespeicherte Apps minimiert, was zu einer erheblichen Akkuschonung führt.

Ausnahmen für Gefrierschränke

Unter bestimmten Bedingungen kann ein App-Prozess in den Cache-Status wechseln, ohne eingefroren zu werden. Diese Ausnahmen sind Implementierungsdetails und können sich in zukünftigen Android-Versionen ändern:

  • Dateisperren: Wenn ein im Cache gespeicherter Prozess eine Dateisperre enthält, die andere nicht im Cache gespeicherte Prozesse blockiert, wird der Prozess, der die Sperre enthält, nicht eingefroren.
  • BIND_WAIVE_PRIORITY-Bindungen: App-Prozesse mit eingehenden Bindungen, die mit Context.BIND_WAIVE_PRIORITY erstellt wurden, können in den Cache-Status wechseln, bleiben aber eingefroren, bis alle verbundenen Clientprozesse ebenfalls im Cache sind. Diese Ausnahme unterstützt Apps mit mehreren Prozessen, z. B. Webbrowser, die benutzerdefinierte Tabs verwenden.

Implementierung der Funktion zum Einfrieren von Apps

Der Freezer für zwischengespeicherte Apps verwendet den Kernel-Freezer für Cgroup v2. Auf Geräten mit einem kompatiblen Kernel kann die Funktion aktiviert werden. Aktivieren Sie die Entwickleroption Ausführung für im Cache gespeicherte Apps anhalten oder legen Sie das Gerätekonfigurationsflag activity_manager_native_boot use_freezer auf true fest. Beispiel:

adb shell device_config put activity_manager_native_boot use_freezer true && adb reboot

Der Freezer wird deaktiviert, wenn Sie das Flag use_freezer auf false setzen oder die Entwickleroption deaktivieren. Beispiel:

adb shell device_config put activity_manager_native_boot use_freezer false && adb reboot

Sie können diese Einstellung ändern, indem Sie eine Gerätekonfiguration in einer Softwareversion oder einem Update ändern.

So überschreiben Sie MAX_CACHED_PROCESSES, um den Wert beispielsweise für Tests auf 1.024 festzulegen:

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

So machen Sie die MAX_CACHED_PROCESSES-Überschreibung rückgängig:

adb shell device_config delete activity_manager max_cached_processes
adb shell device_config set_sync_disabled_for_tests none

Ab Android 16 (API-Level 36) und höher bietet Android offizielle öffentliche APIs wie IBinder.FrozenStateChangeCallback und IBinder.addFrozenStateChangeCallback, um zu beobachten, wann Remote-Prozesse eingefroren oder aufgetaut werden. Komponenten, die mit Apps interagieren, die möglicherweise im Cache gespeichert werden, können diese APIs verwenden, um den eingefrorenen Status von Remote-Prozessen zu verfolgen.

Geräte- und Kernelanforderungen

Für das Einfrieren von Apps im Cache ist die Unterstützung von Kernel-Cgroup v2 erforderlich. Außerdem erfordern Benachrichtigungen über Änderungen des eingefrorenen Status mit IBinder.FrozenStateChangeCallback die Unterstützung des Kernel-Binder-Treibers, der in Android Common Kernels (ACK) und Generic Kernel Images (GKI) ab Android 14 (API-Level 34) und höher standardmäßig enthalten ist.

Mit den folgenden Standardbefehlen für adb können Sie prüfen, ob ein Gerät diese Funktionen unterstützt:

  • Prüfen, ob der Freezer auf dem Gerät aktiviert ist (für Nutzer- oder Debug-Builds):

    adb shell device_config get activity_manager_native_boot use_freezer

    Oder prüfen Sie, ob Prozesse aktiv vom System eingefroren werden:

    adb shell dumpsys activity | grep -A 20 "Apps frozen:"
  • Unterstützung des cgroup v2-Freezer-Controllers prüfen (für alle Geräte):

    Prüfen Sie, ob freezer in der Liste der verfügbaren cgroup v2-Controller aufgeführt ist:

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

    Alternativ können Sie auf einem gerooteten Gerät oder in einem Userdebug-Build prüfen, ob der cgroup v2-Freezer-Knoten in einer untergeordneten Cgroup gemountet ist:

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

    Wenn diese Datei vorhanden ist, unterstützt der Kernel den cgroup v2-Freezer.

  • Unterstützung für Benachrichtigungen über Änderungen des eingefrorenen Status prüfen (für jedes Gerät):

    Auf Geräten mit Android 14 und höher mit kompatiblen GKI-Binder-Treibern (Generic Kernel Image) registriert IBinder.addFrozenStateChangeCallback Rückrufe erfolgreich. Wenn der zugrunde liegende Kernel-Binder-Treiber keine Freeze-Benachrichtigungen unterstützt, wird von der Methode UnsupportedOperationException ausgelöst.

Benutzerdefinierte Funktionen verarbeiten

App-Prozesse sollten im Cache keine Aufgaben ausführen. Einige Apps haben jedoch möglicherweise benutzerdefinierte Funktionen, die von Prozessen unterstützt werden, die im Cache ausgeführt werden sollen. Wenn die App-Gefrierfunktion auf einem Gerät aktiviert ist, auf dem solche Apps ausgeführt werden, werden die im Cache gespeicherten Prozesse eingefroren. Das kann dazu führen, dass benutzerdefinierte Funktionen nicht mehr funktionieren.

Als Workaround können Sie den Prozessstatus in „nicht im Cache“ ändern, bevor der Prozess mit der Arbeit beginnen muss. Durch diese Änderung können Apps aktiv bleiben. Beispiele für aktive Status sind ein gebundener Dienst im Vordergrund oder der Vordergrundstatus.

Häufige Fehlermodi

Wenn App-Prozesse eingefroren werden, kann eine unsachgemäße Interprozesskommunikation (IPC) oder Aufgabenplanung zu App-Beendigungen oder unerwartetem Verhalten führen.

Synchrone Binder-Transaktionen für eingefrorene Prozesse

Wenn ein Client-App-Prozess eine synchrone Binder-Transaktion an einen eingefrorenen Server-App-Prozess sendet, beendet das System den Server-App-Prozess sofort. So wird verhindert, dass der Client-Thread unbegrenzt blockiert wird, während er auf eine Antwort vom eingefrorenen Server wartet. Der Client-Thread empfängt dann RemoteException und alle registrierten Listener werden ausgelöst. Weitere Informationen finden Sie unter: IBinder.linkToDeath.

Ursache: Dieser Fehler wird in der Regel durch einen Fehler in der Client-App verursacht. Wenn ein Client an einen Dienst gebunden ist, ist der Serverprozess an den Client gebunden und kann nicht in den Cache-Zustand eintreten, bevor der Client dies tut. Weitere Informationen finden Sie unter: Context.bindService Sobald der Client jedoch Context.unbindService aufruft, kann der Serverprozess im Cache gespeichert und eingefroren werden. Wenn der Client die zwischengespeicherte IBinder-Referenz nach dem Aufheben der Bindung weiterhin verwendet, riskiert er die Kommunikation mit einem eingefrorenen Prozess.

Um dieses Problem zu vermeiden, müssen Client-Apps IBinder-Referenzen sofort nach dem Aufrufen von Context.unbindService verwerfen.

Remote-Rückrufe an Clientprozesse verwalten

Dienste und Systemkomponenten, die langlebige Binder-Callbacks für Clientprozesse verwalten, können synchrone Fehler und asynchrone Pufferüberläufe verhindern, indem sie den eingefrorenen Status des Clients verfolgen:

  • Listener für Statusänderungen registrieren: Verwenden Sie IBinder.addFrozenStateChangeCallback für eingehende Client-Binder-Tokens, um Benachrichtigungen zu erhalten, wenn der Clientprozess in den Freezer eintritt oder ihn verlässt.
  • Versand pausieren, wenn eingefroren: Wenn ein Client in den Status STATE_FROZEN wechselt, werden Callback- oder Statusaktualisierungen für diesen Client pausiert.
  • Nach dem Entsperren fortsetzen und ausliefern: Wenn der Client zu STATE_UNFROZEN wechselt, wird das Senden von Callbacks fortgesetzt und alle erforderlichen zusammengefassten oder konsolidierten Updates werden ausgeliefert.
  • RemoteCallbackList verwenden: Systemdienste, die RemoteCallbackList verwenden, können Richtlinien für eingefrorene Aufgerufene konfigurieren, um das Senden von Callbacks automatisch zu pausieren und fortzusetzen, ohne dass eine manuelle Tracking-Logik erforderlich ist. Weitere Informationen finden Sie unter Empfehlungen für das Einfrieren von Bindern für Systemdienste.

Asynchroner Binder-Transaktionspufferüberlauf

Wenn ein Server-App-Prozess während des Einfrierens asynchrone (oneway) Binder-Transaktionen empfängt, werden die Transaktionen in einem prozessbezogenen Puffer gepuffert. Wenn der Server während des Einfrierens zu viele asynchrone Transaktionen empfängt, läuft der Puffer über und das System beendet den Server-App-Prozess.

Um diesen Pufferüberlauf zu vermeiden, sollten Sie nicht zu viele asynchrone Binder-Transaktionen an Prozesse senden, die möglicherweise im Cache gespeichert oder eingefroren werden.

Wiederholte Ausführung geplanter Aufgaben nach dem Reaktivieren

Wenn eine App sich wiederholende Aufgaben ausführt, werden diese angehalten, während der Prozess eingefroren ist. Weitere Informationen finden Sie unter ScheduledThreadPoolExecutor.scheduleAtFixedRate oder Timer.scheduleAtFixedRate. Wenn der Prozess wieder aktiviert wird, werden die angehäuften verpassten Ausführungen möglicherweise schnell hintereinander ohne Verzögerung ausgeführt.

Um einen Anstieg der Ausführungen zu verhindern, wenn die App wieder aktiviert wird, verwenden Sie für Hintergrundaufgaben scheduleWithFixedDelay anstelle von scheduleAtFixedRate. Sie können auch WorkManager verwenden.

App-Gefrierfunktion testen und Fehler beheben

Wenn Sie prüfen möchten, ob die App-Einfrierung wie vorgesehen funktioniert, oder Probleme mit der Einfrierung beheben möchten, verwenden Sie die folgenden Diagnosetools und Befehle:

Befehle für den Aktivitätsmanager

Mit adb shell am-Befehlen können Sie das Einfrieren und Verdichten für einen bestimmten Prozess manuell steuern:

  • Prozess zum Einfrieren zwingen:

    adb shell am freeze <process>
  • So erzwingen Sie das Entfrosten eines Prozesses:

    adb shell am unfreeze <process>
  • Erzwingen einer vollständigen Arbeitsspeicherkomprimierung für einen Prozess:

    adb shell am compact full <process>

Logcat-Prüfung

Mit logcat können Sie sehen, welche Einträge eingefroren und wieder aufgetaut werden, wenn ein Prozess in den Freezer migriert oder aus dem Freezer migriert wird:

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

Die Ausgabe von Logs mit dem Grund für das Aufheben der Einfrierung enthält aufgezählte Werte aus der UnfreezeReason-Enum des Protokollzwischenspeichers.

Dumpsys-Prüfung

So rufen Sie eine Liste der eingefrorenen Prozesse mit dumpsys activity auf:

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

Prüfen Sie, ob die Datei /sys/fs/cgroup/uid_0/cgroup.freeze vorhanden ist.

ApplicationExitInfo

Wenn Sie den Grund für die Beendigung eines vorherigen Prozesses abfragen möchten, lesen Sie den Abschnitt ActivityManager.getHistoricalProcessExitReasons. Wenn ein App-Prozess aufgrund eines Freezer-Problems beendet wurde, z. B. weil er während des Einfrierens eine synchrone Binder-Transaktion empfangen hat, wird der Beendigungsgrund auf ApplicationExitInfo.REASON_FREEZER gesetzt.

Perfetto-Tracing

Ereignisse im Zusammenhang mit dem Freezer werden in Perfetto-Traces in einem Track mit dem Namen Freezer unter dem Prozess system_server ausgegeben:

  • Die Segmente Freeze und Unfreeze geben an, wann sich der Status eines Prozesses ändert.
  • updateAppFreezeStateLSP-Ereignisse geben an, wann der Systemserver Prozessattribute noch einmal prüft, um Entscheidungen zum Einfrieren oder Aufheben des Einfrierens zu treffen.

Sie können diese Ereignisse direkt in der Perfetto-UI ansehen oder mit PerfettoSQL analysieren:

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 %");

In der PerfettoSQL-Standardbibliothek werden Freezer-Ereignisse auch in der Tabelle android_freezer_events zusammengefasst.