HIDL C++

Android 8 では Android OS が再構築され、デバイスに依存しない Android プラットフォームと、デバイス / ベンダーに固有のコードとの間に明確なインターフェースが定義されています。このようなインターフェースは、HAL インターフェースの形式ですでに数多く定義されており、C ヘッダーとして hardware/libhardware で定義されています。さらに HIDL により、これらの HAL インターフェースが、バージョニングされた安定したインターフェースに置き換えられました。その結果、C++(後述)または Java のクライアント側 / サーバー側 HIDL インターフェースとして使用できます。

このセクションでは、hidl-gen コンパイラによって HIDL .hal ファイルから自動生成されたファイルの詳細、これらのファイルのパッケージ方法、およびこれらのファイルを使用する C++ コードとの統合方法など、HIDL インターフェースの C++ 実装について説明します。

クライアントとサーバーの実装

HIDL インターフェースには、次のようなクライアントとサーバーの実装があります。

  • HIDL インターフェースのクライアントは、メソッドを呼び出すことによりインターフェースを使用するコードです。
  • サーバーは、クライアントからの呼び出しを受信し、(必要な場合に)結果を返す HIDL インターフェースの実装です。

libhardware HAL から HIDL HAL に移行すると、HAL 実装がサーバーになり、HAL を呼び出すプロセスがクライアントになります。デフォルトの実装では、パススルーとバインドされた HAL の両方を提供し、次のように段階的に変更することが考えられます。

図 1. レガシー HAL の開発の進行

HAL クライアントの作成

最初に、次のように makefile に HAL ライブラリを含めます。

  • Make: LOCAL_SHARED_LIBRARIES += android.hardware.nfc@1.0
  • Soong: shared_libs: [ …, android.hardware.nfc@1.0 ]

次に、このように HAL ヘッダー ファイルを含めます。

#include <android/hardware/nfc/1.0/IFoo.h>

// in code:
sp<IFoo> client = IFoo::getService();
client->doThing();

HAL サーバーの作成

HAL 実装を作成するには、HAL を表す .hal ファイルが存在し、hidl-gen-Lmakefile または -Landroidbp を使用して HAL の makefile をすでに生成している必要があります(./hardware/interfaces/update-makefiles.sh は内部 HAL ファイルに対してこれを行っており、適切なリファレンスとなります)。libhardware から HAL を移行する場合は、c2hal を使用すると大部分の作業を簡単に行えます。

HAL の実装に必要なファイルを作成するには、次のように設定します。

PACKAGE=android.hardware.nfc@1.0
LOC=hardware/interfaces/nfc/1.0/default/
m -j hidl-gen
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces \
    -randroid.hidl:system/libhidl/transport $PACKAGE
hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces \
    -randroid.hidl:system/libhidl/transport $PACKAGE

HAL がパススルー モードで動作するためには、/(system|vendor|...)/lib(64)?/hw/android.hardware.package@3.0-impl(OPTIONAL_IDENTIFIER).so 内に関数 HIDL_FETCH_IModuleName が必要です(OPTIONAL_IDENTIFIER は、パススルーの実装を識別する文字列です)。パススルー モードの要件は、上記のコマンドを実行することで自動的に遵守されます。このコマンドでは android.hardware.nfc@1.0-impl ターゲットも作成されますが、任意の拡張を使用できます。たとえば、android.hardware.nfc@1.0-impl-foo-foo を使用してそれ自体を区別します。

マイナー バージョンまたは別の HAL の拡張である HAL については、ベースとなる HAL を使用してこのバイナリに名前を付ける必要があります。たとえば、android.hardware.graphics.mapper@2.1 の実装は引き続き android.hardware.graphics.mapper@2.0-impl(OPTIONAL_IDENTIFIER) というバイナリに含める必要があります。通常、この OPTIONAL_IDENTIFIER には実際の HAL バージョンを含めます。バイナリにこのような名前を付けることで、2.0 クライアントは直接取得できるようになり、2.1 クライアントは実装をアップキャストできます。

次に、スタブに機能を入力し、デーモンを設定します。デーモンコードの例(パススルーをサポート)を次に示します。

#include <hidl/LegacySupport.h>

int main(int /* argc */, char* /* argv */ []) {
    return defaultPassthroughServiceImplementation<INfc>("nfc");
}

defaultPassthroughServiceImplementation は、指定の -impl ライブラリに dlopen() を呼び出し、バインドされたサービスとして指定します。デーモンコードの例(バインドされた純粋なサービスの場合)を次に示します。

int main(int /* argc */, char* /* argv */ []) {
    // This function must be called before you join to ensure the proper
    // number of threads are created. The threadpool never exceeds
    // size one because of this call.
    ::android::hardware::configureRpcThreadpool(1 /*threads*/, true /*willJoin*/);

    sp<INfc> nfc = new Nfc();
    const status_t status = nfc->registerAsService();
    if (status != ::android::OK) {
        return 1; // or handle error
    }

    // Adds this thread to the threadpool, resulting in one total
    // thread in the threadpool. We could also do other things, but
    // would have to specify 'false' to willJoin in configureRpcThreadpool.
    ::android::hardware::joinRpcThreadpool();
    return 1; // joinRpcThreadpool should never return
}

このデーモンは通常 $PACKAGE + "-service-suffix"(たとえば android.hardware.nfc@1.0-service)に置かれますが、どこにでも配置できます。HAL の特定のクラスに対する sepolicy は、属性 hal_<module>hal_nfc) など)です。この属性は、特定の HAL を実行するデーモンに適用する必要があります(同じプロセスが複数の HAL に対応する場合、複数の属性を適用できます)。