サービスとデータ転送

このセクションでは、.hal ファイルでインターフェースに定義されているメソッドを呼び出すことで、サービスを登録して検出し、サービスにデータを送信する方法について説明します。

サービスの登録

HIDL インターフェース サーバー(インターフェースを実装するオブジェクト)は、名前付きサービスとして登録できます。登録名をインターフェース名またはパッケージ名に関連付ける必要はありません。名前を指定しない場合は、「default」という名前が使用されます。これは、同じインターフェースの 2 つの実装を登録する必要のない HAL に使用してください。たとえば、各インターフェースに定義されているサービス登録用の C++ 呼び出しは次のようになります。

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service");  // if needed

HIDL インターフェースのバージョンはインターフェース自体に含まれています。これはサービス登録に自動的に関連付けられ、すべての HIDL インターフェースでメソッド呼び出し(android::hardware::IInterface::getInterfaceVersion())を使用して取得可能です。サーバー オブジェクトは登録不要で、サーバーへの HIDL メソッド呼び出しを行う別のプロセスに、HIDL メソッド パラメータを介して渡すことができます。

サービスの検出

名前とバージョンで指定したインターフェースに対するクライアント コードによるリクエストは、目的の HAL クラスで getService を呼び出して実行します。

// C++
sp<V1_1::IFooService> service = V1_1::IFooService::getService();
sp<V1_1::IFooService> alternateService = V1_1::IFooService::getService("another_foo_service");
// Java
V1_1.IFooService service = V1_1.IFooService.getService(true /* retry */);
V1_1.IFooService alternateService = V1_1.IFooService.getService("another", true /* retry */);

HIDL インターフェースの各バージョンは、個別のインターフェースとして扱われます。したがって、IFooService バージョン 1.1 と IFooService バージョン 2.2 はどちらも「foo_service」として登録でき、いずれかのインターフェースの getService("foo_service") で、そのインターフェースに登録されているサービスを取得します。このため、ほとんどの場合は登録または検出で名前パラメータを指定する必要はありません(「default」という名前が使われます)。

ベンダー インターフェース オブジェクトは、返されたインターフェースのトランスポート メソッドでも役立ちます。パッケージ android.hardware.foo@1.0 に含まれているインターフェース IFoo の場合、IFoo::getService によって返されるインターフェースは常に、デバイス マニフェストで android.hardware.foo に対して宣言されたトランスポート メソッドを使用します(エントリが存在する場合)。トランスポート メソッドを使用できない場合は、nullptr が返されます。

サービスが検出されなくても、すぐに処理を続ける必要がある場合もあります。たとえば、クライアントがサービス通知自体を管理する必要がある場合や、すべての hwservice の検出と取得が必要な診断プログラム(atrace など)で管理する必要がある場合にこの状況が発生します。この場合は、C++ の tryGetService や Java の getService("instance-name", false) など、追加の API が提供されます。Java で提供されている従来の API getService もサービス通知で使用する必要があります。この API を使用しても、クライアントがこれらの非リトライ API のいずれかを使用してサーバーを要求した後にサーバーが自身を登録する競合状態は回避されません。

サービス終了通知

サービスが終了したことを把握する必要があるクライアントは、フレームワークによって配信される終了通知を受け取ることができます。通知を受け取るには、クライアントで次の操作を行う必要があります。

  1. HIDL クラス / インターフェース hidl_death_recipient をサブクラス化します(HIDL ではなく C++ コード)。
  2. serviceDied() メソッドをオーバーライドします。
  3. hidl_death_recipient サブクラスのオブジェクトをインスタンス化します。
  4. モニタリングするサービスで linkToDeath() メソッドを呼び出して、IDeathRecipient のインターフェース オブジェクトを渡します。このメソッドは、終了通知受信者または呼び出し元プロキシの所有権を取得しません。

擬似コードの例(C++ と Java は類似しています):

class IMyDeathReceiver : hidl_death_recipient {
  virtual void serviceDied(uint64_t cookie,
                           wp<IBase>& service) override {
    log("RIP service %d!", cookie);  // Cookie should be 42
  }
};
....
IMyDeathReceiver deathReceiver = new IMyDeathReceiver();
m_importantService->linkToDeath(deathReceiver, 42);

同じ終了通知受診者を複数の異なるサービスに登録できます。

データ転送

.hal ファイルでインターフェースに定義されたメソッドを呼び出して、サービスにデータを送信できます。メソッドには次の 2 種類があります。

  • blocking メソッドはサーバーが結果を生成するまで待機します。
  • oneway メソッドはデータを一方向にのみ送信し、ブロックを実行しません。RPC 呼び出しで送信中のデータの量が実装の上限を超えた場合、呼び出しはブロックを実行するか、エラー通知を返します(動作は未定)。

値を返さなくても、oneway として宣言されていないメソッドは blocking です。

HIDL インターフェースで宣言されているすべてのメソッドは、HAL からまたは HAL への一方向に呼び出されます。インターフェースは呼び出しの方向を指定しません。HAL からの呼び出しを必要とするアーキテクチャは、HAL パッケージに 2 つ以上のインターフェースを用意し、各プロセスから適切なインターフェースを提供する必要があります。「クライアント」と「サーバー」という用語はインターフェースの呼び出し方向に対して使用されます。つまり、HAL は 1 つのインターフェースのサーバーであると同時に、別のインターフェースのクライアントです。

コールバック

「コールバック」という用語は、同期コールバックと非同期コールバックで区別される 2 つの概念を指します。

同期コールバックは、データを返す一部の HIDL メソッドで使用されます。複数の値を返す(または非プリミティブ型の値を 1 つ返す)HIDL メソッドは、コールバック関数によって結果を返します。値が 1 つだけ返されるプリミティブ型の場合、コールバックは使用されずにメソッドから値が返されます。サーバーは HIDL メソッドを実装し、クライアントはコールバックを実装します。

非同期コールバックでは、HIDL インターフェースのサーバーが呼び出しを開始できます。この場合は、2 番目のインターフェースのインスタンスが最初のインターフェースに渡されます。最初のインターフェースのクライアントは、2 番目のインターフェースのサーバーとして動作する必要があります。最初のインターフェースのサーバーは、2 番目のインターフェース オブジェクトでメソッドを呼び出すことができます。たとえば、HAL 実装はプロセスによって作成および処理されたインターフェース オブジェクトでメソッドを呼び出して、そのプロセスが使用する情報を非同期的にプロセスに送信できます。非同期コールバックに使用されるインターフェースのメソッドは、blocking(呼び出し元に値を返す場合がある)である可能性も、oneway である可能性もあります。例については、HIDL C++ の「非同期コールバック」をご覧ください。

メモリ所有権をシンプルにするために、メソッド呼び出しとコールバックでは in パラメータのみが使用され、out または inout パラメータはサポートされません。

トランザクションごとの制限

トランザクションごとの制限は、HIDL メソッドとコールバックで送信されるデータの量には適用されません。ただし、トランザクションあたり 4 KB を超える呼び出しは過剰と見なされます。この場合は、特定の HIDL インターフェースを再構築することをおすすめします。複数の同時トランザクションを処理する HIDL インフラストラクチャで使用できるリソースにも制限があります。複数のスレッドまたはプロセスが 1 つのプロセスに呼び出しを送信したり、複数の oneway 呼び出しが受信プロセスによって迅速に処理されなかったりすることが原因で、複数のトランザクションが同時に実行される可能性があります。すべての同時トランザクションで使用できる最大合計容量は、デフォルトで 1 MB です。

適切に設計されたインターフェースでは、これらのリソース制限を超えることはありません。仮に超えた場合、超過した呼び出しはリソースが使用可能になるまでブロックを実行するか、トランスポート エラーを発します。トランザクションごとの制限を超過したり、実行中のトランザクションの集約によって HIDL 実装リソースのオーバーフローが発生したりすると、円滑なデバッグを目的としてすべてログに記録されます。

メソッドの実装

HIDL は、ターゲット言語(C++ または Java)で必要な型、メソッド、コールバックを宣言するヘッダー ファイルを生成します。HIDL 定義のメソッドとコールバックのプロトタイプは、クライアント コードとサーバーコードで変わりません。HIDL システムは、IPC トランスポートのデータを整理するメソッドのプロキシ実装を呼び出し元に提供し、呼び出し先にはメソッドのデベロッパー実装にデータを渡すスタブコードを提供します。

関数(HIDL メソッドまたはコールバック)の呼び出し元は、関数に渡されたデータ構造の所有権を持ち、呼び出し後も所有権を保持します。どのような場合も、呼び出し先がストレージを解放する必要はありません。

  • C++ のデータは読み取り専用の場合があり(書き込みを試行するとセグメンテーション違反の原因になることがある)、呼び出しの間は有効です。クライアントはデータをディープコピーして呼び出し後に伝播できます。
  • Java のコードは、データのローカルコピー(通常の Java オブジェクト)を受け取ってから、保持して変更したり、ガベージ コレクションの対象にしたりすることが可能です。

RPC 以外のデータ転送

HIDL で RPC 呼び出しを使用せずにデータを転送するには、共有メモリと高速メッセージ キュー(FMQ)の 2 つの方法があります。どちらの方法も C++ でのみサポートされています。

  • 共有メモリ。組み込みの HIDL 型 memory は、割り当てられた共有メモリを表すオブジェクトを渡すために使用されます。受信プロセスで共有メモリをマッピングするために使用できます。
  • 高速メッセージ キュー(FMQ)。HIDL は、待ち時間なしのメッセージの受け渡しを実装する、テンプレート化されたメッセージ キュー タイプを提供します。カーネルまたはスケジューラは、パススルー モードまたはバインダー化モードでは使用されません(デバイス間通信にはこれらのプロパティがありません)。通常、HAL はキューの最後を設定し、組み込みの HIDL 型 MQDescriptorSync または MQDescriptorUnsync のパラメータで RPC を介して渡すことができるオブジェクトを作成します。受信プロセスはこのオブジェクトを使用して、キューのもう一方の最後を設定できます。
    • 同期キューはオーバーフローできず、1 つのリーダーのみを指定できます
    • 非同期キューはオーバーフローが可能で、多くのリーダーを指定できます。各リーダーは、データが失われないうちに、一定時間内に読み取る必要があります
    いずれのタイプもアンダーフローは許可されず(空のキューからの読み取りは失敗する)、各タイプで 1 つのライターのみを指定できます。

FMQ の詳細については、高速メッセージ キュー(FMQ)をご覧ください。