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到底站在哪?我画了张图,你看一眼就明白了。

Android系统层次结构与HAL位置 应用层 (App Layer) 应用框架层 (Framework) 系统运行库层 (Libraries & ART) HAL层 (硬件抽象层) ★ 本章核心 ★ Linux内核层 (Kernel + Drivers) 硬件设备 (Hardware) HAL的作用 隔离硬件差异 HAL的边界 用户空间 · 非内核

HAL的作用,我总结成三点:

  1. 解耦 — 框架层不用知道硬件怎么实现的。换芯片?换HAL实现就行,上层代码不用动。
  2. 标准化 — Google定义好接口,各家厂商按规矩办事。没有HAL,每家写的接口都不一样,框架层根本没法统一。
  3. 保护 — 厂商不想公开的硬件细节,可以藏在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文档里写的,是我踩坑踩出来的。

  1. 接口要稳定 — 一旦发布,不要轻易改。我见过一个厂商在HAL接口里加了个参数,结果所有上层应用都得跟着改。这不是HAL,这是给自己挖坑。
  2. 功能要单一 — 一个接口只做一件事。别搞什么万能接口,传个flag决定行为。调试的时候你根本不知道flag传对了没有。
  3. 错误处理要完整 — 每个接口都要返回明确的错误码。我最烦那种「成功返回0,失败返回-1」的接口。你倒是告诉我为什么失败啊!
  4. 性能要可预期 — HAL调用不能有不可控的延迟。比如你在HAL里做内存分配、文件IO,上层调一次卡一次,这谁受得了?
  5. 线程安全要考虑 — 你的HAL可能被多个客户端同时调用。不加锁?等着崩溃吧。

核心原则总结:HAL接口是契约,不是实现。设计时多想想「如果换一家厂商来实现,这个接口还能用吗?」能,就对了。

嗯,第一章就到这里。HAL的概念和架构先搭起来,后面我们会一步步深入——从怎么写一个真正的HAL模块,到调试、性能优化、兼容性测试,都会讲到。