HIDL 深度指南:从接口定义到服务实现
HIDL 深度指南:从接口定义到服务实现
本文档基于工程实战整理,完整覆盖 HIDL 运作机制、源码框架、编译流程、服务注册与客户端访问,并补充线程模型、内存共享、类型映射、Return 对象等关键概念,可作为从入门到落地的第一手参考。原有已验证的脚本与代码将原样保留,并附上必要的上下文解释与纠错。
1. 概述
HIDL (HAL Interface Definition Language) 是 Android 8.0 (Project Treble) 引入的接口描述语言,用于定义硬件抽象层 (HAL) 与其用户之间的接口。HIDL 将 Android 框架 (framework) 与硬件相关实现 (vendor) 解耦,使得系统镜像可以独立于供应商实现进行更新。
核心目标
- 模块化:通过接口契约隔离系统与供应商。
- 向前兼容:版本化接口支持平滑升级。
- 多传输模式:binder 化(跨进程)与直通模式(同进程)灵活选择。
- 高效数据传递:支持零拷贝共享内存。
2. 参考资源
- HIDL Demo
- Android HIDL 实例
- Android HIDL 中 hidl-gen使用
- AndroidO Treble架构下的接口文件编译
- 服务和数据转移
- 数据类型
- 线程模型
- HIDL 内存块
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、同步返回、异步回调) - 自定义类型(
enum、struct、union) - 注释、版本化、导入其他包的类型
4. HIDL 实现步步通 (Step-by-Step)
本章以自定义包 vendor.huawei.helloworld@1.0 为例,完整演示从接口定义到服务运行的流程。
4.1 整体流程
- 生成
hidl-gen工具:make hidl-gen -j8 - 编写
.hal接口文件 - 使用
hidl-gen生成Android.bp、C++/Java 实现骨架 - 在生成的服务实现中填充逻辑
- 创建服务入口函数 (
service.cpp) - 编写启动服务的
.rc文件 - 配置 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 源码根目录运行,且已执行
source和lunch。
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 类型以实现大块数据的零拷贝传递,适用于音频、视频等场景。通过 IAllocator 或 IMapper 分配共享内存,客户端和服务端通过句柄访问同一物理内存。
工作流程:
- 服务端创建
hidl_memory对象。 - 通过 binder 调用将句柄传给客户端。
- 客户端使用
mapMemory()方法获得本地映射。
5.7 源码框架分析
AOSP 中 hardware/interfaces/ 提供了大量标准 HAL 实现,可作为开发范本:
graphics/composer:硬件合成器接口。audio:音频 HAL 接口与效应器。camera:相机设备与流配置。
其目录结构、Android.bp、update-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 → 编译 → 客户端调用
关键设计思想
- 接口与实现分离:
interface与package解耦系统与供应商。 - 版本化:
@<major>.<minor>支持向前兼容。 - 双模式:binder 化(跨进程)与直通(同进程)灵活选择。
- 无痛数据传输:
memory和handle支持零拷贝。
类型映射速查
| 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 兼容性、独立更新系统镜像的必经之路。