1. HAL概述与Android系统架构
做Android底层开发,第一个绕不开的概念就是HAL。说白了,HAL就是硬件和系统之间的一道桥。我刚开始接触Android时,总觉得这层有点多余——直接调驱动不就行了?后来踩过坑才明白,没有HAL,整个生态根本玩不转。
1.1 Android系统层次结构
先看看Android系统长什么样。从上到下,大概分这么几层:
- 应用层 — 你手机上那些App,用户直接看到的
- 应用框架层 — ActivityManager、WindowManager这些系统服务
- 系统运行库层 — C/C++库、ART运行时
- HAL层 — 硬件抽象层,我们今天的主角
- Linux内核层 — 真正的硬件驱动、进程管理、内存管理
嗯,这里要注意:很多人把HAL和内核驱动搞混。我见过不少新人问「HAL是不是就是驱动?」其实不是。驱动在内核里,HAL在用户空间。它们之间通过系统调用通信。
关键认知:HAL是用户空间程序,不是内核模块。它封装了硬件访问细节,让上层框架不用关心你用的是高通还是MTK的芯片。
1.2 HAL在系统中的位置与作用
HAL到底站在哪?我画了张图,你看一眼就明白了。
HAL的作用,我总结成三点:
- 解耦 — 框架层不用知道硬件怎么实现的。换芯片?换HAL实现就行,上层代码不用动。
- 标准化 — Google定义好接口,各家厂商按规矩办事。没有HAL,每家写的接口都不一样,框架层根本没法统一。
- 保护 — 厂商不想公开的硬件细节,可以藏在HAL实现里。只暴露接口,不暴露实现。
我的经验:有一次做平板项目,客户临时换了触控芯片。内核驱动重写了一遍,但HAL接口没变,上层应用完全不受影响。这就是HAL解耦的价值——换硬件就像换插座,插头规格不变就行。
1.3 HAL的发展历程:从libhardware到Treble架构
HAL不是一天长成这样的。我经历了从Android 4.4到14的演进,看着它一步步变「重」。
| 阶段 | Android版本 | 核心机制 | 特点 |
|---|---|---|---|
| libhardware时代 | Android 1.0 ~ 7.x | hw_module_t / hw_device_t | 直接加载.so,厂商和系统紧耦合 |
| Treble架构 | Android 8.0+ | HIDL接口 + hwservicemanager | vendor分区独立,框架和厂商分离 |
| Treble演进 | Android 10+ | AIDL化 + 稳定版接口 | 更轻量,更安全,支持模块化更新 |
1.3.1 libhardware时代
早期Android的HAL很简单。你写一个.so,实现几个结构体指针,框架直接dlopen加载。代码大概长这样:
// 老式HAL模块定义
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.id = "audio",
.methods = &audio_module_methods,
};
// 打开设备
hw_device_t *device;
audio_module->methods->open(audio_module, "primary", &device);
有什么问题?我举个例子。你买一台手机,厂商给你编译了一个完整的system.img。如果Google发布了安全补丁,厂商得重新编译整个镜像。因为HAL和框架是编译在一起的,换一个.so都得整包升级。这就是所谓的「碎片化」根源。
避坑指南:我曾经在Android 6.0上调试一个GPS HAL。厂商给的.so在A设备上跑得好好的,换到B设备就崩溃。查了两天才发现是.so里硬编码了设备节点路径。这就是libhardware时代的通病——没有接口契约,厂商想怎么写就怎么写。
1.3.2 Treble架构
Android 8.0引入Treble,是一次大手术。核心思路:把vendor分区独立出来,框架和厂商实现通过HIDL通信。
HIDL是什么?Interface Definition Language。你定义好接口文件,系统自动生成C++代码。厂商实现接口,框架调用接口,两边通过Binder通信。
// HIDL接口定义示例
package android.hardware.audio@4.0;
interface IDevice {
init(string name) generates (Result result);
openStream(IOStreamCallback cb) generates (Result result, IStream stream);
close() generates (Result result);
};
这样做的好处很明显:
- 框架可以独立OTA升级,不用等厂商
- 厂商实现和框架代码完全解耦
- 接口版本化,向后兼容
我记得Treble刚出来时,很多厂商抱怨「又要改架构」。但几年下来,碎片化问题确实改善了很多。现在你买一台Android 12的手机,Google可以直接推送系统更新,不用等厂商适配——这就是Treble的功劳。
1.3.3 从HIDL到AIDL
到了Android 10以后,Google开始推AIDL化的HAL。说白了,就是不用HIDL那套了,改用Android原生的AIDL。为什么?
- AIDL更轻量,不用额外生成那么多代码
- AIDL和系统服务共用一套IPC机制
- 维护成本更低
现在新项目基本都走AIDL HAL。但老项目里HIDL HAL还大量存在,你得都懂。
1.4 HAL接口设计原则
做了这么多年HAL,我总结了几条设计原则。这些不是Google文档里写的,是我踩坑踩出来的。
- 接口要稳定 — 一旦发布,不要轻易改。我见过一个厂商在HAL接口里加了个参数,结果所有上层应用都得跟着改。这不是HAL,这是给自己挖坑。
- 功能要单一 — 一个接口只做一件事。别搞什么万能接口,传个flag决定行为。调试的时候你根本不知道flag传对了没有。
- 错误处理要完整 — 每个接口都要返回明确的错误码。我最烦那种「成功返回0,失败返回-1」的接口。你倒是告诉我为什么失败啊!
- 性能要可预期 — HAL调用不能有不可控的延迟。比如你在HAL里做内存分配、文件IO,上层调一次卡一次,这谁受得了?
- 线程安全要考虑 — 你的HAL可能被多个客户端同时调用。不加锁?等着崩溃吧。
核心原则总结:HAL接口是契约,不是实现。设计时多想想「如果换一家厂商来实现,这个接口还能用吗?」能,就对了。
嗯,第一章就到这里。HAL的概念和架构先搭起来,后面我们会一步步深入——从怎么写一个真正的HAL模块,到调试、性能优化、兼容性测试,都会讲到。