6、HAL 硬件抽象层:HAL 架构设计、Stub 实现、HIDL 与 AIDL 接口定义

说到 Android 系统的 HAL,我脑子里第一个蹦出来的词就是「中间人」。它夹在 Linux 内核驱动和 Android Framework 之间,干的是翻译和隔离的活儿。说白了,HAL 就是让 Google 的工程师不用管你用的是高通还是联发科的芯片,上层代码写一套,底层各家自己适配。

我在 2016 年做过一个项目,要把某款老手机移植到 Android 8.0。那时候最大的坑就是 HAL 层不兼容。嗯,从那以后我对 HAL 的敬畏心就上来了。今天咱们就把 HAL 的架构、Stub 实现、还有 HIDL 和 AIDL 的区别,一次性讲透。

6.1 HAL 架构设计:分层与解耦

HAL 的全称是 Hardware Abstraction Layer。它的核心目标就一个:让 Framework 不直接碰硬件。你想想看,如果上层代码直接调用内核驱动,那换一颗 SoC 就得重写整个系统。这谁受得了?

传统的 HAL 架构(Android 7.0 之前)是「模块 + Stub」模式。每个硬件模块(比如 Camera、GPS)对应一个 .so 动态库。Framework 通过 dlopen 加载这个库,然后调用固定的函数指针。这种方式简单粗暴,但有个致命问题:Framework 进程如果崩溃,硬件状态就丢了

到了 Android 8.0,Google 推出了 Treble 架构。HAL 被拆成了两部分:HAL 实现端(跑在独立的守护进程里)和 HAL 客户端(Framework 端)。两者通过 HIDL 或 AIDL 通信。这样一来,Framework 再怎么崩,HAL 进程依然健在。我个人习惯把这个变化叫做「HAL 的进程化」。

核心要点:Treble 架构的本质是「接口与实现分离」。HAL 接口定义在 /hardware/interfaces/ 下,各家芯片厂商只需要实现这些接口,Google 负责 Framework 端。从此系统升级不再依赖芯片厂商。

下面这张图展示了 HAL 在 Android 系统中的位置和通信路径:

Android HAL 架构分层图 Android 应用 / Framework (Java) HIDL / AIDL 接口定义 HAL 实现 (Stub / 守护进程) Linux 内核驱动 (Kernel Driver) 物理硬件 (Camera / GPS / Sensor)

6.2 Stub 实现:传统 HAL 的写法

Stub 这个词,直译是「存根」。在 HAL 语境下,它指的是一个 硬件模块的具体实现。每个 Stub 对应一个硬件模块,比如 camera.stub.so、gps.stub.so。

传统 HAL 的 Stub 实现遵循固定的结构。每个模块必须暴露一个 hw_module_t 结构体,以及一个或多个 hw_device_t 结构体。我刚开始写 HAL 时,老搞混这两个结构体的关系。后来我总结了一句话:module 代表「谁家的硬件」,device 代表「怎么操作这个硬件」

来看一个典型的 Stub 实现代码框架:

// hardware/libhardware/include/hardware/camera.h
typedef struct camera_module {
    hw_module_t common;
    int (*get_number_of_cameras)(void);
    int (*get_camera_info)(int camera_id, struct camera_info *info);
} camera_module_t;

// 实现端:camera_stub.cpp
static int camera_device_open(const hw_module_t* module, const char* id,
                              hw_device_t** device) {
    // 分配设备结构体,初始化硬件
    return 0;
}

static struct hw_module_methods_t camera_module_methods = {
    .open = camera_device_open,
};

camera_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = CAMERA_MODULE_API_VERSION_1_0,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
        .id = CAMERA_HARDWARE_MODULE_ID,
        .name = "Camera Stub Module",
        .author = "Your Company",
        .methods = &camera_module_methods,
    },
    .get_number_of_cameras = stub_get_number_of_cameras,
    .get_camera_info = stub_get_camera_info,
};

避坑指南:我曾经在写 GPS HAL 时,忘记在 HAL_MODULE_INFO_SYM 中正确填充 .id 字段。结果 Framework 死活加载不到模块。调试了两天才发现,id 必须和 /hardware/libhardware/include/hardware/gps.h 中定义的 GPS_HARDWARE_MODULE_ID 完全一致。大小写、下划线,一个都不能错。

6.3 HIDL 接口定义:Treble 时代的标配

HIDL 全称是 HAL Interface Definition Language。它是 Google 为 Treble 架构量身定做的接口语言。HIDL 文件以 .hal 结尾,语法上有点像 C++ 和 Java 的混合体。

为什么要用 HIDL?说白了,就是为了 跨进程通信的标准化。传统 HAL 的 Stub 是直接 dlopen 加载的,Framework 和 HAL 在同一个进程里。HIDL 则强制把 HAL 放到独立进程,通过 Binder 通信。这样一来,稳定性大幅提升。

一个典型的 HIDL 接口定义长这样:

// android.hardware.camera@3.2::ICameraDevice.hal
package android.hardware.camera@3.2;

interface ICameraDevice {
    /**
     * 打开相机设备
     * @param cameraId 相机 ID
     * @return 操作状态
     */
    open(string cameraId) generates (Status status);

    /**
     * 配置相机流
     * @param config 流配置
     * @return 配置结果
     */
    configureStreams(StreamConfiguration config) generates (Status status);

    /**
     * 捕获图像
     * @param request 捕获请求
     * @return 捕获结果
     */
    capture(CaptureRequest request) generates (Status status, CaptureResult result);
};

HIDL 接口定义好之后,用 hidl-gen 工具生成 C++ 或 Java 的客户端和服务端代码。我个人习惯用 C++ 写 HAL 实现,因为性能更好,而且可以直接操作硬件寄存器。

关键区别:HIDL 是 Google 专门为 HAL 设计的,它支持「直通式」(passthrough)和「绑定式」(binderized)两种模式。直通式就是传统 Stub 的变种,绑定式才是真正的跨进程。Android 8.0 之后,Google 推荐全部使用绑定式。

6.4 AIDL 接口定义:从 Framework 到 HAL 的桥梁

AIDL 大家应该不陌生,Android 应用开发里经常用。但在 HAL 层面,AIDL 是 Android 11 之后才被正式引入的。Google 发现,HIDL 虽然好,但毕竟是一门新语言,学习成本高。而 AIDL 是 Android 的老牌 IPC 语言,开发者更熟悉。

所以从 Android 11 开始,Google 允许用 AIDL 来定义 HAL 接口。AIDL 和 HIDL 在功能上是等价的,但语法更接近 Java。来看一个例子:

// IMySensor.aidl
package android.hardware.mysensor;

interface IMySensor {
    /**
     * 获取传感器数据
     * @return 传感器值数组
     */
    float[] getSensorData();

    /**
     * 设置采样率
     * @param rateHz 采样率(Hz)
     */
    void setSamplingRate(int rateHz);
}

用 AIDL 定义 HAL 接口的好处是:Framework 端的代码可以直接复用。因为 AIDL 生成的 Java 代码和普通的 Service 通信一模一样。我在一个项目中把 HIDL 的 Camera HAL 迁移到 AIDL,Framework 端的改动量减少了大概 40%。

注意:HIDL 和 AIDL 不能混用。一个 HAL 服务要么全部用 HIDL,要么全部用 AIDL。Google 的路线图是逐步用 AIDL 取代 HIDL,但这个过程很漫长。目前(Android 14)两种方式都支持,但新开发的 HAL 建议直接用 AIDL。

6.5 HIDL vs AIDL:怎么选?

很多刚接触 HAL 的同学会问:到底用 HIDL 还是 AIDL?我根据实际项目经验,整理了一个对比表:

对比维度 HIDL AIDL
引入版本 Android 8.0 Android 11
语言风格 类似 C++/IDL 混合 类似 Java 接口
代码生成 hidl-gen 生成 C++/Java aidl 工具生成 Java/C++
跨进程性能 优秀(原生 Binder 优化) 优秀(与 Framework 一致)
学习曲线 较陡(新语言) 平缓(Android 开发者熟悉)
Google 推荐 旧项目维护 新项目首选

我个人建议:如果你是从零开始一个新项目,直接用 AIDL。如果是维护 Android 8.0~10 的老项目,那就继续用 HIDL,别折腾迁移。我曾经试图把一个运行稳定的 HIDL HAL 强行迁移到 AIDL,结果引入了一堆 Binder 超时的 bug,得不偿失。

6.6 实战:实现一个简单的 HAL 服务

光说不练假把式。咱们来实现一个最简单的 HAL 服务:一个虚拟的 LED 控制 HAL。它只有一个功能:开灯和关灯。

第一步,定义 AIDL 接口:

// IMyLed.aidl
package android.hardware.myled;

interface IMyLed {
    void setOn(bool on);
    bool isOn();
}

第二步,实现 HAL 服务端(C++):

// MyLed.cpp
#include "android/hardware/myled/IMyLed.h"
#include <utils/Log.h>

class MyLed : public android::hardware::myled::BnMyLed {
public:
    android::binder::Status setOn(bool on) override {
        mIsOn = on;
        // 这里操作 GPIO 或 I2C 控制 LED
        ALOGD("LED set to %s", on ? "ON" : "OFF");
        return android::binder::Status::ok();
    }

    android::binder::Status isOn(bool* _aidl_return) override {
        *_aidl_return = mIsOn;
        return android::binder::Status::ok();
    }

private:
    bool mIsOn = false;
};

第三步,注册服务:

// main.cpp
int main() {
    android::sp<MyLed> service = new MyLed();
    android::status_t ret = android::defaultServiceManager()->addService(
        android::String16("android.hardware.myled.IMyLed/default"),
        service);
    if (ret != android::OK) {
        ALOGE("Failed to add service");
        return -1;
    }
    ALOGD("MyLed HAL service started");
    android::ProcessState::self()->startThreadPool();
    android::IPCThreadState::self()->joinThreadPool();
    return 0;
}

避坑指南:我曾经在注册服务时,忘了调用 startThreadPool()joinThreadPool()。结果服务注册成功了,但客户端一调用就卡死。因为 Binder 线程池没启动,根本没人处理请求。这个坑我踩了整整一个下午。

第四步,Framework 端调用(Java):

// 在 SystemServer 或 App 中调用
IMyLed ledService = IMyLed.Stub.asInterface(
    ServiceManager.getService("android.hardware.myled.IMyLed/default"));
if (ledService != null) {
    ledService.setOn(true);
    boolean isOn = ledService.isOn();
    Log.d("MyLed", "LED is " + (isOn ? "ON" : "OFF"));
}

嗯,到这里,一个完整的 HAL 服务就搭建好了。从接口定义到服务注册,再到客户端调用,整个链路是通的。你可以在自己的开发板上试试,把 GPIO 控制逻辑加进去,就能真正控制硬件了。

最后说一句:HAL 是 Android 系统中最接近硬件的一层,也是最能体现嵌入式功底的地方。把 HAL 吃透了,你就能真正理解「硬件无关」这四个字背后的代价和智慧。


公众号:蓝海资料掘金营,微信deep3321