1. HAL 架构总览:从硬件到框架的桥梁
做 Android 系统底层开发这么多年,我经常被问到同一个问题:HAL 到底是什么? 说白了,它就是 Android 系统里连接硬件和软件的那层胶水。没有它,你的手机摄像头、Wi-Fi、蓝牙这些硬件,上层应用根本使唤不动。
今天这一章,咱们就把 HAL 的来龙去脉彻底捋清楚。我会结合自己踩过的坑,带你看看 HAL 从 1.0 到 HIDL 再到 AIDL HAL 的演进过程,以及它在整个 Android 系统里到底扮演什么角色。
1.1 HAL 的演进历史:三代架构的变迁
Android HAL 从诞生到现在,经历了三个主要阶段。我把它整理成了一张表,方便你对比:
| 阶段 | 版本 | 核心机制 | 主要问题 |
|---|---|---|---|
| HAL 1.0 | Android 1.0 ~ 4.4 | 动态库 (.so) 直接加载 | 耦合严重,升级困难 |
| HIDL HAL | Android 8.0 ~ 10 | 进程间通信 (IPC),接口定义语言 | 接口定义繁琐,性能开销 |
| AIDL HAL | Android 11+ | 统一 AIDL 接口,Binder 通信 | 相对成熟,仍在演进 |
HAL 1.0:原始时代
最早的 HAL 实现非常简单。硬件厂商编译一个 .so 动态库,Framework 通过 dlopen 直接加载它。听起来很直接对吧?但问题也出在这里。
我在项目中遇到过一个典型的坑:某厂商的摄像头 HAL 库和系统库版本不兼容,结果一加载就段错误。因为 HAL 1.0 是直接链接到系统进程里的,一个库崩溃,整个 mediaserver 进程就挂了。你想想看,手机突然不能拍照了,用户得多崩溃。
HIDL HAL:解耦时代
Android 8.0 引入了 Treble 项目,HIDL(HAL Interface Definition Language)应运而生。它的核心思想是:把 HAL 从系统进程里搬出来,变成一个独立的服务进程。
HIDL 使用 .hal 文件定义接口,然后通过 hwservicemanager 来管理服务。这样,Framework 和 HAL 之间通过进程间通信(IPC)交互,不再直接加载动态库。
// 一个典型的 HIDL 接口定义示例
package vendor.example.hardware.camera@1.0;
interface ICamera {
openCamera(string cameraId) generates (Status status);
startPreview() generates (Status status);
stopPreview() generates (Status status);
closeCamera() generates (Status status);
};
这样做的好处很明显:HAL 崩溃不会拖垮系统进程。但代价是接口定义变得非常繁琐。我记得第一次写 HIDL 接口时,光是定义数据类型就写了上百行。而且每次修改接口,都要重新生成代码,编译时间长得让人抓狂。
AIDL HAL:统一时代
Android 11 开始,Google 决定用 AIDL 统一所有接口定义。AIDL 本来就是 Android 系统里最成熟的 IPC 机制,现在把它扩展到 HAL 层,好处是显而易见的:
- 接口定义更简洁:AIDL 语法比 HIDL 更接近 Java,学习成本低
- 工具链更成熟:Android Studio 原生支持 AIDL 编译
- 性能更好:Binder 驱动经过多年优化,比 HIDL 的 hwbinder 更高效
1.2 HAL 在 Android 系统架构中的位置
咱们来看看 HAL 在整个 Android 系统里到底站在哪。我画了一张架构图,帮你直观理解:
从这张图你可以看到,HAL 层正好卡在系统服务和内核驱动之间。它的核心职责就两个:
- 向上提供统一接口:不管底层是什么硬件,Framework 看到的都是同样的 API
- 向下屏蔽硬件差异:不同厂商的芯片、传感器,HAL 层把它们包装成标准接口
1.3 HAL 与内核驱动、Framework 的交互关系
理解了位置,咱们再深入看看它们之间到底怎么交互的。我习惯把这三层的关系比作一个三层快递系统:
- Framework 是客户,只管下单(调用 API)
- HAL 是快递站,负责分拣和调度
- 内核驱动 是快递员,真正去送货(操作硬件)
Framework 与 HAL 的交互
Framework 通过 JNI(Java Native Interface)调用 HAL 的接口。举个例子,当你的 App 调用 Camera.open() 时,调用链是这样的:
App (Java)
→ Camera Framework (Java)
→ CameraService (C++/Java)
→ HAL 接口 (HIDL/AIDL)
→ HAL 实现 (C++)
→ 内核驱动 (ioctl)
这里有个关键点:在 HIDL/AIDL 架构下,Framework 和 HAL 运行在不同的进程里。这意味着每次调用都要经过 Binder 驱动,有序列化和反序列化的开销。我见过不少性能问题,就是因为频繁的 IPC 调用导致的。
HAL 与内核驱动的交互
HAL 和内核驱动之间,最常见的交互方式就是 ioctl 系统调用。HAL 打开设备节点(比如 /dev/video0),然后通过 ioctl 发送命令和数据。
// HAL 层调用内核驱动的典型代码
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
// 处理错误
}
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1920;
fmt.fmt.pix.height = 1080;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
// 处理错误
}
我曾经踩过一个坑:某个芯片的驱动对 ioctl 参数有特殊的对齐要求,但 HAL 层没有做对齐处理。结果在某些场景下,驱动返回 EINVAL,导致摄像头打不开。排查了两天才发现是参数对齐的问题。
ioctl 命令的定义和参数结构体,一定要自己确认。
1.4 本章小结
这一章咱们把 HAL 的来龙去脉讲清楚了。从 HAL 1.0 的原始时代,到 HIDL 的解耦时代,再到 AIDL 的统一时代,每一步都是为了解决实际问题。HAL 在系统架构中的位置决定了它的核心价值:隔离硬件差异,提供统一接口。
记住一句话:HAL 做得好,上层开发就舒服;HAL 做得烂,整个系统都跟着遭殃。后面几章,我会带你深入每个细节,从接口设计到性能优化,把 HAL 开发的实战经验全部分享给你。