處理快取和凍結的應用程式

使用繫結器在程序之間通訊時,如果遠端程序處於快取或凍結狀態,請特別留意。呼叫已快取或凍結的應用程式可能會導致應用程式當機,或是不必要地耗用資源。

快取和凍結的應用程式狀態

Android 會將應用程式維持在不同狀態,以便管理記憶體和 CPU 等系統資源。

已快取狀態

如果應用程式沒有任何使用者可見的元件 (例如活動或服務),可以移至「已快取」狀態。詳情請參閱「處理程序與應用程式生命週期」。快取應用程式會保留在記憶體中,以防使用者切換回這些應用程式,但預期不會主動運作。

從一個應用程式程序繫結至另一個程序時 (例如使用 bindService),伺服器程序的程序狀態會提升至至少與用戶端程序同等重要 (除非指定 Context#BIND_WAIVE_PRIORITY)。舉例來說,如果用戶端不在快取狀態,伺服器也不會處於快取狀態。

反之,伺服器程序的狀態不會決定其用戶端的狀態。因此,伺服器可能會與用戶端建立繫結器連線 (最常見的形式是回呼),而遠端程序處於快取狀態時,伺服器不會快取。

設計 API 時,如果回呼源自於權限提升的程序,並傳送至應用程式,請考慮在應用程式進入快取狀態時暫停傳送回呼,並在應用程式離開此狀態時繼續傳送。這樣可避免在快取的應用程式程序中執行不必要的工作。

如要追蹤應用程式進入或離開快取狀態的時間,請使用 ActivityManager.addOnUidImportanceListener

Java

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

Kotlin

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

凍結狀態

系統可以凍結快取的應用程式,以節省資源。應用程式凍結後,CPU 時間會歸零,無法執行任何工作。詳情請參閱「凍結快取應用程式」。

如果程序將同步 (非 oneway) 繫結交易傳送至凍結的遠端程序,系統會終止該遠端程序。這樣可防止呼叫程序中的呼叫執行緒無限期暫停,等待遠端程序解除凍結,否則可能會導致呼叫應用程式中的執行緒資源不足或發生死鎖。

當程序將非同步 (oneway) 繫結器交易傳送至凍結的應用程式 (通常是透過通知回呼,這通常是 oneway 方法),交易會緩衝處理,直到遠端程序解除凍結為止。如果緩衝區溢位,接收端應用程式程序可能會停止運作。此外,應用程式程序解除凍結並處理緩衝交易時,這些交易可能已過時。

為避免應用程式因過時事件或緩衝區溢位而負載過重,在接收端應用程式的程序凍結時,您必須暫停分派回呼。

如要追蹤應用程式凍結或解除凍結的時間,請使用 IBinder.addFrozenStateChangeCallback

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++

如果是使用 C++ 繫結 API 的平台程式碼:

#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

RemoteCallbackList 類別可協助管理遠端程序註冊的 IInterface 回呼清單。這個類別會自動處理繫結終止通知,並提供處理回呼的選項,以傳送至凍結的應用程式。

建構 RemoteCallbackList 時,您可以指定凍結的呼叫端政策:

  • FROZEN_CALLEE_POLICY_DROP:系統會無聲無息地捨棄對凍結應用程式的回呼。 如果應用程式在快取期間發生的事件對應用程式不重要 (例如即時感應器事件),請使用這項政策。
  • FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT:如果應用程式凍結時廣播多個回呼,則只有最近一個回呼會排入佇列,並在應用程式解除凍結時傳送。這適用於以狀態為基礎的回呼,因為只有最新的狀態更新才重要,例如通知應用程式目前媒體音量的回呼。
  • FROZEN_CALLEE_POLICY_ENQUEUE_ALL:應用程式凍結時廣播的所有回呼都會加入佇列,並在應用程式解除凍結時傳送。請謹慎使用這項政策,因為如果排入佇列的回呼過多,可能會導致緩衝區溢位,或累積過時的事件。

以下範例說明如何建立及使用 RemoteCallbackList 執行個體,以便捨棄凍結應用程式的回呼:

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

如果您使用 FROZEN_CALLEE_POLICY_DROP,只有在裝載回呼的程序未凍結時,系統才會叫用 callback.onSomeEvent()

系統服務和應用程式互動

系統服務通常會使用繫結器與許多不同的應用程式互動。由於應用程式可能會進入快取和凍結狀態,系統服務必須特別注意如何妥善處理這些互動,以維持系統穩定性和效能。

系統服務已需要處理因各種原因而終止應用程式程序的情況。這包括代表他們停止工作,且不會嘗試繼續將回呼傳送至已終止的程序。凍結應用程式的考量是現有監控責任的延伸。

從系統服務追蹤應用程式狀態

system_server 中或以原生常駐程式執行的系統服務,也可以使用先前說明的 API 追蹤應用程式程序的優先順序和凍結狀態:

  • ActivityManager.addOnUidImportanceListener:系統服務可以註冊接聽程式,追蹤 UID 重要性變化。當服務從應用程式收到繫結呼叫或回呼時,可以使用 Binder.getCallingUid() 取得 UID,並將其與接聽程式追蹤的重要性狀態建立關聯。這可讓系統服務瞭解通話應用程式是否處於快取狀態。

  • IBinder.addFrozenStateChangeCallback:當系統服務從應用程式收到繫結器物件時 (例如,做為回呼的註冊程序的一部分),應在該特定 IBinder 執行個體上註冊 FrozenStateChangeCallback。當代管該繫結器的應用程式程序凍結或解除凍結時,這會直接通知系統服務。

系統服務建議

我們建議所有可能與應用程式互動的系統服務,都應追蹤與其通訊的應用程式程序快取和凍結狀態。否則可能會導致:

  • 資源耗用:為已快取且使用者看不到的應用程式執行工作,可能會浪費系統資源。
  • 應用程式當機:對凍結應用程式進行同步繫結器呼叫時,應用程式會當機。如果非同步交易緩衝區溢位,對凍結應用程式進行非同步繫結器呼叫會導致當機。
  • 非預期的應用程式行為:應用程式解除凍結後,會立即收到凍結期間傳送給應用程式的所有緩衝非同步繫結器交易。應用程式可能會無限期處於凍結狀態,因此緩衝交易可能會過時。

系統服務通常會使用 RemoteCallbackList 管理遠端回呼,並自動處理終止的程序。如要處理凍結的應用程式,請按照「使用 RemoteCallbackList」一文所述,套用凍結的呼叫端政策,延長 RemoteCallbackList 的現有使用時間。