4. AIDL HAL 实战:从 HIDL 到 AIDL 的演进之路
这一章我们来聊聊 AIDL for HAL。说实话,这个变化挺大的。我记得刚接触 Android 时,HAL 层还是用传统的 hw_module_t 结构体,后来 Google 推出了 HIDL,现在又全面转向 AIDL。很多人问我:「为什么折腾来折腾去?」
嗯,其实原因很简单——生态统一。你想想看,Android 系统里本来就有 AIDL 用于应用层 IPC,现在 HAL 层也用同一套,工具链、序列化、稳定性保障都能复用。说白了,就是少维护一套东西。
核心观点:AIDL for HAL 不是简单的语法替换,而是 Android 系统走向「统一 IPC 框架」的关键一步。它让 HAL 开发变得更像普通 Android 开发,降低了学习曲线。
4.1 AIDL for HAL 的演进背景
我们先回顾一下历史。Android 8.0 之前,HAL 层用的是 传统 HAL,也就是 hw_module_t 这种 C 结构体。问题是,这种设计没有进程隔离——HAL 运行在系统服务进程里,一旦崩溃,整个系统跟着挂。
Android 8.0 引入了 Treble 架构,核心就是 HIDL。HIDL 把 HAL 服务化,每个 HAL 跑在独立进程里,通过 Binder 通信。这解决了稳定性问题,但也带来了新麻烦:
- 学习成本高:HIDL 语法和 AIDL 不一样,开发者要学两套东西
- 工具链割裂:HIDL 的
hidl-gen和 AIDL 的aidl是两套生成器 - 生态不统一:应用层用 AIDL,HAL 层用 HIDL,中间还要做转换
所以从 Android 12 开始,Google 正式推荐用 AIDL for HAL。我在项目中遇到过这种情况:团队里新来的同事刚学会 HIDL,结果发现新项目又要切 AIDL,确实有点折腾。但长远看,这是对的。
我的建议:新项目直接用 AIDL for HAL。老项目如果还在维护期,可以逐步迁移。不要同时维护两套接口定义,那会把人逼疯。
4.2 AIDL 接口定义与 HIDL 的差异
语法上,AIDL 和 HIDL 确实很像,但细节差异不少。我整理了一个对比表,方便你快速对照:
| 对比维度 | HIDL | AIDL for HAL |
|---|---|---|
| 文件后缀 | .hal |
.aidl |
| 包名规则 | android.hardware.xxx@1.0 |
android.hardware.xxx.IVendor |
| 接口定义 | interface IMyHAL { ... } |
interface IMyHAL { ... } |
| 方法返回值 | 通过 generate 回调 |
直接返回值或 oneway |
| 数据类型 | HIDL 原生类型 + vec<T> |
AIDL 原生类型 + List<T> |
| 服务注册 | registerAsService() |
ServiceManager.addService() |
| 稳定性级别 | 通过版本号管理 | 通过 @VintfStability 注解 |
举个例子,HIDL 里定义一个获取传感器数据的方法:
// HIDL 方式
interface ISensor {
getData() generates (SensorData data);
};
换成 AIDL 就变成了:
// AIDL for HAL 方式
@VintfStability
interface ISensor {
SensorData getData();
}
看到了吗?AIDL 更简洁,不需要 generates 这种关键字。而且 @VintfStability 这个注解很重要,它告诉系统这个接口是稳定的,需要纳入 VINTF 兼容性检查。
注意:如果不加 @VintfStability,系统不会把你的 HAL 服务纳入兼容性保障。我曾经见过一个项目,因为漏了这个注解,OTA 升级后 HAL 服务注册失败,整批设备变砖。嗯,血的教训。
4.3 AIDL HAL 的服务注册与发现
服务注册这块,AIDL 和 HIDL 的思路差不多,但 API 不同。我们来看一个完整的例子。
首先,定义接口文件 IVendorLight.aidl:
@VintfStability
package android.hardware.light;
interface IVendorLight {
int getBrightness();
void setBrightness(int value);
}
然后,在服务端实现这个接口:
public class LightService extends IVendorLight.Stub {
@Override
public int getBrightness() {
return currentBrightness;
}
@Override
public void setBrightness(int value) {
currentBrightness = value;
// 操作硬件
}
}
最后,注册服务:
// 服务端注册
LightService service = new LightService();
ServiceManager.addService(
"android.hardware.light.IVendorLight/default",
service
);
客户端发现服务也很简单:
// 客户端发现
IBinder binder = ServiceManager.getService(
"android.hardware.light.IVendorLight/default"
);
IVendorLight light = IVendorLight.Stub.asInterface(binder);
int brightness = light.getBrightness();
这里有个关键点:服务名必须和 VINTF manifest 里声明的一致。否则系统在启动时检查不到对应的 HAL 服务,会直接报错。
避坑指南:我曾经在调试时发现服务注册成功了,但客户端就是连不上。查了半天,原来是服务名里的大小写写错了。AIDL 的服务名是大小写敏感的,这个细节很容易忽略。
4.4 AIDL HAL 的稳定性保障机制
稳定性是 HAL 层的命门。AIDL for HAL 提供了几层保障:
- @VintfStability 注解:标记接口为稳定接口,纳入兼容性检查
- 版本号管理:通过
@VintfStability(majorVersion=1, minorVersion=0)控制 - VINTF manifest 校验:系统启动时检查所有声明的 HAL 服务是否可用
- 进程隔离:每个 HAL 服务运行在独立进程中,崩溃不影响系统
我画了一张图,帮你理解 AIDL HAL 的稳定性保障体系:
这个体系的核心思想是:把问题暴露在编译期和启动期,而不是运行时。你想想看,如果 HAL 服务在用户使用时才崩溃,那体验得多糟糕。
关于版本管理,AIDL 的做法和 HIDL 不太一样。HIDL 通过 @1.0、@1.1 这种版本号来管理,而 AIDL 用的是 @VintfStability 注解里的版本字段:
// 版本 1.0
@VintfStability(majorVersion=1, minorVersion=0)
interface IVendorLight {
int getBrightness();
void setBrightness(int value);
}
// 版本 1.1(新增方法)
@VintfStability(majorVersion=1, minorVersion=1)
interface IVendorLight {
int getBrightness();
void setBrightness(int value);
void setColor(int r, int g, int b); // 新增
}
这里有个原则:只能增加,不能删除或修改已有方法。否则老版本的客户端调用时会出问题。我在项目中遇到过这种情况:有人觉得某个方法参数不合理,直接改了签名,结果 OTA 后老设备上的应用全部崩溃。嗯,这种错误犯一次就够了。
总结一下:AIDL for HAL 不是简单的技术选型,它是 Android 系统走向统一 IPC 框架的关键一步。从 HIDL 到 AIDL,语法更简洁,工具链更统一,稳定性保障更完善。如果你正在设计新的 HAL 接口,我建议直接上 AIDL。