4. AIDL HAL 实战:从 HIDL 到 AIDL 的演进之路

这一章我们来聊聊 AIDL for HAL。说实话,这个变化挺大的。我记得刚接触 Android 时,HAL 层还是用传统的 hw_module_t 结构体,后来 Google 推出了 HIDL,现在又全面转向 AIDL。很多人问我:「为什么折腾来折腾去?」

嗯,其实原因很简单——生态统一。你想想看,Android 系统里本来就有 AIDL 用于应用层 IPC,现在 HAL 层也用同一套,工具链、序列化、稳定性保障都能复用。说白了,就是少维护一套东西。

核心观点:AIDL for HAL 不是简单的语法替换,而是 Android 系统走向「统一 IPC 框架」的关键一步。它让 HAL 开发变得更像普通 Android 开发,降低了学习曲线。

4.1 AIDL for HAL 的演进背景

我们先回顾一下历史。Android 8.0 之前,HAL 层用的是 传统 HAL,也就是 hw_module_t 这种 C 结构体。问题是,这种设计没有进程隔离——HAL 运行在系统服务进程里,一旦崩溃,整个系统跟着挂。

Android 8.0 引入了 Treble 架构,核心就是 HIDL。HIDL 把 HAL 服务化,每个 HAL 跑在独立进程里,通过 Binder 通信。这解决了稳定性问题,但也带来了新麻烦:

  • 学习成本高:HIDL 语法和 AIDL 不一样,开发者要学两套东西
  • 工具链割裂:HIDL 的 hidl-gen 和 AIDL 的 aidl 是两套生成器
  • 生态不统一:应用层用 AIDL,HAL 层用 HIDL,中间还要做转换

所以从 Android 12 开始,Google 正式推荐用 AIDL for HAL。我在项目中遇到过这种情况:团队里新来的同事刚学会 HIDL,结果发现新项目又要切 AIDL,确实有点折腾。但长远看,这是对的。

我的建议:新项目直接用 AIDL for HAL。老项目如果还在维护期,可以逐步迁移。不要同时维护两套接口定义,那会把人逼疯。

4.2 AIDL 接口定义与 HIDL 的差异

语法上,AIDL 和 HIDL 确实很像,但细节差异不少。我整理了一个对比表,方便你快速对照:

对比维度 HIDL AIDL for HAL
文件后缀 .hal .aidl
包名规则 android.hardware.xxx@1.0 android.hardware.xxx.IVendor
接口定义 interface IMyHAL { ... } interface IMyHAL { ... }
方法返回值 通过 generate 回调 直接返回值或 oneway
数据类型 HIDL 原生类型 + vec<T> AIDL 原生类型 + List<T>
服务注册 registerAsService() ServiceManager.addService()
稳定性级别 通过版本号管理 通过 @VintfStability 注解

举个例子,HIDL 里定义一个获取传感器数据的方法:

// HIDL 方式
interface ISensor {
    getData() generates (SensorData data);
};

换成 AIDL 就变成了:

// AIDL for HAL 方式
@VintfStability
interface ISensor {
    SensorData getData();
}

看到了吗?AIDL 更简洁,不需要 generates 这种关键字。而且 @VintfStability 这个注解很重要,它告诉系统这个接口是稳定的,需要纳入 VINTF 兼容性检查。

注意:如果不加 @VintfStability,系统不会把你的 HAL 服务纳入兼容性保障。我曾经见过一个项目,因为漏了这个注解,OTA 升级后 HAL 服务注册失败,整批设备变砖。嗯,血的教训。

4.3 AIDL HAL 的服务注册与发现

服务注册这块,AIDL 和 HIDL 的思路差不多,但 API 不同。我们来看一个完整的例子。

首先,定义接口文件 IVendorLight.aidl:

@VintfStability
package android.hardware.light;

interface IVendorLight {
    int getBrightness();
    void setBrightness(int value);
}

然后,在服务端实现这个接口:

public class LightService extends IVendorLight.Stub {
    @Override
    public int getBrightness() {
        return currentBrightness;
    }

    @Override
    public void setBrightness(int value) {
        currentBrightness = value;
        // 操作硬件
    }
}

最后,注册服务:

// 服务端注册
LightService service = new LightService();
ServiceManager.addService(
    "android.hardware.light.IVendorLight/default",
    service
);

客户端发现服务也很简单:

// 客户端发现
IBinder binder = ServiceManager.getService(
    "android.hardware.light.IVendorLight/default"
);
IVendorLight light = IVendorLight.Stub.asInterface(binder);
int brightness = light.getBrightness();

这里有个关键点:服务名必须和 VINTF manifest 里声明的一致。否则系统在启动时检查不到对应的 HAL 服务,会直接报错。

避坑指南:我曾经在调试时发现服务注册成功了,但客户端就是连不上。查了半天,原来是服务名里的大小写写错了。AIDL 的服务名是大小写敏感的,这个细节很容易忽略。

4.4 AIDL HAL 的稳定性保障机制

稳定性是 HAL 层的命门。AIDL for HAL 提供了几层保障:

  1. @VintfStability 注解:标记接口为稳定接口,纳入兼容性检查
  2. 版本号管理:通过 @VintfStability(majorVersion=1, minorVersion=0) 控制
  3. VINTF manifest 校验:系统启动时检查所有声明的 HAL 服务是否可用
  4. 进程隔离:每个 HAL 服务运行在独立进程中,崩溃不影响系统

我画了一张图,帮你理解 AIDL HAL 的稳定性保障体系:

AIDL HAL 稳定性保障体系 @VintfStability 接口定义 编译时:VINTF 兼容性检查 启动时:ServiceManager 注册 + Manifest 校验 运行时:进程隔离 + 崩溃恢复 + 版本兼容 系统稳定性保障 关键点 接口一旦发布 不能随意修改 必须向前兼容

这个体系的核心思想是:把问题暴露在编译期和启动期,而不是运行时。你想想看,如果 HAL 服务在用户使用时才崩溃,那体验得多糟糕。

关于版本管理,AIDL 的做法和 HIDL 不太一样。HIDL 通过 @1.0、@1.1 这种版本号来管理,而 AIDL 用的是 @VintfStability 注解里的版本字段:

// 版本 1.0
@VintfStability(majorVersion=1, minorVersion=0)
interface IVendorLight {
    int getBrightness();
    void setBrightness(int value);
}

// 版本 1.1(新增方法)
@VintfStability(majorVersion=1, minorVersion=1)
interface IVendorLight {
    int getBrightness();
    void setBrightness(int value);
    void setColor(int r, int g, int b);  // 新增
}

这里有个原则:只能增加,不能删除或修改已有方法。否则老版本的客户端调用时会出问题。我在项目中遇到过这种情况:有人觉得某个方法参数不合理,直接改了签名,结果 OTA 后老设备上的应用全部崩溃。嗯,这种错误犯一次就够了。

总结一下:AIDL for HAL 不是简单的技术选型,它是 Android 系统走向统一 IPC 框架的关键一步。从 HIDL 到 AIDL,语法更简洁,工具链更统一,稳定性保障更完善。如果你正在设计新的 HAL 接口,我建议直接上 AIDL。