第2章:HAL接口定义语言(HIDL)基础
HIDL,说白了就是Android用来定义硬件接口的“合同语言”。你写驱动,别人写框架,两边怎么约定数据格式、方法签名?靠的就是HIDL。我刚开始接触时也觉得它啰嗦,后来发现——没有这套规范,团队协作就是灾难。
2.1 HIDL语法与数据类型
HIDL的语法长得像C++,但更精简。每个接口文件以.hal结尾,里面定义方法和数据结构。
核心语法结构:
- 接口定义:
interface IMyDevice { ... } - 方法声明:
returnType methodName(type param1, type param2); - 回调机制:
oneway关键字用于异步调用
数据类型这块,我踩过不少坑。HIDL有自己的类型系统,不能直接用C++的int、float。
| HIDL类型 | 对应C++类型 | 说明 |
|---|---|---|
int8_t |
int8_t |
有符号8位整数 |
uint32_t |
uint32_t |
无符号32位整数 |
float |
float |
单精度浮点 |
string |
std::string |
UTF-8字符串 |
vec<T> |
std::vector<T> |
动态数组 |
hidl_array<T, N> |
std::array<T, N> |
固定长度数组 |
注意:HIDL不支持指针类型。所有数据必须通过值传递或引用传递。我见过有人试图传void*,编译直接报错。
来看一个完整的接口定义示例:
// IMySensor.hal
package vendor.example.sensor@1.0;
interface IMySensor {
// 获取传感器数据
getData() generates (vec<float> values);
// 设置采样率(oneway表示异步调用)
oneway setSamplingRate(uint32_t rateHz);
// 回调接口
notifyEvent(int32_t eventId, const hidl_string& message);
};
2.2 服务端与客户端接口定义
HIDL把接口分成了两端:服务端(实现方)和客户端(调用方)。这个设计理念很清晰——谁提供能力,谁使用能力。
服务端职责:
- 实现接口中声明的所有方法
- 注册到hwservicemanager,让客户端能找到你
- 处理跨进程调用的生命周期
客户端职责:
- 通过服务名获取服务端代理
- 调用接口方法,传递参数
- 处理返回结果或回调
我的经验:写客户端代码时,一定要做空指针检查。服务端可能没注册成功,或者进程挂了。我曾经因为没检查返回值,线上设备直接空指针崩溃。
服务端注册代码示例:
// 服务端注册
IMySensor* service = new MySensorImpl();
status_t status = service->registerAsService("default");
if (status != OK) {
ALOGE("Failed to register IMySensor service");
return -1;
}
客户端获取服务代码示例:
// 客户端获取服务
sp<IMySensor> sensor = IMySensor::getService("default");
if (sensor == nullptr) {
ALOGE("Failed to get IMySensor service");
return -1;
}
// 调用方法
vec<float> data;
sensor->getData([&](vec<float> result) {
data = result;
});
2.3 包名与版本管理
包名不是随便起的。Android有严格的命名规范:
package vendor.公司名.产品名@版本号;
举个例子:vendor.google.sensors@1.0
版本号管理是HIDL里最容易出问题的地方。我来说说规则:
- 主版本号:接口不兼容时递增(比如删了方法、改了参数类型)
- 次版本号:向后兼容时递增(比如新增方法、新增枚举值)
- 版本演进:
@1.0→@1.1→@2.0
关键原则:客户端可以调用低版本服务端,但不能调用高版本。服务端必须兼容所有低版本客户端。
版本目录结构长这样:
vendor/example/sensor/
├── 1.0/
│ ├── IMySensor.hal
│ ├── types.hal
│ └── Android.bp
└── 1.1/
├── IMySensor.hal
├── types.hal
└── Android.bp
2.4 @1.0与@1.1版本演进实战
假设我们有一个@1.0的接口,现在要升级到@1.1。怎么做?
@1.0原始接口:
// vendor/example/sensor/1.0/IMySensor.hal
package vendor.example.sensor@1.0;
interface IMySensor {
getTemperature() generates (float temp);
getHumidity() generates (float humidity);
};
@1.1扩展接口:
// vendor/example/sensor/1.1/IMySensor.hal
package vendor.example.sensor@1.1;
import vendor.example.sensor@1.0::IMySensor;
interface IMySensor extends @1.0::IMySensor {
// 新增方法
getPressure() generates (float pressure);
// 新增枚举
enum SensorStatus : int32_t {
OK,
ERROR,
CALIBRATING
};
// 新增回调
oneway onStatusChanged(SensorStatus status);
};
我曾经踩过的坑:在@1.1里修改了@1.0已有的方法签名。比如把getTemperature()的返回值从float改成double。结果所有@1.0的客户端全部编译失败。记住——版本升级只能加,不能改。
版本演进的核心流程,我用一张图来说明:
版本管理还有个细节——types.hal。公共数据类型要放在types.hal里,不同版本可以共享或扩展。
// 1.0/types.hal
package vendor.example.sensor@1.0;
struct SensorData {
float temperature;
float humidity;
int64_t timestamp;
};
// 1.1/types.hal
package vendor.example.sensor@1.1;
import vendor.example.sensor@1.0::SensorData;
struct ExtendedSensorData {
SensorData base;
float pressure;
SensorStatus status;
};
我的建议:写HIDL接口时,一开始就把版本号规划好。哪怕只做@1.0,也要预留扩展点。比如枚举值留几个reserved,结构体里加个reserved字段。这样后面升级@1.1时,改动最小。
最后说一句——HIDL的版本管理不是摆设。Android系统升级时,vendor分区可能还是旧版本,system分区已经更新了。如果版本不兼容,设备直接变砖。嗯,这个坑我替你们踩过了。