1. HAL与HIDL概述:Android硬件抽象层的演进之路
大家好,我是老张。做Android底层开发十几年,今天咱们聊聊HAL和HIDL。这两个词,做驱动的兄弟应该都不陌生。但要说清楚它们怎么来的、为什么要有HIDL、跟传统HAL到底啥关系——嗯,我当年也是踩了不少坑才真正搞明白的。
1.1 从“硬编码”到“标准接口”:HAL的诞生
最早期的Android,硬件驱动怎么调?说白了,直接改内核代码。厂商把驱动编译进内核,上层通过ioctl或者sysfs来操作。那时候我还在做功能机,每次换一个硬件平台,整个BSP层几乎要重写。那叫一个痛苦。
Google也意识到这个问题。于是Android 1.0开始,引入了第一代HAL——libhardware_legacy。它的思路很简单:把硬件操作封装成.so库,上层通过dlopen动态加载。这样至少做到了“驱动代码和应用框架解耦”。
但说实话,那个版本很粗糙。每个模块的接口不统一,有的用结构体指针,有的用回调函数。我接手过一个项目,光适配一个Sensor HAL就花了三周——因为厂商的接口文档跟实际代码对不上。
第一代HAL的核心痛点:
- 接口定义不严格,全靠“约定”
- 没有版本管理,升级容易出兼容性问题
- 厂商可以随意修改接口,碎片化严重
1.2 第二代HAL:libhardware的标准化尝试
到了Android 4.0左右,Google推出了libhardware。这次有了统一的模块结构——hw_module_t和hw_device_t。每个HAL模块必须暴露一个固定的符号,比如HAL_MODULE_INFO_SYM。上层通过hw_get_module()来加载。
我个人觉得,这是HAL历史上最重要的一次改进。它让接口变得可枚举、可检查。你想想看,以前调试一个Camera HAL,得先猜厂商用了哪个函数名;现在只要看module->methods->open()就行了。
但问题依然存在:HAL和框架跑在同一个进程里。这意味着什么?HAL一旦崩溃,整个system_server也跟着挂。我记得有一次,某个厂商的GPS HAL在定位时内存越界,直接导致手机重启。用户反馈说“导航到一半就黑屏了”——那场面,你懂的。
| 特性 | libhardware_legacy | libhardware |
|---|---|---|
| 接口规范 | 无统一结构 | hw_module_t / hw_device_t |
| 加载方式 | dlopen + 自定义符号 | hw_get_module() |
| 进程模型 | 同进程 | 同进程 |
| 版本管理 | 无 | module->hal_api_version |
| 稳定性 | 差 | 中等 |
1.3 Treble架构:为什么需要一场革命?
时间来到Android 8.0。这时候Android设备已经碎片化到令人发指的程度。Google内部统计过,一个AOSP版本发布后,厂商平均需要6-9个月才能完成适配。为什么?因为每次框架升级,HAL接口都可能变,厂商得跟着改驱动。
Google决定动大手术——这就是Treble架构。它的核心思想很简单:把framework和vendor(厂商实现)彻底分开。怎么分?用进程边界来分。
在Treble之前,framework直接调用HAL的.so。在Treble之后,framework通过一个“中间层”来跟HAL通信。这个中间层,就是HIDL。
我的理解: Treble相当于在系统里画了一条“红线”。红线以上是Google控制的framework,红线以下是厂商控制的vendor。HIDL就是跨越这条红线的“协议”。
1.4 HIDL的诞生:接口定义即契约
HIDL全称是HAL Interface Definition Language。它跟AIDL很像,但专门为硬件抽象层设计。你用HIDL定义好接口,然后自动生成C++代码。framework端和vendor端各拿一份,通过Binder或直连方式通信。
我曾经参与过一个项目,把老项目的传统HAL迁移到HIDL。最直观的感受是:接口变得“硬”了。以前改个函数签名,只要两边同步改就行;现在接口定义在.hal文件里,改了就得重新生成代码,编译不过就是编译不过。这其实是好事——把问题暴露在编译期,而不是运行时。
// 一个典型的HIDL接口定义示例
// IMySensor.hal
package vendor.example.sensor@1.0;
interface IMySensor {
getSensorList() generates (vec<SensorInfo> list);
enable(int32_t sensorId, bool enable) generates (Result result);
readEvent() generates (SensorEvent event);
};
你看,参数类型、返回值、异常——全都写死了。这就是契约。
1.5 HAL与HIDL的关系与区别
很多人问:HIDL是不是取代了HAL?其实不是。HIDL是HAL的“通信层”,不是HAL本身。
传统HAL是“库”,HIDL是“服务”。传统HAL的代码编译成.so,被framework直接加载;HIDL的代码编译成可执行文件或共享库,通过Binder IPC来调用。
我画了一张图,帮你理清它们的关系:
从图上你能看到:传统HAL是“直连”,HIDL是“隔了一层”。这一层带来了什么?
- 稳定性:HAL服务挂了,framework不会死。我曾经在调试一个Audio HAL时,故意让服务段错误,system_server纹丝不动——这在以前根本不敢想。
- 可升级性:framework可以独立升级,不用等厂商更新HAL。因为接口是稳定的,只要.hal文件不变,两边各玩各的。
- 安全性:HAL服务运行在独立的SELinux上下文中,权限控制更精细。
注意: HIDL不是银弹。它引入了IPC开销,对于高频调用(比如Sensor事件流),性能确实有影响。我见过一个项目,因为HIDL调用太频繁,CPU占用率直接飙到30%。后来不得不改用直连模式(passthrough)来绕过Binder。
1.6 一张表总结:HAL vs HIDL
| 对比维度 | 传统HAL | HIDL |
|---|---|---|
| 本质 | 共享库(.so) | 服务 + IPC |
| 进程模型 | 同进程 | 跨进程(Binder/直连) |
| 接口定义 | C结构体 + 函数指针 | .hal文件 + 自动生成代码 |
| 版本管理 | 手动维护 | @1.0, @2.0 等显式版本 |
| 稳定性 | 低(同进程崩溃) | 高(进程隔离) |
| 性能 | 高(无IPC开销) | 中等(有IPC开销) |
| 适用场景 | Android 8.0之前 | Android 8.0+ (Treble) |
1.7 我的建议
如果你现在开始一个新项目,直接用HIDL。别犹豫。Google已经明确表示,后续版本会逐步淘汰传统HAL。而且HIDL的工具链越来越成熟,调试也比以前方便多了。
但如果你要维护老项目,也别急着全盘迁移。我建议先评估一下:
- 如果HAL接口稳定、没有频繁升级需求,传统HAL还能用
- 如果涉及到framework独立升级、或者需要多厂商兼容,尽早切到HIDL