バインダを使用してプロセス間で通信する場合は、リモート プロセスがキャッシュ状態またはフリーズ状態にあるときに特に注意してください。キャッシュに保存されたアプリやフリーズされたアプリへの呼び出しは、アプリのクラッシュや不要なリソースの消費を引き起こす可能性があります。
キャッシュ保存済みアプリと凍結済みアプリの状態
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 の既存の使用を拡張します。