HIDL:Android HAL 接口的“通用语言”

说到 HIDL,我得先跟你聊聊它的定位。在 Android 8.0 之后,Google 把 HAL 和框架层彻底解耦了。HAL 跑在独立的进程里,框架层想调用底层硬件,就得通过一个稳定的 IPC 通道。HIDL 就是定义这个通道的“契约语言”。

说白了,HIDL 就是一套接口描述语言。你用它写好 .hal 文件,然后工具链自动帮你生成 C++ 或 Java 的客户端和服务端代码。我个人习惯把 HIDL 看作是 Android 生态里的 IDL(接口定义语言),但它比传统的 AIDL 更重、更强调稳定性。

核心观点: HIDL 不是为了替代 AIDL,而是为了解决 HAL 层长期存在的“接口碎片化”问题。它强制要求接口版本化、向后兼容,这是 AIDL 做不到的。

HIDL 基础语法:从 .hal 文件说起

一个典型的 HIDL 接口文件长这样:

package android.hardware.light@2.0;

interface ILight {
    /**
     * 设置灯的状态
     * @param light 灯的类型
     * @param state 灯的状态
     * @return Status 操作结果
     */
    setLight(LightType light, LightState state) generates (Status status);
    
    /**
     * 获取支持的灯类型列表
     * @return vec<LightType> 灯类型列表
     */
    getSupportedTypes() generates (vec<LightType> types);
};

嗯,这里有几个关键点:

  • package 声明:格式是 package 包名@版本号。版本号必须是整数,比如 @2.0。我见过不少新手把版本号写成 @2.0.1,这是错的——HIDL 只支持主版本和次版本。
  • interface 关键字:定义接口,接口名必须以 I 开头,这是约定俗成的规矩。
  • 方法签名:HIDL 的方法都是异步的。返回值用 generates 关键字声明,而不是传统的 return。这一点跟 AIDL 完全不同。
  • 注释:支持 /** */ 的 JavaDoc 风格注释,工具链会把这些注释提取到生成的代码里。
我的经验: 写 .hal 文件时,尽量把注释写完整。因为生成的代码里会保留这些注释,团队其他人看接口文档时直接看头文件就行,不用来回翻 .hal 文件。

数据类型:HIDL 的“类型系统”

HIDL 的数据类型比 AIDL 丰富得多。我把它分成三类:

类别 类型 说明
基础类型 int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, float, double 跟 C++ 标准类型一一对应,没有 byte 或 short 这种模糊类型
容器类型 vec<T>, string, hidl_array<T, N> vec 是动态数组,hidl_array 是固定大小数组
复合类型 struct, enum, union, safe_union safe_union 是 HIDL 特有的,比 C++ 的 union 更安全

举个例子,定义一个结构体:

struct LightState {
    uint32_t color;
    uint32_t brightness;
    FlashMode flashMode;
};

enum FlashMode : uint32_t {
    NONE = 0,
    TIMED = 1,
    ALWAYS = 2
};

这里要注意:enum 必须显式指定底层类型,不能省略。我曾经在项目里漏写了 : uint32_t,编译直接报错,排查了半天才发现是枚举类型没指定宽度。

接口定义:版本化与继承

HIDL 最强大的特性之一就是版本化。你可以在同一个包名下定义多个版本的接口:

// 文件: android/hardware/light/2.0/ILight.hal
package android.hardware.light@2.0;
interface ILight {
    setLight(LightType light, LightState state) generates (Status status);
};

// 文件: android/hardware/light/2.1/ILight.hal
package android.hardware.light@2.1;
interface ILight extends @2.0::ILight {
    setLightBrightness(LightType light, uint32_t brightness) generates (Status status);
};

看到 extends @2.0::ILight 了吗?这是 HIDL 的接口继承语法。新版本可以继承旧版本,然后添加新方法。这样做的好处是:旧版本的客户端不需要修改代码,就能继续使用新版本的 HAL 服务。

避坑指南: 我曾经在项目中直接修改了 2.0 版本的接口,添加了一个新方法。结果所有依赖 2.0 接口的客户端全部编译失败。正确的做法是创建 2.1 版本,继承 2.0 再添加新方法。记住:已发布的接口永远不要修改,只能扩展。

服务端与客户端实现:一图看懂

先看一张整体架构图,理解 HIDL 的通信模型:

HIDL 服务端与客户端通信架构 客户端进程 应用 / Framework HIDL 客户端桩 (Proxy) 服务端进程 HIDL 服务端桩 (Stub) HAL 实现 (硬件操作) HwBinder IPC 通道 说明: 1. 客户端通过 HwBinder 调用服务端方法,参数被序列化后跨进程传输。 2. 服务端反序列化参数,调用实际的 HAL 实现,结果再序列化返回。 典型调用流程 1. 获取服务 2. 构造参数 3. IPC 调用 4. 处理结果

实现一个 HIDL 服务端,大致分三步:

  1. 生成代码:用 hidl-gen 工具从 .hal 文件生成 C++ 桩代码。
  2. 实现接口:继承生成的 ILight 类,实现所有纯虚方法。
  3. 注册服务:调用 registerAsService() 把服务注册到 HwBinder 服务管理器。

客户端就更简单了:

// 客户端获取服务并调用
sp<ILight> lightService = ILight::getService();
if (lightService != nullptr) {
    LightState state;
    state.color = 0x00FF00;
    state.brightness = 128;
    Status status;
    auto ret = lightService->setLight(LightType::BACKLIGHT, state, [&](Status s) {
        status = s;
    });
    // 注意:这里是异步回调,需要等待结果
}

嗯,这里有个坑:HIDL 的调用默认是异步的。如果你需要同步等待结果,得用 hidl::details::waitForFuture() 或者自己管理回调。我刚开始做 HIDL 开发时,没注意这个异步特性,直接拿返回值当结果用,结果拿到的全是默认值。

HIDL vs AIDL:到底选哪个?

这个问题我经常被问到。直接上对比表:

维度 HIDL AIDL
设计目标 HAL 接口标准化、版本化 应用层 IPC 通信
版本管理 强制版本化,支持接口继承 无版本概念,需手动管理
数据类型 丰富,支持 vec、safe_union、hidl_array 基础类型 + Parcelable
调用方式 异步回调(默认) 同步(默认),支持 oneway
性能 优化了批量传输,适合高频调用 通用 IPC,适合低频调用
使用场景 HAL 层、系统服务 应用层、跨进程回调

我的建议很简单:

  • 如果你在写 HAL 层代码,用 HIDL。它强制版本化,能避免接口不兼容的问题。
  • 如果你在写应用层或者系统服务之间的通信,用 AIDL。它更轻量,学习成本更低。
  • 如果你需要高频调用(比如传感器数据流),HIDL 更合适,因为它对批量传输做了优化。
个人经验: 我在一个项目中同时用了 HIDL 和 AIDL。HAL 层用 HIDL 定义接口,保证版本兼容;HAL 服务再把数据通过 AIDL 抛给上层应用。这样既保证了底层稳定性,又让上层开发更灵活。

避坑指南:HIDL 开发中的常见问题

最后分享几个我踩过的坑:

  • 接口版本号不能回退:一旦发布了 @2.0,就不能再发布 @1.5。版本号只能递增。
  • 方法参数不能太多:HIDL 对方法参数个数有限制(我记得是 32 个)。如果参数太多,建议封装成 struct。
  • 回调接口要小心生命周期:客户端传进来的回调对象,服务端不能持有太久。我曾经因为持有了一个已经销毁的回调,导致进程崩溃。
  • 调试时用 lshal 命令:这个命令能列出所有注册的 HIDL 服务,是排查服务注册问题的利器。

好了,HIDL 的基础内容就聊到这里。记住一句话:HIDL 是 Android HAL 层的“契约”,写好了能让整个系统稳定运行好几年,写砸了就是无尽的兼容性噩梦。