Apps im Cache und eingefrorene Apps verarbeiten

Wenn Sie Binder für die Kommunikation zwischen Prozessen verwenden, ist besondere Vorsicht geboten, wenn sich der Remote-Prozess in einem Cache- oder eingefrorenen Zustand befindet. Aufrufe in zwischengespeicherte oder eingefrorene Apps können dazu führen, dass sie abstürzen oder unnötig Ressourcen verbrauchen.

Zwischengespeicherte und eingefrorene App-Status

Android versetzt Apps in verschiedene Status, um Systemressourcen wie Arbeitsspeicher und CPU zu verwalten.

Zwischengespeicherter Status

Wenn eine App keine für den Nutzer sichtbaren Komponenten wie Aktivitäten oder Dienste hat, kann sie in den Status im Cache verschoben werden. Weitere Informationen finden Sie unter Prozesse und App-Lebenszyklus. Zwischengespeicherte Apps werden im Arbeitsspeicher behalten, falls der Nutzer zu ihnen zurückkehrt. Es wird jedoch nicht erwartet, dass sie aktiv ausgeführt werden.

Wenn Sie von einem App-Prozess an einen anderen binden, z. B. mit bindService, wird der Prozessstatus des Serverprozesses auf mindestens so wichtig wie der Clientprozess festgelegt (außer wenn Context#BIND_WAIVE_PRIORITY angegeben ist). Wenn sich der Client beispielsweise nicht im Cache-Status befindet, gilt das auch für den Server.

Umgekehrt bestimmt der Status eines Serverprozesses nicht den seiner Clients. Ein Server kann also Binder-Verbindungen zu Clients haben, meist in Form von Callbacks. Während sich der Remote-Prozess im Cache-Status befindet, wird der Server nicht im Cache gespeichert.

Wenn Sie APIs entwerfen, bei denen Callbacks aus einem Prozess mit erhöhten Berechtigungen stammen und an Apps gesendet werden, sollten Sie das Senden von Callbacks pausieren, wenn eine App in den Cache-Status wechselt, und fortsetzen, wenn sie diesen Status verlässt. Dadurch wird unnötige Arbeit in zwischengespeicherten App-Prozessen vermieden.

Wenn Sie erfassen möchten, wann Apps in den Cache-Zustand wechseln oder ihn verlassen, verwenden Sie ActivityManager.addOnUidImportanceListener:

// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
    new UidImportanceListener() { ... },
    IMPORTANCE_CACHED);

Status „Gesperrt“

Das System kann eine im Cache gespeicherte App einfrieren, um Ressourcen zu sparen. Wenn eine App eingefroren ist, erhält sie keine CPU-Zeit und kann keine Aufgaben ausführen. Weitere Informationen finden Sie unter Cached Apps Freezer.

Wenn ein Prozess eine synchrone (nicht oneway) Binder-Transaktion an einen anderen eingefrorenen Remote-Prozess sendet, beendet das System den Remote-Prozess. Dadurch wird verhindert, dass der aufrufende Thread im aufrufenden Prozess unbegrenzt lange wartet, bis der Remote-Prozess wieder aktiv ist. Dies könnte zu Thread-Starvation oder Deadlocks in der aufrufenden App führen.

Wenn ein Prozess eine asynchrone (oneway) Binder-Transaktion an eine eingefrorene App sendet (in der Regel durch Benachrichtigen eines Callbacks, der normalerweise eine oneway-Methode ist), wird die Transaktion gepuffert, bis der Remote-Prozess wieder aktiviert wird. Wenn der Puffer überläuft, kann der Prozess der Empfänger-App abstürzen. Außerdem können gepufferte Transaktionen veraltet sein, wenn der App-Prozess reaktiviert und verarbeitet wird.

Um zu verhindern, dass Apps mit alten Ereignissen überlastet werden oder ihre Puffer überlaufen, müssen Sie das Senden von Callbacks pausieren, während der Prozess der Empfänger-App eingefroren ist.

Wenn Sie nachverfolgen möchten, wann Apps eingefroren oder aufgetaut werden, verwenden Sie IBinder.addFrozenStateChangeCallback:

// The binder token of the remote process
IBinder binder = service.getBinder();

// Keep track of frozen state
AtomicBoolean remoteFrozen = new AtomicBoolean(false);

// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(
    myExecutor,
    new IBinder.FrozenStateChangeCallback() {
        @Override
        public void onFrozenStateChanged(boolean isFrozen) {
            remoteFrozen.set(isFrozen);
        }
    });

// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
    // dispatch callback to remote process
}

RemoteCallbackList verwenden

Die Klasse RemoteCallbackList ist eine Hilfsklasse zum Verwalten von Listen mit IInterface-Callbacks, die von Remote-Prozessen registriert werden. Diese Klasse verarbeitet automatisch Benachrichtigungen über das Beenden von Bindern und bietet Optionen für die Verarbeitung von Callbacks an eingefrorene Apps.

Beim Erstellen von RemoteCallbackList können Sie eine eingefrorene Richtlinie für den Aufgerufenen angeben:

  • FROZEN_CALLEE_POLICY_DROP: Rückrufe an eingefrorene Apps werden ohne Benachrichtigung verworfen. Verwenden Sie diese Richtlinie, wenn Ereignisse, die während des Caching der App aufgetreten sind, für die App nicht wichtig sind, z. B. Echtzeit-Sensorereignisse.
  • FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: Wenn mehrere Callbacks gesendet werden, während eine App eingefroren ist, wird nur der letzte in die Warteschlange gestellt und zugestellt, wenn die App wieder aktiviert wird. Dies ist nützlich für zustandsbasierte Callbacks, bei denen nur die letzte Statusaktualisierung wichtig ist, z. B. ein Callback, der die App über die aktuelle Medienlautstärke informiert.
  • FROZEN_CALLEE_POLICY_ENQUEUE_ALL: Alle Callbacks, die gesendet werden, während eine App eingefroren ist, werden in die Warteschlange gestellt und zugestellt, wenn die App wieder aktiviert wird. Seien Sie vorsichtig mit dieser Richtlinie, da sie zu Pufferüberläufen führen kann, wenn zu viele Callbacks in die Warteschlange gestellt werden, oder zur Ansammlung veralteter Ereignisse.

Das folgende Beispiel zeigt, wie Sie eine RemoteCallbackList-Instanz erstellen und verwenden, die Callbacks an eingefrorene Apps verwirft:

RemoteCallbackList<IMyCallbackInterface> callbacks =
        new RemoteCallbackList.Builder<IMyCallbackInterface>(
                        RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
                .setExecutor(myExecutor)
                .build();

// Registering a callback:
callbacks.register(callback);

// Broadcasting to all registered callbacks:
callbacks.broadcast((callback) -> callback.onSomeEvent(eventData));

Wenn Sie FROZEN_CALLEE_POLICY_DROP verwenden, ruft das System callback.onSomeEvent() nur auf, wenn der Prozess, in dem der Callback gehostet wird, nicht eingefroren ist.

Systemdienste und App-Interaktionen

Systemdienste interagieren häufig mit vielen verschiedenen Apps über Binder. Da Apps in den Cache- und eingefrorenen Zustand wechseln können, müssen Systemdienste diese Interaktionen sorgfältig verarbeiten, um die Systemstabilität und ‑leistung aufrechtzuerhalten.

Systemdienste müssen bereits Situationen verarbeiten, in denen App-Prozesse aus verschiedenen Gründen beendet werden. Das bedeutet, dass die Arbeit in ihrem Namen eingestellt wird und keine weiteren Callbacks an beendete Prozesse gesendet werden. Die Berücksichtigung von eingefrorenen Apps ist eine Erweiterung dieser bestehenden Überwachungsverantwortung.

App-Status über Systemdienste verfolgen

Systemdienste, die in system_server oder als native Daemons ausgeführt werden, können auch die oben beschriebenen APIs verwenden, um die Wichtigkeit und den eingefrorenen Status von App-Prozessen zu verfolgen:

  • ActivityManager.addOnUidImportanceListener: Systemdienste können einen Listener registrieren, um Änderungen der UID-Wichtigkeit zu verfolgen. Wenn der Dienst einen Binder-Aufruf oder einen Callback von einer App empfängt, kann er Binder.getCallingUid() verwenden, um die UID abzurufen und sie mit dem vom Listener erfassten Wichtigkeitsstatus zu korrelieren. So können Systemdienste erkennen, ob sich die aufrufende App im Cache befindet.

  • IBinder.addFrozenStateChangeCallback: Wenn ein Systemdienst ein Binder-Objekt von einer App empfängt (z. B. im Rahmen einer Registrierung für Callbacks), sollte er ein FrozenStateChangeCallback für diese bestimmte IBinder-Instanz registrieren. Dadurch wird der Systemdienst direkt benachrichtigt, wenn der App-Prozess, in dem dieser Binder gehostet wird, eingefroren oder aufgetaut wird.

Empfehlungen für Systemdienste

Wir empfehlen, dass alle Systemdienste, die möglicherweise mit Apps interagieren, den Status „Im Cache“ und „Eingefroren“ der App-Prozesse verfolgen, mit denen sie kommunizieren. Andernfalls kann es zu folgenden Problemen kommen:

  • Ressourcenverbrauch:Wenn für Apps, die im Cache gespeichert und für den Nutzer nicht sichtbar sind, Aufgaben ausgeführt werden, können Systemressourcen verschwendet werden.
  • App stürzt ab:Synchrone Binder-Aufrufe an eingefrorene Apps führen dazu, dass diese abstürzen. Asynchrone Binder-Aufrufe an eingefrorene Apps führen zu einem Absturz, wenn ihr asynchroner Transaktionspuffer überläuft.
  • Unerwartetes App-Verhalten:Aufgetaute Apps erhalten sofort alle gepufferten asynchronen Binder-Transaktionen, die während des Einfrierens an sie gesendet wurden. Apps können unbegrenzt lange im Gefrierschrank verbleiben, sodass gepufferte Transaktionen sehr alt sein können.

Systemdienste verwenden häufig RemoteCallbackList, um Remote-Callbacks zu verwalten und automatisch mit beendeten Prozessen umzugehen. Um eingefrorene Apps zu verarbeiten, erweitern Sie die vorhandene Verwendung von RemoteCallbackList, indem Sie eine Richtlinie für eingefrorene Aufgerufene anwenden, wie in RemoteCallbackList verwenden beschrieben.