使用繫結器在程序之間通訊時,如果遠端程序處於已快取或凍結狀態,請特別留意。呼叫已快取或凍結的應用程式可能會導致應用程式當機,或是不必要地耗用資源。
快取和凍結的應用程式狀態
Android 會將應用程式維持在不同狀態,以管理記憶體和 CPU 等系統資源。
已快取狀態
如果應用程式沒有任何使用者可見的元件 (例如活動或服務),可以移至「已快取」狀態。詳情請參閱「處理程序與應用程式生命週期」。快取應用程式會保留在記憶體中,以防使用者切換回這些應用程式,但這些應用程式不應處於使用中狀態。
從一個應用程式程序繫結至另一個程序時 (例如使用 bindService),伺服器程序的程序狀態會提升至至少與用戶端程序同等重要 (除非指定 Context#BIND_WAIVE_PRIORITY)。舉例來說,如果用戶端不在快取狀態,伺服器也不會處於快取狀態。
反之,伺服器程序的狀態不會決定其用戶端的狀態。因此,伺服器可能會與用戶端建立繫結器連線 (最常見的形式是回呼),而當遠端程序處於快取狀態時,伺服器不會快取。
設計 API 時,如果回呼源自於權限提升的程序,並傳送至應用程式,請考慮在應用程式進入快取狀態時暫停回呼的調度作業,並在應用程式離開此狀態時繼續調度。這樣可避免在快取的應用程式程序中執行不必要的工作。
如要追蹤應用程式進入或離開快取狀態的時間,請使用 ActivityManager.addOnUidImportanceListener:
// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
new UidImportanceListener() { ... },
IMPORTANCE_CACHED);
凍結狀態
系統可以凍結快取的應用程式,以節省資源。應用程式凍結後,會收到零 CPU 時間,無法執行任何工作。詳情請參閱「凍結快取應用程式」。
如果程序將同步 (非 oneway) 繫結交易傳送至凍結的遠端程序,系統會終止該遠端程序。這樣可防止呼叫程序中的呼叫執行緒無限期暫停,等待遠端程序解除凍結,否則可能會導致呼叫應用程式中的執行緒資源不足或發生死鎖。
當程序將非同步 (oneway) 繫結器交易傳送至凍結的應用程式 (通常是透過通知回呼,這通常是 oneway 方法),交易會緩衝處理,直到遠端程序解除凍結為止。如果緩衝區溢位,接收端應用程式程序可能會停止運作。此外,應用程式程序解除凍結並處理緩衝交易時,這些交易可能已過時。
為避免應用程式因過時事件或緩衝區溢位而負載過重,當接收端應用程式的程序凍結時,您必須暫停分派回呼。
如要追蹤應用程式凍結或解除凍結的時間,請使用 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
RemoteCallbackList 類別可協助管理遠端程序註冊的 IInterface 回呼清單。這個類別會自動處理繫結終止通知,並提供處理回呼的選項,以便傳送至凍結的應用程式。
建構 RemoteCallbackList 時,您可以指定凍結的呼叫端政策:
FROZEN_CALLEE_POLICY_DROP:系統會無聲無息地捨棄凍結應用程式的回呼。如果應用程式在快取期間發生的事件對應用程式不重要 (例如即時感應器事件),請使用這項政策。FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT:如果應用程式凍結時廣播多個回呼,只有最近一個回呼會排入佇列,並在應用程式解除凍結時傳送。這適用於以狀態為基礎的回呼,因為只有最新的狀態更新才重要,例如通知應用程式目前媒體音量的回呼。FROZEN_CALLEE_POLICY_ENQUEUE_ALL:應用程式凍結時廣播的所有回呼都會加入佇列,並在應用程式解除凍結時傳送。請謹慎使用這項政策,因為如果排入佇列的回呼過多,可能會導致緩衝區溢位,或累積過時的事件。
以下範例說明如何建立及使用 RemoteCallbackList 執行個體,以便捨棄凍結應用程式的回呼:
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));
如果您使用 FROZEN_CALLEE_POLICY_DROP,只有在裝載回呼的程序未凍結時,系統才會叫用 callback.onSomeEvent()。
系統服務和應用程式互動
系統服務通常會使用繫結器與許多不同的應用程式互動。由於應用程式可能會進入快取和凍結狀態,系統服務必須特別注意妥善處理這些互動,以維持系統穩定性和效能。
系統服務已需要處理因各種原因而終止應用程式程序的情況。這包括代表他們停止工作,且不會嘗試繼續將回呼傳送至已終止的程序。凍結應用程式是現有監控責任的延伸。
從系統服務追蹤應用程式狀態
在 system_server 中或以原生常駐程式執行的系統服務,也可以使用先前說明的 API 追蹤應用程式程序的優先順序和凍結狀態:
ActivityManager.addOnUidImportanceListener:系統服務可以註冊監聽器,追蹤 UID 重要性變化。當服務從應用程式收到繫結呼叫或回呼時,可以使用Binder.getCallingUid()取得 UID,並將其與接聽程式追蹤的重要性狀態建立關聯。這可讓系統服務瞭解通話應用程式是否處於快取狀態。IBinder.addFrozenStateChangeCallback:當系統服務從應用程式收到繫結器物件時 (例如,做為回呼的註冊程序),應在該特定IBinder執行個體上註冊FrozenStateChangeCallback。當代管該繫結器的應用程式程序凍結或解除凍結時,這會直接通知系統服務。
系統服務建議
我們建議所有可能與應用程式互動的系統服務,都應追蹤與其通訊的應用程式程序快取和凍結狀態。否則可能會導致:
- 資源耗用:為已快取且使用者看不到的應用程式執行工作,可能會浪費系統資源。
- 應用程式當機:對凍結應用程式進行同步繫結器呼叫時,應用程式會當機。如果非同步交易緩衝區溢位,對凍結應用程式進行非同步繫結器呼叫會導致當機。
- 應用程式行為異常:應用程式解除凍結後,會立即收到凍結期間傳送給它們的任何緩衝非同步繫結器交易。應用程式可能會無限期處於凍結狀態,因此緩衝交易可能會過時。
系統服務通常會使用 RemoteCallbackList 管理遠端回呼,並自動處理終止的程序。如要處理凍結的應用程式,請按照「使用 RemoteCallbackList」一文所述,套用凍結的呼叫端政策,延長 RemoteCallbackList 的現有使用時間。