2、HAL接口定义语言(HIDL):基础语法、服务端与客户端实现、passthrough与binderized模式
HIDL,全称是HAL Interface Definition Language。说白了,它就是Android系统用来定义HAL接口的一种工具。你想想看,以前Android用HAL的方式,每个厂商自己搞一套,接口五花八门。Google后来受不了了,干脆统一了规矩——这就是HIDL诞生的原因。
我个人习惯把HIDL理解成「HAL世界的契约」。你定义好这个契约,服务端(HAL实现)和客户端(Framework)各守各的规矩,谁也别乱来。这样系统才能稳定,调试起来也省心。
2.1 HIDL基础语法
HIDL的语法其实挺像C++的,但更精简。我刚开始接触的时候,觉得它就是个「瘦身版C++头文件」。咱们直接看个例子:
package android.hardware.light@2.0;
interface ILight {
enum Type : int32_t {
BACKLIGHT = 0,
KEYBOARD = 1,
BUTTONS = 2,
NOTIFICATIONS = 3
};
struct LightState {
int32_t color;
int32_t flashMode;
int32_t brightness;
};
getSupportedTypes() generates (vec<Type> types);
setLight(Type type, LightState state) generates (Status status);
};
这里有几个关键点:
- package:定义包名和版本号。注意版本号是必须的,这是HIDL的硬性要求。
- interface:接口定义,里面可以包含枚举、结构体和方法。
- enum/struct:数据类型定义,支持嵌套。
- generates:方法返回值的关键字。HIDL的方法可以有返回值,也可以没有。
HIDL支持的数据类型包括:
| 类型 | 说明 | 示例 |
|---|---|---|
| int8_t ~ int64_t | 有符号整数 | int32_t count |
| uint8_t ~ uint64_t | 无符号整数 | uint8_t flags |
| float/double | 浮点数 | float temperature |
| string | 字符串 | string name |
| vec<T> | 动态数组 | vec<uint8_t> data |
| hidl_array<T, N> | 固定大小数组 | hidl_array<int32_t, 3> rgb |
2.2 服务端与客户端实现
定义好接口文件后,接下来就是生成代码。HIDL编译器(hidl-gen)会帮你生成C++代码。嗯,这里要注意:生成的代码分两部分——服务端和客户端。
服务端实现:你写一个类继承生成的接口,然后实现里面的方法。我举个例子:
#include <android/hardware/light/2.0/ILight.h>
using namespace android::hardware::light::V2_0;
struct LightImpl : public ILight {
Return<Status> setLight(Type type, const LightState& state) override {
// 这里写你的HAL逻辑
// 比如操作GPIO、写寄存器之类的
return Status::SUCCESS;
}
Return<void> getSupportedTypes(
std::function<void(const hidl_vec<Type>&)> _hidl_cb) override {
// 回调方式返回数据
_hidl_cb({Type::BACKLIGHT, Type::NOTIFICATIONS});
}
};
客户端调用:Framework那边通过服务名获取接口,然后直接调用:
sp<ILight> service = ILight::getService();
if (service != nullptr) {
LightState state = {0xFFFFFF, 0, 255};
Status status = service->setLight(Type::BACKLIGHT, state);
}
2.3 Passthrough与Binderized模式
这是HIDL里最容易让人迷糊的地方。说白了,两种模式的区别就是「进程边界」的问题。
Binderized模式:HAL跑在独立进程里,通过Binder通信。好处是隔离性好,HAL挂了不会拖垮Framework。坏处是每次调用都有IPC开销。
Passthrough模式:HAL以共享库的形式加载到调用方进程里,直接函数调用。好处是零开销,性能好。坏处是HAL和调用方同进程,HAL崩了调用方也崩。
我画个图帮你理解:
两种模式的选择,其实是个权衡。我个人的经验是:
- 性能敏感型HAL(比如传感器、音频、相机预览)→ 用Passthrough。每次调用省掉IPC开销,延迟能降不少。
- 安全隔离型HAL(比如指纹、DRM、TEE相关)→ 用Binderized。万一HAL崩了,不影响系统其他部分。
- 调试阶段→ 先用Binderized。独立进程好调试,crash了不会带崩整个系统。
实际项目中的选择:我在做车载系统时,遇到过一个问题:音频HAL用Passthrough模式,结果音频服务偶尔crash,导致整个媒体播放器挂掉。后来改成Binderized模式,虽然延迟多了几百微秒,但稳定性大幅提升。所以,别一味追求性能,稳定性更重要。
最后说一句,HIDL的这两种模式,在Android 10之后其实有了变化。Google开始推AIDL(Android Interface Definition Language)来统一HAL和Framework的接口。但HIDL作为过渡方案,在现有系统里依然大量存在。你搞懂了HIDL,再去看AIDL,会发现很多概念是相通的。