HIDL 深度指南:从接口定义到服务实现

HIDL 深度指南:从接口定义到服务实现

本文档基于工程实战整理,完整覆盖 HIDL 运作机制、源码框架、编译流程、服务注册与客户端访问,并补充线程模型、内存共享、类型映射、Return 对象等关键概念,可作为从入门到落地的第一手参考。原有已验证的脚本与代码将原样保留,并附上必要的上下文解释与纠错。


1. 概述

HIDL (HAL Interface Definition Language) 是 Android 8.0 (Project Treble) 引入的接口描述语言,用于定义硬件抽象层 (HAL) 与其用户之间的接口。HIDL 将 Android 框架 (framework) 与硬件相关实现 (vendor) 解耦,使得系统镜像可以独立于供应商实现进行更新。

核心目标

  • 模块化:通过接口契约隔离系统与供应商。
  • 向前兼容:版本化接口支持平滑升级。
  • 多传输模式:binder 化(跨进程)与直通模式(同进程)灵活选择。
  • 高效数据传递:支持零拷贝共享内存。

2. 参考资源


3. 软件包与接口

3.1 软件包命名与目录位置

软件包是 HIDL 接口的逻辑组织单元,命名与文件系统路径强关联。

软件包前缀 位置
android.hardware.* hardware/interfaces/*
android.frameworks.* frameworks/hardware/interfaces/*
android.system.* system/hardware/interfaces/*
android.hidl.* system/libhidl/transport/*
vendor.xxx.hardware.* vendor/xxx/interfaces/*

android.hardware 为例:

  • .hal 文件路径:hardware/interfaces/<module>/[<submodule>]/<ver-major.ver-minor>/
  • 文件头必须声明: package android.hardware.<module>.[<submodule>]@<ver-major.ver-minor>;

hidl-gen 的 -r 参数:用于指定包根目录到物理路径的映射。例如:

hidl-gen -r android.hardware:hardware/interfaces ...

若包为 vendor.awesome.foo@1.0::IFoo,且通过 -r vendor.awesome:some/device/independent/path/interfaces 映射,则接口文件应位于:

$ANDROID_BUILD_TOP/some/device/independent/path/interfaces/foo/1.0/IFoo.hal

3.2 接口与代码样式

.hal 文件必须遵循 HIDL 代码样式指南。基本元素包括:

  • 方法声明(支持单向 oneway、同步返回、异步回调)
  • 自定义类型(enumstructunion
  • 注释、版本化、导入其他包的类型

4. HIDL 实现步步通 (Step-by-Step)

本章以自定义包 vendor.huawei.helloworld@1.0 为例,完整演示从接口定义到服务运行的流程。

4.1 整体流程

  1. 生成 hidl-gen 工具:make hidl-gen -j8
  2. 编写 .hal 接口文件
  3. 使用 hidl-gen 生成 Android.bp、C++/Java 实现骨架
  4. 在生成的服务实现中填充逻辑
  5. 创建服务入口函数 (service.cpp)
  6. 编写启动服务的 .rc 文件
  7. 配置 SELinux 策略

4.2 源码框架设计

项目目录结构(已实际验证):

android$ tree vendor/huawei/
vendor/huawei/
└── interfaces
    ├── helloworld
    │   └── 1.0
    │       ├── IHelloWorld.hal
    │       └── types.hal
    ├── update-files.sh
    └── update-makefiles.sh

核心文件内容:

  • IHelloWorld.hal

    package vendor.huawei.helloworld@1.0;
    
    interface IHelloWorld {
        justTest(string name) generates (string result, HelloTest value);
        justTest1(HelloTest name);
    };
    
  • types.hal

    package vendor.huawei.helloworld@1.0;
    
    enum HelloTest : uint8_t {
        V_TEST1 = 0,
        V_TEST2 = 1,
    };
    

4.3 使用 hidl-gen 生成骨架

脚本 update-files.sh:生成 C++ 实现骨架及编译规则。

#!/bin/bash
LOC=vendor/huawei/interfaces/helloworld/1.0/default/
PACKAGE="vendor.huawei.helloworld@1.0"
# make hidl-gen -j8
hidl-gen -o $LOC -Lc++-impl -rvendor.huawei:vendor/huawei/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
hidl-gen -o $LOC -Landroidbp-impl -rvendor.huawei:vendor/huawei/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE

执行后将在 default/ 下生成:

  • HelloWorld.cpp :服务实现存根
  • HelloWorld.h :头文件
  • Android.bp :实现库的编译规则

执行位置:必须在 Android 源码根目录运行,且已执行 sourcelunch

4.4 更新全局 Makefile

脚本 update-makefiles.sh 生成接口本身的 Android.bp

#!/bin/bash
source $ANDROID_BUILD_TOP/system/tools/hidl/update-makefiles-helper.sh

do_makefiles_update \
  "vendor.huawei:vendor/huawei/interfaces" \
  "android.hidl:system/libhidl/transport"

执行后会在 helloworld/1.0/ 下创建 Android.bp

4.5 编译并解决包根路径问题

首次编译接口时常见错误:

error: ... Cannot find package root specification for package root 'vendor.huawei' ...

解决方法:在 helloworld/1.0/Android.bp 中添加:

hidl_package_root {
    name: "vendor.huawei",
    path: "vendor/huawei/interfaces",
}

然后执行 mmm vendor/huawei/interfaces/helloworld/1.0/ 编译。

编译产物概览(位于 out/soong/.intermediates/vendor/huawei/interfaces/helloworld/1.0/):

  • vendor.huawei.helloworld@1.0.so : Binder 化客户端库
  • vendor.huawei.helloworld@1.0-impl.so : 服务实现库
  • vendor.huawei.helloworld@1.0-adapter-helper.so : 直通模式辅助库
  • Java 库 vendor.huawei.helloworld-V1.0-java.jar
  • 生成的头文件(gen/ 目录),如 BnHwHelloWorld.h, BpHwHelloWorld.h

这些产物对应 HIDL 开发各阶段的目标文件:客户端链接 .so,服务实现需链接 -impl.so,Java 层访问则用 jar 包。

4.6 实现服务端与启动配置

default/ 目录下添加 service.cpp.rc 文件。

service.cpp 使用 defaultPassthroughServiceImplementation 注册服务,同时支持 binder 和直通模式。

#define LOG_TAG "vendor.huawei.helloworld@1.0-service"
#include <vendor/huawei/helloworld/1.0/IHelloWorld.h>
#include <hidl/LegacySupport.h>

using vendor::huawei::helloworld::V1_0::IHelloWorld;
using android::hardware::defaultPassthroughServiceImplementation;

int main() {
    return defaultPassthroughServiceImplementation<IHelloWorld>();
}

vendor.huawei.helloworld@1.0-service.rc 原笔记中接口名 Ihelloworld 大小写错误,应为 IHelloWorld

service vendor.huawei.helloworld /vendor/bin/hw/vendor.huawei.helloworld@1.0-service
    interface vendor::huawei::helloworld@1.0::IHelloWorld default
    class hal
    user system
    group system

default/Android.bp 中添加编译规则

cc_binary {
   name: "vendor.huawei.helloworld@1.0-service",
   defaults: ["hidl_defaults"],
   proprietary: true,
   relative_install_path: "hw",
   srcs: ["service.cpp"],
   init_rc: ["vendor.huawei.helloworld@1.0-service.rc"],
   shared_libs: [
       "libhidlbase",
       "libhidltransport",
       "libutils",
       "liblog",
       "vendor.huawei.helloworld@1.0",
       "vendor.huawei.helloworld@1.0-impl",
   ],
}

4.7 客户端测试程序

创建 test/HelloWorldTest.cpp 和对应 Android.bp

#include <vendor/huawei/helloworld/1.0/IHelloWorld.h>
#include <hidl/Status.h>
#include <hidl/LegacySupport.h>
#include <hidl/HidlSupport.h>
#include <stdio.h>

using vendor::huawei::helloworld::V1_0::IHelloWorld;
using android::sp;
using android::hardware::hidl_string;

int main() {
    android::sp<IHelloWorld> service = IHelloWorld::getService();
    if(service == nullptr) {
        printf("Failed to get service\n");
        return -1;
    }
    // 异步调用示例
    // service->justTest("test", [&](hidl_string result, HelloTest value) {
    //     printf("result: %s, enum: %hhu\n", result.c_str(), static_cast<uint8_t>(value));
    // });
    return 0;
}

test/Android.bp 链接所需库即可。

4.8 配置 manifest 文件

hwservicemanager 依赖 manifest 文件来发现服务。在设备 manifest 中添加:

<hal format="hidl">
    <name>vendor.huawei.helloworld</name>
    <transport>hwbinder</transport>
    <version>1.0</version>
    <interface>
        <name>IHelloWorld</name>
        <instance>default</instance>
    </interface>
</hal>

该文件通常位于 device/<vendor>/<product>/manifest.xml

常见错误

getTransport: Cannot find entry vendor.weiluocn.BinderUtils

表示 manifest.xml 中未声明对应 HIDL 服务或实例名不匹配,请核对包名、版本与实例。


5. 核心机制详析

5.1 hidl-gen 命令详解

hidl-gen 是 HIDL 开发的核心工具,常用参数:

  • -L : 输出语言/类型,如 c++-impl(实现骨架)、androidbp(编译规则)、java 等。
  • -o : 输出目录。
  • -r : 包前缀与路径映射,可重复使用。
  • 输入格式:package@version::interface 或整个包名。

5.2 生成代码的继承体系

IHelloWorld 为例,生成的核心类:

  • IHelloWorld :客户端代理基类。
  • BpHelloWorld (Binder proxy) :binder 代理具体实现。
  • BnHelloWorld (Binder native) :服务端 binder 桩。
  • IHwHelloWorld :HIDL 硬件绑定模板。
  • BsHelloWorld (Binder server) :服务实现包装。

直通模式 (Passthrough):服务运行在调用者进程,无 binder 开销。defaultPassthroughServiceImplementation 会先尝试 binder 注册,失败则回退到直通模式。

5.3 HIDL 语法与类型映射

HIDL 定义了一套独立于语言的数据类型系统,与 C++ 的映射需严格遵循。

基础类型

HIDL 类型 C++ 等价类型 说明
int8_t int8_t
uint16_t uint16_t
int32_t int32_t
uint64_t uint64_t
float float
double double
string hidl_string UTF-8 字符串
vec<T> hidl_vec<T> 动态数组,使用 setToExternal() 可引用外部内存
enum 枚举类,底层类型由 : 指定 默认 int32_t,也可声明 uint8_t
struct C++ 结构体 支持嵌套,注意内存对齐
union 联合体 需标记 discriminated 并使用安全访问
memory hidl_memory 共享内存块,用于高效数据传输
handle native_handle_t* 原生句柄

枚举定义示例

enum MyEnum : uint8_t {
    A = 0,
    B = 1,
};

生成的 C++ 代码包含 operator==、数组大小等辅助方法。

struct 内存对齐

HIDL 的 struct 布局与 C 语言一致,成员按声明顺序排列,遵循自然对齐规则。在跨平台通信时需确保兼容。

vec 使用技巧

  • 避免频繁拷贝:使用 hidl_vec::setToExternal() 指向已有数据。
  • 接收大数组:使用 const hidl_vec<T>& 或直接传递 hidl_vec(所有权转移)。

更完整的类型参考见 官方数据类型

5.4 返回值 Return 对象

HIDL 方法通常返回 Return<T>Return<void>,该对象封装了 Binder 调用的传输状态和返回值。

正确使用模式

Return<ResultType> result = service->method(args);
if (!result.isOk()) {
    // 传输失败:服务端崩溃、binder 断开、权限不足等
    return ERR_FAILED;
}
ResultType val = result; // 或 result.withDefault(defaultVal);

关键点

  • Return::isOk() 判断通信是否成功,失败时返回值不可用。
  • Return<T> 支持隐式转换为 T(可能 crash),推荐显式检查。
  • 异步回调返回 Return<void>,同样需检查 isOk()

5.5 线程模型

HIDL 服务可运行在不同线程模式下:

  • 单独线程:每个接口实例运行在自己的线程中,方法调用串行执行。
  • 线程池:服务可指定共享线程池,方法可并发执行。
  • 直通模式:运行在调用者线程,可能阻塞调用者。

通过 interface 注解或在 .rc 文件中配置线程策略。详见 线程模型

5.6 内存共享 (MemoryBlock)

HIDL 支持 memory 类型以实现大块数据的零拷贝传递,适用于音频、视频等场景。通过 IAllocatorIMapper 分配共享内存,客户端和服务端通过句柄访问同一物理内存。

工作流程:

  1. 服务端创建 hidl_memory 对象。
  2. 通过 binder 调用将句柄传给客户端。
  3. 客户端使用 mapMemory() 方法获得本地映射。

5.7 源码框架分析

AOSP 中 hardware/interfaces/ 提供了大量标准 HAL 实现,可作为开发范本:

  • graphics/composer:硬件合成器接口。
  • audio:音频 HAL 接口与效应器。
  • camera:相机设备与流配置。

其目录结构、Android.bpupdate-makefiles.sh 等均可直接复用为自定义接口的模板。


6. 调试与排错

  • 语法检查hidl-gen -L check package@version::Interface
  • 服务列表lshal 列出所有 treble HAL 服务。
  • binder 调试service call <service> <code>
  • 内核日志dmesg | grep hidl 查看服务启动失败信息。
  • 常见问题
    • getTransport: Cannot find entry:检查 manifest.xml。
    • 编译缺少 hidl_package_root:在顶级 Android.bp 添加该模块。
    • 类型不匹配:参考官方类型映射表,注意枚举底层类型。

总结卡片

HIDL 开发核心流程

编写.hal → hidl-gen 生成骨架 → 实现服务函数 → 编写 service 入口 + rc
         → 配置 Android.bp → 配置 manifest.xml → 编译 → 客户端调用

关键设计思想

  • 接口与实现分离interfacepackage 解耦系统与供应商。
  • 版本化@<major>.<minor> 支持向前兼容。
  • 双模式:binder 化(跨进程)与直通(同进程)灵活选择。
  • 无痛数据传输memoryhandle 支持零拷贝。

类型映射速查

HIDL C++
string hidl_string
vec<T> hidl_vec<T>
enum 枚举类,: 后为底层类型
struct 结构体,C 对齐规则
memory hidl_memory

排错口诀

  • “服务拿不到,先查 manifest”
  • “编译通不过,检查 hidl_package_root
  • “方法调用崩溃,检查 Return::isOk()
  • “类型对不上,翻阅官方类型映射表”

掌握 HIDL 即掌握了 Android 硬件抽象层的“大动脉”,是通向 GSI/CTS 兼容性、独立更新系统镜像的必经之路。