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 vs HIDL架构对比 传统HAL架构 Android Framework HAL .so (hw_get_module) Linux Kernel + Driver 同进程调用,无边界保护 HAL崩溃 → Framework也挂 接口变更需同步修改 HIDL架构 (Treble) Android Framework HIDL Binder IPC HAL Service (HIDL实现) Linux Kernel + Driver 跨进程调用,有边界保护 HAL服务崩溃 → Framework不受影响 接口由.hal文件严格定义

从图上你能看到:传统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