3. HIDL核心概念
HIDL(HAL Interface Definition Language)是 Android 8.0 到 Android 12 之间 HAL 层的主要接口描述语言。它把原本以 C 头文件形式散落在各处的 HAL 接口,抽象为可版本化、可跨进程、可校验的接口契约。理解 HIDL 的四个核心概念——接口定义、hal 文件语法、客户端/服务端模型、直通与绑定模式——是后续迁移到 AIDL 的基础,因为 AIDL 迁移本质上是在保留这些语义的前提下更换描述语言与运行时。
一、HIDL 接口定义:契约先行的设计思想
HIDL 的核心思想是“接口与实现分离”。一个 HAL 模块对外暴露什么能力,由 .hal 文件描述;具体由谁实现、运行在哪个进程、用什么语言实现,则由实现方决定。接口定义包含包名、版本、接口名、方法签名、数据类型和回调。包名通常采用 android.hardware.xxx@1.0 形式,其中 @1.0 是主版本.次版本,主版本不兼容,次版本向后兼容。
接口定义一旦发布,就应保持稳定。新增方法只能通过次版本升级,不能修改已有方法的参数或返回值。这种约束让 Framework 与 Vendor 可以独立演进,也是 HIDL 相比传统 C HAL 最大的工程价值。
二、hal 文件语法:从类型到接口
.hal 文件使用类 C++ 的语法,但只允许 HIDL 支持的类型。常见类型包括基本类型(int32_t、uint64_t、float、bool、string)、枚举 enum、结构体 struct、联合体 union、向量 vec<T>、数组 T[N]、内存 memory、句柄 handle 以及接口类型本身。方法可以声明为单向 oneway,表示调用方不等待返回。
package android.hardware.light@2.0;
interface ILight {
enum Type : uint32_t {
BACKLIGHT = 0,
KEYBOARD = 1,
BUTTONS = 2,
};
struct LightState {
Type type;
uint32_t color;
uint32_t flashMode;
uint32_t flashOnMs;
uint32_t flashOffMs;
uint32_t brightnessMode;
};
setLightState(LightState state) generates (bool success);
getSupportedTypes() generates (vec<Type> types);
};
语法要点:每个方法必须显式写出 generates 返回类型,无返回值也要写 generates ();oneway 方法不能有返回值;接口可以继承其他接口;所有类型必须定义在包内或使用 HIDL 内置类型。编译时 hidl-gen 会校验这些规则,并生成 C++、Java 等语言的桩代码。
三、客户端与服务端模型
HIDL 采用典型的代理/桩(Proxy/Stub)模型。服务端实现接口并注册到 hwservicemanager,客户端通过 getService() 获取代理对象,调用方法时由 HIDL 运行时完成序列化、跨进程传输和反序列化。客户端与服务端可以位于不同进程,也可以位于同一进程,这取决于运行模式。
服务端注册通常发生在 hw_get_module 或 registerAsService 阶段;客户端通过 ILight::getService() 拿到 sp<ILight>,随后像调用本地对象一样调用方法。HIDL 运行时负责把参数打包为 Parcel,通过 Binder 驱动传递,服务端解包后调用真实实现,再把结果回传。
四、直通模式与绑定模式
直通模式(Passthrough)下,HAL 实现被编译为共享库,客户端通过 dlopen 加载并直接调用函数,不经过 Binder。它适合启动早期、Binder 尚未就绪的场景,也适合对延迟敏感的 HAL。绑定模式(Binderized)下,HAL 运行在独立进程,通过 Binder 通信,具备进程隔离、权限控制和崩溃隔离能力,是 Android 8.0 之后推荐的方式。
两种模式并非互斥。同一个 HAL 可以同时提供直通实现和绑定实现,由 hwservicemanager 或 hw_get_module 根据系统属性、设备配置和启动阶段选择。迁移到 AIDL 时,AIDL 同样支持直通与绑定,但直通在 AIDL 中逐渐弱化,因此理解 HIDL 的两种模式有助于判断迁移后哪些 HAL 需要改为绑定模式。
| 对比维度 | 直通模式 | 绑定模式 |
|---|---|---|
| 通信方式 | 同进程直接函数调用 | Binder IPC 跨进程调用 |
| 进程隔离 | 无,与客户端同进程 | 有,独立 HAL 进程 |
| 启动时机 | 可早于 Binder 就绪 | 需 hwservicemanager 就绪 |
| 崩溃影响 | 可能拖垮客户端进程 | 仅 HAL 进程崩溃,可重启 |
| 典型场景 | Bootloader、早期显示、传感器 | Camera、Audio、Light 等常规 HAL |
| AIDL 迁移建议 | 优先评估是否可改为绑定 | 直接对应 AIDL Binder 模式 |
五、案例:Light HAL 的接口与调用
以 android.hardware.light@2.0 为例,服务端实现 ILight 并注册为 default 实例;客户端通过 ILight::getService() 获取代理,调用 setLightState() 控制背光。若该 HAL 以直通模式编译,客户端会加载 android.hardware.light@2.0-impl.so 并直接调用;若以绑定模式运行,则通过 Binder 发送到独立进程。迁移到 AIDL 后,接口变为 android.hardware.light.ILight,方法签名和语义基本保留,但类型系统、版本管理和注册方式发生变化。
六、常见误区与复盘
- 误区一:把
.hal当成普通头文件。.hal是接口描述,不能直接包含实现代码,也不能随意使用 C++ 标准库类型。 - 误区二:认为直通模式一定更快。 直通省去 IPC 开销,但失去隔离;绑定模式虽有 IPC 成本,却换来稳定性和可维护性。
- 误区三:忽略版本兼容规则。 主版本升级意味着不兼容,客户端必须同步更新;次版本升级才可向后兼容。
- 误区四:迁移时直接照搬 HIDL 类型。 AIDL 对
vec、memory、handle等类型有不同表达,需要逐项映射。