HIDL C++

Android O 对 Android 操作系统的架构重新进行了设计,以在独立于设备的 Android 平台与特定于设备和供应商的代码之间定义清晰的接口。Android 已经以 HAL 接口的形式(在 hardware/libhardware 中定义为 C 头文件)定义了许多此类接口。HIDL 将这些 HAL 接口替换为带版本编号的稳定接口,它们可以是采用 C++(如下所述)或 Java 的客户端和服务器端 HIDL 接口。

本部分中的几页内容介绍了 HIDL 接口的 C++ 实现,其中详细说明了 hidl-gen 编译器基于 HIDL .hal 文件自动生成的文件,这些文件如何打包,以及如何将这些文件与使用它们的 C++ 代码集成。

客户端和服务器实现

HIDL 接口具有客户端和服务器实现:

  • HIDL 接口的客户端是指通过在该接口上调用方法来使用该接口的代码。
  • 服务器是指 HIDL 接口的实现,它可接收来自客户端的调用并返回结果(如有必要)。

在从 libhardware HAL 转换为 HIDL HAL 的过程中,HAL 实现成为服务器,而调用 HAL 的进程则成为客户端。默认实现可提供直通和 Binder 化 HAL,并可能会随着时间而发生变化:

图 1. 旧版 HAL 的发展历程。

创建 HAL 客户端

首先将 HAL 库添加到 makefile 中:

  • 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 在直通模式下运行,您必须具有 HIDL_FETCH_IModuleName 函数,该函数位于 /(system|vendor|...)/lib(64)?/hw/android.hardware.package@3.0-impl(OPTIONAL_IDENTIFIER).so,其中 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() 操作,并将其作为 Binder 化服务提供。下面是一个守护进程代码(对于纯 Binder 化服务)示例:

    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 will never exceed
        // size one because of this call.
        ::android::hardware::configureRpcThreadpool(1 /*threads*/, true /*willJoin*/);

        sp 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 的特定类的 sepolicyhal_<module> 属性(例如 hal_nfc))。您必须将此属性应用到运行特定 HAL 的守护进程(如果同一进程提供多个 HAL,则可以将多个属性应用到该进程)。