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 im Cache gespeicherten oder eingefrorenen Zustand befindet. Aufrufe in Apps im Cache oder eingefrorenen Zustand können dazu führen, dass sie abstürzen oder unnötig Ressourcen verbrauchen.

Zustände von Apps im Cache und eingefrorenen Zustand

Android verwaltet Apps in verschiedenen Zuständen, um Systemressourcen wie Arbeitsspeicher und CPU zu verwalten.

Zustand „Im Cache“

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

Wenn Sie eine Bindung von einem App-Prozess zu einem anderen herstellen, z. B. mit bindService, wird der Prozesszustand des Serverprozesses so erhöht, dass er mindestens so wichtig ist wie der Clientprozess (außer wenn Context#BIND_WAIVE_PRIORITY angegeben ist ). Wenn sich der Client beispielsweise nicht im Cache befindet, gilt das auch für den Server.

Umgekehrt bestimmt der Zustand 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 befindet, ist der Server nicht im Cache.

Wenn Sie APIs entwerfen, bei denen Callbacks in einem Prozess mit erhöhten Berechtigungen stammen und an Apps gesendet werden, sollten Sie die Übertragung von Callbacks pausieren, wenn eine App in den Zustand „Im Cache“ wechselt, und sie wieder aufnehmen, wenn sie diesen Zustand verlässt. So vermeiden Sie unnötige Arbeit in App-Prozessen im Cache.

Verwenden Sie ActivityManager.addOnUidImportanceListener, um zu verfolgen, wann Apps in den Zustand „Im Cache“ wechseln oder ihn verlassen:

Java

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

Kotlin

// in ActivityManager or Context
activityManager.addOnUidImportanceListener({ uid, importance ->
  // ...
}, IMPORTANCE_CACHED)

Zustand „Eingefroren“

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

Wenn ein Prozess eine synchrone (nicht oneway) Binder-Transaktion an einen anderen Remote-Prozess sendet, der eingefroren ist, beendet das System den Remote-Prozess. So wird verhindert, dass der aufrufende Thread im aufrufenden Prozess unbegrenzt lange wartet, bis der Remote-Prozess wieder aktiv ist. Dies könnte zu Thread-Auslastung 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, das normalerweise eine oneway-Methode ist), wird die Transaktion gepuffert, bis der Remote-Prozess wieder aktiv ist. Wenn der Puffer überläuft, kann der App-Prozess des Empfängers abstürzen. Außerdem können gepufferte Transaktionen veraltet sein, wenn der App-Prozess wieder aktiv ist und sie verarbeitet.

Um zu vermeiden, dass Apps mit veralteten Ereignissen überlastet werden oder ihre Puffer überlaufen, müssen Sie die Übertragung von Callbacks pausieren, während der Prozess der Empfänger-App eingefroren ist.

Verwenden Sie IBinder.addFrozenStateChangeCallback, um zu verfolgen, wann Apps eingefroren oder wieder aktiv sind:

Java

// 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
}

Kotlin

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

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

// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(myExecutor) { isFrozen ->
    remoteFrozen.set(isFrozen)
}

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

C++

Für Plattformcode mit den C++-Binder-APIs:

#include <binder/Binder.h>
#include <binder/IBinder.h>

// The binder token of the remote process
android::sp<android::IBinder> binder = service->getBinder();

// Keep track of frozen state
std::atomic<bool> remoteFrozen = false;

// Define a callback class
class MyFrozenStateCallback : public android::IBinder::FrozenStateChangeCallback {
public:
    explicit MyFrozenStateCallback(std::atomic<bool>* frozenState) : mFrozenState(frozenState) {}
    void onFrozenStateChanged(bool isFrozen) override {
        mFrozenState->store(isFrozen);
    }
private:
    std::atomic<bool>* mFrozenState;
};

// Update remoteFrozen when the remote process freezes or unfreezes
if (binder != nullptr) {
    binder->addFrozenStateChangeCallback(android::sp<android::IBinder::FrozenStateChangeCallback>::make(
        new MyFrozenStateCallback(&remoteFrozen)));
}

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

RemoteCallbackList verwenden

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

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

  • FROZEN_CALLEE_POLICY_DROP: Callbacks an eingefrorene Apps werden stumm gelöscht. Verwenden Sie diese Richtlinie, wenn Ereignisse, die aufgetreten sind, während sich die App im Cache befand, für die App nicht wichtig sind, z. B. Echtzeit-Sensorereignisse.
  • FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: Wenn mehrere Callbacks übertragen werden, während eine App eingefroren ist, wird nur der letzte in die Warteschlange gestellt und übertragen, wenn die App wieder aktiv ist. Dies ist nützlich für zustandsbasierte Callbacks, bei denen nur die letzte Zustandsaktualisierung wichtig ist, z. B. ein Callback, das die App über die aktuelle Medienlautstärke informiert.
  • FROZEN_CALLEE_POLICY_ENQUEUE_ALL: Alle Callbacks, die übertragen werden, während eine App eingefroren ist, werden in die Warteschlange gestellt und übertragen, wenn die App wieder aktiv ist. 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 Anhäufung veralteter Ereignisse.

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

Java

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

Kotlin

val callbacks =
    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, der das Callback hostet, nicht eingefroren ist.

Systemdienste und App-Interaktionen

Systemdienste interagieren häufig mit vielen verschiedenen Apps über Binder. Da Apps in den Zustand „Im Cache“ und „Eingefroren“ 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. Dazu gehört, die Arbeit in ihrem Namen zu beenden und nicht zu versuchen, Callbacks an beendete Prozesse zu senden. Die Berücksichtigung von eingefrorenen Apps ist eine Erweiterung dieser bestehenden Überwachungsaufgabe.

App-Zustände von Systemdiensten verfolgen

Systemdienste, die in system_server oder als native Daemons ausgeführt werden, können auch die zuvor beschriebenen APIs verwenden, um die Wichtigkeit und den eingefrorenen Zustand 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 ein Callback von einer App erhält, kann er mit Binder.getCallingUid() die UID abrufen und sie mit dem Wichtigkeitszustand korreliert, der vom Listener verfolgt wird. So können Systemdienste feststellen, ob sich die aufrufende App im Cache befindet.

  • IBinder.addFrozenStateChangeCallback: Wenn ein Systemdienst ein Binder-Objekt von einer App erhält (z. B. im Rahmen einer Registrierung für Callbacks), sollte er ein FrozenStateChangeCallback für diese bestimmte IBinder-Instanz registrieren. So wird der Systemdienst direkt benachrichtigt, wenn der App-Prozess, der diesen Binder hostet, eingefroren oder wieder aktiv wird.

Empfehlungen für Systemdienste

Wir empfehlen, dass alle Systemdienste, die mit Apps interagieren können, den Zustand „Im Cache“ und „Eingefroren“ der App-Prozesse verfolgen , mit denen sie kommunizieren. Andernfalls kann es zu Folgendem kommen:

  • Ressourcenverbrauch:Wenn Sie Arbeit für Apps ausführen, die sich im Cache befinden und für Nutzer nicht sichtbar sind, können Systemressourcen verschwendet werden.
  • App-Abstürze:Synchrone Binder-Aufrufe an eingefrorene Apps führen zu Abstürzen. Asynchrone Binder-Aufrufe an eingefrorene Apps führen zu einem Absturz, wenn der Puffer für asynchrone Transaktionen überläuft.
  • Unerwartetes App-Verhalten:Apps, die wieder aktiv sind, erhalten sofort alle gepufferten asynchronen Binder-Transaktionen, die an sie gesendet wurden, während sie eingefroren waren. Apps können unbegrenzt lange eingefroren sein, daher können gepufferte Transaktionen sehr veraltet sein.

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