第2章:HIDL——HAL接口定义语言

HIDL,全称是HAL Interface Definition Language。说白了,它就是Android系统里用来定义HAL接口的一种专用语言。你想想看,Framework要跟底层硬件打交道,总得有个明确的契约吧?HIDL就是这份契约。

我个人习惯把HIDL理解成「中间人」。它不关心你底层驱动怎么实现,也不管上层Service怎么调用,它只负责把双方沟通的格式定死。这样,Google能升级Framework,芯片厂商能改驱动,只要接口不变,大家各玩各的,互不干扰。

核心要点:HIDL的核心价值在于解耦。它让Android系统升级时,不需要重新编译所有的HAL库。只要接口定义不变,二进制兼容性就能保证。

2.1 HIDL基础语法

HIDL的语法,其实跟C++很像,但更精简。我刚开始接触时,觉得它就是个「瘦身版」的C++头文件。没有复杂的模板,没有异常处理,就是干干净净的接口声明。

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

package android.hardware.light@2.0;

interface ILight {
    enum Type : int32_t {
        BACKLIGHT,
        KEYBOARD,
        BUTTONS,
        NOTIFICATIONS,
        ATTENTION
    };

    struct LightState {
        int32_t color;
        int32_t flashMode;
        int32_t brightness;
    };

    getSupportedTypes() generates (vec<Type> types);
    setLight(Type type, LightState state) generates (Status status);
};

嗯,这里要注意几个关键点:

  • package声明:每个HIDL文件都必须以package开头,后面跟着包名和版本号。版本号用@符号分隔,比如@2.0。
  • interface关键字:定义接口,里面可以包含枚举、结构体和方法。
  • 方法定义:方法名后面跟参数列表,然后用generates关键字指定返回值。注意,HIDL的方法可以有多个返回值。

我的经验:写HIDL文件时,尽量把枚举和结构体定义在接口内部。这样生成的代码更紧凑,调用起来也更直观。我曾经见过有人把所有类型定义放在一个单独的types.hal里,结果找起来特别费劲。

2.2 包与命名空间

包名在HIDL里不是随便起的。它有一套严格的命名规则,直接决定了生成代码的目录结构和C++命名空间。

包名的格式是:vendor.product.hardware.module@version

组成部分 说明 示例
vendor 厂商或组织名 android, google, qcom
product 产品线或硬件类型 hardware, system, media
module 具体模块名 light, camera, audio
version 主版本.次版本 2.0, 1.3

举个例子,android.hardware.light@2.0这个包名,生成C++代码后,对应的命名空间是:

android::hardware::light::V2_0

目录结构则是:

out/soong/.intermediates/
  android/hardware/light/
    2.0/
      android.hardware.light@2.0_genc++/

为什么会这样设计?我个人的理解是:这种命名方式天然解决了命名冲突问题。不同厂商、不同版本的HAL实现,可以共存于同一个系统里。你想想看,如果Google和Qualcomm都定义了camera接口,没有命名空间隔离,那不乱套了?

避坑指南:我曾经遇到过一个问题:包名里的版本号写成了@2而不是@2.0。结果编译时死活找不到生成的代码。记住,版本号必须包含主版本和次版本,哪怕次版本是0也不能省略。

2.3 数据类型与接口定义

HIDL支持的数据类型,我把它分成三类:基础类型、复合类型和特殊类型。

基础类型

HIDL类型 C++对应类型 说明
int8_t / uint8_t int8_t / uint8_t 有/无符号8位整数
int16_t / uint16_t int16_t / uint16_t 有/无符号16位整数
int32_t / uint32_t int32_t / uint32_t 有/无符号32位整数
int64_t / uint64_t int64_t / uint64_t 有/无符号64位整数
float / double float / double 浮点数
bool bool 布尔值
string android::hardware::hidl_string 字符串

复合类型

  • struct:结构体,跟C++的struct一样,可以包含多个字段。
  • enum:枚举,可以指定底层类型,比如enum Type : int32_t。
  • union:联合体,用得比较少,但某些场景下很有用。
  • vec<T>:动态数组,相当于C++的std::vector。
  • hidl_array<T, N>:固定大小数组。

特殊类型

  • handle:用于传递底层文件描述符或原生句柄。
  • MemoryBlock:用于共享内存。
  • fmq_sync / fmq_unsync:快速消息队列,用于高效的数据传输。

接口定义时,方法可以有三种模式:

  1. 单向调用(oneway):调用后立即返回,不等待结果。适合通知类操作。
  2. 同步调用:默认模式,调用方会阻塞直到收到返回值。
  3. 回调(callback):通过generates关键字定义返回值,可以返回多个值。
// 单向调用
oneway notifyEvent(int32_t eventId);

// 同步调用,返回单个值
Status setParameter(int32_t key, int32_t value);

// 回调,返回多个值
getStatus() generates (int32_t code, string message);

我的建议:能用同步调用就别用oneway。oneway虽然快,但没法知道调用是否成功。我在调试一个传感器HAL时,用了oneway传数据,结果底层报错了上层完全不知道,排查了半天才发现问题。

2.4 生成C++代码

HIDL文件写好了,怎么变成C++代码?答案是:用hidl-gen工具。

编译系统会自动调用hidl-gen,但如果你想手动生成看看效果,可以这样:

hidl-gen -o output -L c++-impl \
  -r android.hardware:hardware/interfaces \
  -r android.hidl:system/libhidl/transport \
  android.hardware.light@2.0

这条命令会生成什么?我列一下:

生成文件 说明
ILight.h 接口头文件,包含纯虚类
ILight.cpp 接口实现骨架
LightAll.cpp 所有类型的序列化代码
BnHwLight.h / BpBwLight.h Binder通信的本地端和代理端
hwLight.h HAL实现需要继承的基类

你真正需要关心的,其实只有两个文件:

  • ILight.h:里面定义了纯虚类ILight,你的HAL实现需要继承它。
  • LightAll.cpp:里面包含了所有类型的序列化和反序列化代码,一般不需要修改。

生成的接口类长这样:

struct ILight : public ::android::hardware::HALInterfaceBase {
    // 枚举
    enum class Type : int32_t {
        BACKLIGHT = 0,
        KEYBOARD = 1,
        BUTTONS = 2,
        NOTIFICATIONS = 3,
        ATTENTION = 4
    };

    // 结构体
    struct LightState {
        int32_t color;
        int32_t flashMode;
        int32_t brightness;
    };

    // 方法
    virtual Return<void> getSupportedTypes(
        std::function<void(const hidl_vec<Type>&)> _hidl_cb) = 0;
    virtual Return<Status> setLight(Type type, 
        const LightState& state) = 0;
};

看到没?HIDL里的generates关键字,在C++里变成了回调函数参数。这是HIDL实现异步调用的方式。

避坑指南:生成代码时,-r参数指定的根路径必须正确。我曾经因为路径写错,导致生成的代码里#include找不到头文件。建议直接用Android源码里的hardware/interfaces和system/libhidl/transport这两个路径。

知识结构总览

下面这张图,把HIDL的核心知识点串起来了:

HIDL 知识体系结构图 .hal 源文件 包与命名空间 数据类型系统 接口定义 包名规则 vendor.product.hardware .module@version 类型分类 基础 | 复合 | 特殊 struct/enum/vec/handle 方法模式 oneway | 同步 | 回调 generates 多返回值 hidl-gen 代码生成器 ILight.h(接口) LightAll.cpp(序列化) BnHw/BpBw(Binder) hwLight.h(基类) 开发者只需继承 hwLight.h,实现具体方法即可

从这张图能看出来,HIDL的整个流程是单向的:从.hal源文件出发,经过hidl-gen处理,生成一系列C++代码。你作为HAL开发者,只需要关注最左边和最右边——写好.hal文件,然后实现生成的接口类。

嗯,这一章的内容就到这里。HIDL的语法其实不复杂,但细节很多。尤其是包名和版本号,写错了编译都过不了。我建议你动手写一个简单的light HAL试试,走一遍完整的流程,比看十遍文档都管用。