2. HAL 接口设计原则:抽象、封装、解耦,以及 HIDL 与 AIDL 的选择

这一章,我们来聊聊 HAL 接口设计的核心思想。说白了,就是怎么把硬件藏好,让上层用着舒服,底层改着方便。

我刚开始做 HAL 时,也踩过不少坑。最典型的就是把硬件寄存器直接暴露给上层。结果呢?换了个芯片,上层代码全得重写。后来我才明白,HAL 设计的本质就六个字:抽象、封装、解耦。

2.1 抽象:只暴露该暴露的

抽象,就是只告诉别人「你能做什么」,不告诉别人「你怎么做的」。比如一个摄像头 HAL,上层只需要知道 open()、startPreview()、takePicture(),至于底层是 I2C 还是 SPI,是 OV5640 还是 IMX219,上层根本不用关心。

核心原则:接口只描述功能,不描述实现。

我在项目中遇到过一件事:有个同事把 sensor 的增益计算方式直接写进了接口参数里。结果换了 sensor,接口签名都得改。这就是抽象没做好。

好的抽象应该像这样:

// 不好的抽象:暴露了实现细节
int setGain(int analog_gain, int digital_gain);

// 好的抽象:只暴露概念
int setExposureCompensation(int ev_value);

你想想看,上层应用真的需要知道模拟增益和数字增益的区别吗?不需要。它只需要知道「曝光补偿」这个抽象概念就够了。

2.2 封装:把复杂性关进笼子里

封装,就是把实现细节藏起来。上层调用接口时,不需要知道内部怎么运作的。

我记得有个项目,音频 HAL 的初始化函数有 12 个参数。每次调用都要传一堆 NULL 或 0。这就是封装没做好。

好的封装应该做到:

  • 接口参数少:一般不超过 5 个
  • 内部状态不外露:用句柄或指针隐藏实现
  • 错误码统一:别一会儿返回 -1,一会儿返回 NULL
我的习惯:每个 HAL 接口都定义一个统一的返回值类型,比如 status_t。这样上层处理错误时,逻辑就统一了。

2.3 解耦:让各层各司其职

解耦,就是让 HAL 接口不依赖具体的硬件实现。你换硬件,接口不变;你换接口实现,上层不变。

我曾经见过一个项目,HAL 接口里直接传了 struct i2c_msg。这等于把驱动层的细节暴露给了框架层。后来换了 SPI 总线,整个 HAL 接口都得改。

解耦的关键在于:

  • 接口不依赖硬件数据结构:用抽象的数据类型
  • 接口不依赖特定协议:别把 I2C、SPI 的细节带进来
  • 接口不依赖操作系统:尽量用标准类型
避坑指南:我曾经在接口里用了 Linux 特有的 struct file_operations。结果项目要移植到 RTOS 上,整个 HAL 都得重写。从那以后,我坚持接口只使用标准 C 类型。

2.4 HIDL 与 AIDL 的选择

Android 提供了两种接口定义语言:HIDL 和 AIDL。很多人纠结选哪个。其实没那么复杂,我帮你理清楚。

对比维度 HIDL AIDL
设计初衷 HAL 层接口定义 应用层 IPC
稳定性 接口稳定,版本化管理 接口灵活,可随时修改
性能 支持直通模式,零拷贝 Binder 传输,有序列化开销
适用场景 硬件抽象层,跨进程 HAL 应用与服务通信
学习成本 较高,需要理解 hidl-gen 较低,Android 开发者都熟悉

我的建议很简单:

  • 做 HAL 接口:用 HIDL。它天生就是为硬件抽象设计的,支持版本化、直通模式、零拷贝。
  • 做应用层 IPC:用 AIDL。它更灵活,开发效率更高。
  • 做系统服务:看情况。如果服务需要跨进程调用硬件,用 HIDL;如果只是应用层逻辑,用 AIDL。
核心判断标准:接口是否直接操作硬件?是 → HIDL;否 → AIDL。

我记得有个项目,团队用了 AIDL 做 HAL 接口。结果每次升级 Android 版本,接口都得跟着改。后来换成 HIDL,接口版本化管理,升级就轻松多了。

2.5 知识体系总览

下面这张图,帮你把本章的核心逻辑串起来:

HAL 接口设计知识体系 抽象 封装 解耦 只暴露功能 隐藏实现细节 接口稳定不变 参数少而精 状态不外露 错误码统一 不依赖硬件结构 不依赖特定协议 不依赖操作系统 接口定义语言选择 HIDL:硬件抽象层接口 AIDL:应用层 IPC

2.6 实战建议

最后,给你几个实战中的建议:

  1. 先设计接口,再写实现:我习惯先写 .hal 或 .aidl 文件,评审通过后再写 C++ 代码。
  2. 接口要小,职责要单一:一个接口只做一件事。别搞出万能接口。
  3. 版本化要早做:HIDL 的版本管理是强项,别等到接口变了才想起来。
  4. 性能敏感用 HIDL 直通模式:如果 HAL 和框架在同一个进程,直通模式可以省掉 Binder 开销。
我的经验:刚开始做 HAL 时,别追求完美。先做出一个能用的接口,然后通过版本迭代逐步优化。比一开始就想设计一个「万能接口」要靠谱得多。