3. AIDL for HAL(AIDL HAL)入门:从HIDL到AIDL的演进

各位好,我是老张。今天我们来聊聊Android HAL开发中一个绕不开的话题——AIDL for HAL。说白了,就是Google在Android 11之后主推的一种新的HAL接口定义方式。

我记得第一次接触AIDL HAL是在一个车载项目上。当时客户要求系统必须跑Android 12,我习惯性地想用HIDL写驱动接口,结果被架构师拦住了。他说:“老张,时代变了。”嗯,从那以后我就开始认真研究AIDL HAL了。

3.1 AIDL与HIDL的对比:为什么要有两套方案?

先看一张对比表,帮你快速建立认知框架:

对比维度 HIDL AIDL HAL
引入版本 Android 8.0 Android 11(稳定)
接口语言 HIDL专用语言(.hal) 标准AIDL(.aidl)
后端支持 C++ / Java NDK / Java / Rust
稳定性 @1.0 ~ @1.x 版本化 @V1 ~ @Vn 版本化
跨进程效率 Binder优化版 标准Binder + 零拷贝
学习成本 需要学新语言 复用Android已有知识
Google推荐度 逐步弃用 强烈推荐

你可能会问:为什么Google要搞两套方案? 我个人理解是这样的——HIDL是Android Treble项目的产物,目的是解耦Framework和HAL。但HIDL有自己的语法、类型系统和编译工具链,开发者需要额外学习。而AIDL是Android开发者早就熟悉的东西,用AIDL写HAL接口,说白了就是“用你会的工具干新活”。

核心差异一句话总结: HIDL是“为HAL而生”的新语言,AIDL HAL是“用标准IPC写HAL”的演进方案。Google希望统一接口定义语言,降低开发门槛。

3.2 AIDL HAL的适用场景:什么时候该用它?

我在项目中总结了几条经验,供你参考:

  • 新项目首选: 如果你的目标平台是Android 11+,直接上AIDL HAL。别犹豫。
  • 跨版本兼容: AIDL HAL支持版本化接口,升级时不会破坏旧客户端。
  • 多语言需求: 团队里有Rust高手?AIDL HAL原生支持Rust后端。
  • 性能敏感场景: AIDL HAL支持零拷贝传输,适合摄像头、传感器等高频数据流。

避坑指南: 我曾经在一个遗留项目上试图把HIDL全改成AIDL HAL,结果发现底层驱动库依赖了HIDL的某些特性(比如FMQ快速消息队列)。改到一半发现性能不达标,最后只能部分迁移。所以我的建议是——新模块用AIDL,旧模块逐步迁移,别搞一刀切。

3.3 AIDL接口定义与编译:手把手写一个示例

好,理论说完了,咱们动手写代码。假设我们要定义一个简单的温度传感器HAL,提供读取当前温度的功能。

3.3.1 定义AIDL接口

在 hardware/interfaces/temperature/aidl/ 目录下创建 ITemperature.aidl:

// ITemperature.aidl
package hardware.interfaces.temperature;

@VintfStability
interface ITemperature {
    /**
     * 读取当前温度值
     * @return 温度值,单位摄氏度
     */
    float getTemperature();

    /**
     * 设置温度阈值,超过阈值触发回调
     * @param threshold 阈值温度
     */
    void setThreshold(float threshold);
}

注意看 @VintfStability 这个注解——这是AIDL HAL和普通AIDL的关键区别。它告诉编译器和VINTF(Vendor Interface)工具:这个接口是稳定的、可版本化的HAL接口。

3.3.2 编写Android.bp编译配置

在同级目录下创建 Android.bp:

// Android.bp
aidl_interface {
    name: "hardware.interfaces.temperature.aidl",
    srcs: ["ITemperature.aidl"],
    stability: "vintf",
    backend: {
        ndk: {
            enabled: true,
        },
        java: {
            enabled: true,
        },
    },
}

这里 stability: "vintf" 是关键配置,表示这是一个VINTF稳定的接口。后端我通常同时开启NDK和Java,方便Framework层和Native层都能调用。

3.3.3 编译生成代码

执行编译命令:

source build/envsetup.sh
lunch aosp_arm64-userdebug
make hardware.interfaces.temperature.aidl

编译成功后,你会看到生成的代码在 out/soong/.intermediates/hardware/interfaces/temperature/aidl/ 目录下。包括:

  • NDK后端: C++头文件和源文件
  • Java后端: Java接口和Stub类

小技巧: 我习惯在编译后先检查生成的 BpTemperature.h 头文件,确认接口方法签名是否正确。有一次我忘了在AIDL里加 @VintfStability,结果生成的代码里没有Binder服务注册逻辑,排查了半天才发现。

3.4 AIDL HAL的服务注册与发现:让HAL跑起来

接口定义好了,代码也编译了,接下来就是让HAL服务真正跑起来,并且能被客户端找到。

3.4.1 服务端实现(Native层)

创建一个C++实现文件 Temperature.cpp:

// Temperature.cpp
#include "aidl/hardware/interfaces/temperature/BnTemperature.h"

namespace aidl::hardware::interfaces::temperature {

class Temperature : public BnTemperature {
    ndk::ScopedAStatus getTemperature(float* _aidl_return) override {
        // 模拟读取硬件温度
        *_aidl_return = 36.5f;
        return ndk::ScopedAStatus::ok();
    }

    ndk::ScopedAStatus setThreshold(float threshold) override {
        // 设置阈值逻辑
        return ndk::ScopedAStatus::ok();
    }
};

} // namespace

3.4.2 注册服务

在 main() 函数中注册服务:

int main() {
    auto service = ndk::SharedRefBase::make<Temperature>();
    const std::string instance = "default";

    // 注册到ServiceManager
    auto status = AServiceManager_addService(
        service->asBinder().get(),
        instance.c_str()
    );

    if (status != STATUS_OK) {
        // 注册失败处理
        return -1;
    }

    // 进入消息循环
    ABinderProcess_joinThreadPool();
    return 0;
}

3.4.3 客户端发现服务

客户端通过ServiceManager查找服务:

// 客户端代码
const std::string instance = "default";
auto service = ITemperature::fromBinder(
    ndk::SpAIBinder(
        AServiceManager_getService(
            "hardware.interfaces.temperature.ITemperature/default"
        )
    )
);

if (service == nullptr) {
    // 服务未找到
    return;
}

float temp;
auto status = service->getTemperature(&temp);
if (status.isOk()) {
    // 使用温度值
}

关键点: 服务名称的格式是 包名.接口名/实例名。比如上面的 hardware.interfaces.temperature.ITemperature/default。这个命名规则和HIDL完全一致,方便VINTF工具做兼容性检查。

3.5 AIDL HAL的整体架构

下面这张SVG图展示了AIDL HAL从定义到运行的完整链路:

AIDL HAL 架构与运行流程 AIDL接口定义 ITemperature.aidl 编译生成 aidl_interface + Android.bp 生成桩代码 BnTemperature / BpTemperature 服务端实现 继承BnTemperature 实现getTemperature() 注册到ServiceManager AServiceManager_addService 实例名: "default" 客户端发现 AServiceManager_getService 调用接口方法 关键说明: 1. @VintfStability 注解是AIDL HAL与普通AIDL的核心区别 2. 服务注册名称格式:包名.接口名/实例名 3. 客户端通过ServiceManager发现服务,支持动态绑定 4. 编译时需指定 stability: "vintf" 确保VINTF兼容性

3.6 实战经验总结

最后分享几个我在项目中踩过的坑:

  • 版本号管理: AIDL HAL的版本号写在包名里,比如 @V1、@V2。升级接口时一定要新建包路径,不要原地修改——否则旧客户端会崩溃。
  • 性能测试: 虽然AIDL HAL支持零拷贝,但实际效果取决于数据大小。我测过,传输1KB以下的数据,零拷贝优势不明显;超过64KB时,性能提升显著。
  • 调试技巧: 用 lshal 命令可以查看当前系统注册了哪些AIDL HAL服务。如果服务没注册成功,先跑一下 lshal | grep temperature 确认。

我的习惯: 每次写完AIDL HAL接口,我都会先写一个简单的单元测试,验证接口定义是否正确。用 aidl_test 框架可以快速验证跨进程调用是否正常。别等到集成测试才发现问题,那时候排查成本就高了。