3. HIDL 接口定义实战:定义 .hal 文件、package 声明、数据类型、方法定义

在 Treble 架构中,HIDL(HAL Interface Definition Language)是连接 Framework 与 Vendor HAL 的核心契约语言。本章通过实战方式,系统讲解如何编写一个完整的 .hal 接口文件,涵盖包声明、数据类型选择与方法定义规范。掌握这些基础,是后续进行接口测试验证的前提。

实战目标:定义一个名为 IHealth 的 HIDL 接口,用于获取设备电池温度与充电状态,并支持回调通知。该接口将贯穿后续章节的测试用例设计。

3.1 .hal 文件结构与包声明

每个 HIDL 接口文件以 .hal 为后缀,文件路径必须与包名严格对应。包名遵循反向域名风格,并包含版本号。

包声明规则:

  • 格式:package <vendor>.<subsystem>.<module>@<major>.<minor>;
  • 版本号必须与文件所在目录的版本子目录一致。
  • 一个包可以包含多个 .hal 文件,但每个文件只能声明一个包。

示例:IHealth.hal 文件结构

// 文件路径:hardware/interfaces/health/2.0/IHealth.hal
package android.hardware.health@2.0;

import android.hardware.health@2.0::types;

interface IHealth {
    // 方法定义将在后续小节展开
};
路径映射规则:包名 android.hardware.health@2.0 对应文件系统路径 hardware/interfaces/health/2.0/。所有 .hal 文件必须放置在该目录下,否则编译系统无法识别。
HIDL 包声明与文件路径映射关系 包声明(.hal 文件内) package android.hardware.health@2.0; 文件系统路径 hardware/interfaces/health/2.0/ 版本子目录必须与 @major.minor 一致 hidl-gen 编译工具自动校验路径

3.2 数据类型定义与使用

HIDL 支持多种数据类型,包括基础类型、枚举、结构体、联合体以及集合类型。合理选择数据类型直接影响接口的跨进程传输效率与可测试性。

常用数据类型一览表:

类别 类型 HIDL 关键字 说明
基础类型 整数 int8_t, uint8_t, int32_t, uint32_t, int64_t, uint64_t 固定宽度,跨平台一致
浮点 float, double IEEE 754 标准
布尔 bool true / false
字符串 string UTF-8 编码,自动管理内存
集合类型 数组 T[] 可变长度数组
向量 vec<T> 动态数组,推荐用于传输列表
自定义类型 枚举 enum 具名整数常量,增强可读性
结构体 struct 复合数据容器

实战:在 types.hal 中定义健康状态枚举与结构体

// 文件路径:hardware/interfaces/health/2.0/types.hal
package android.hardware.health@2.0;

enum HealthStatus : int32_t {
    GOOD = 0,
    OVERHEAT = 1,
    COLD = 2,
    CRITICAL = 3,
    UNKNOWN = 4
};

struct HealthInfo {
    float batteryTemperature;       // 电池温度,单位摄氏度
    int32_t batteryLevel;           // 电池电量百分比 0-100
    HealthStatus currentStatus;     // 当前健康状态
    bool isCharging;                // 是否正在充电
};
注意:枚举类型必须显式指定底层整数类型(如 int32_t),否则编译会报错。结构体成员不支持默认值,所有字段必须在调用时显式赋值。
HIDL 数据类型层级关系 HIDL 数据类型 基础类型 集合类型 自定义类型 int32/float/bool string T[] vec<T> enum struct 选择原则:跨进程传输优先使用基础类型和 vec<T>,避免深层嵌套结构体

3.3 方法定义与回调机制

HIDL 接口方法支持同步调用与单向调用(oneway),同时可以通过回调接口(callback)实现异步通知。方法定义需要明确参数方向(in / out / inout)以及返回值类型。

方法定义语法:

interface IHealth {
    // 同步方法:返回 HealthInfo 结构体
    getHealthInfo() generates (HealthInfo info);

    // 单向方法:设置阈值,不等待返回
    oneway setTemperatureThreshold(float threshold);

    // 回调注册:客户端传入回调接口,服务端在状态变化时调用
    registerCallback(IHealthCallback callback) generates (bool success);
};

// 回调接口定义
interface IHealthCallback {
    oneway onHealthStatusChanged(HealthStatus newStatus);
};

参数方向说明:

方向关键字 含义 使用场景
in 输入参数,由客户端传入服务端 默认方向,可省略
out 输出参数,由服务端填充返回给客户端 用于返回多个值
inout 输入输出参数,双向传递 较少使用,影响性能
关键设计原则:
  • 优先使用 generates 返回多个值,避免使用 inout 参数。
  • 回调接口中的方法建议声明为 oneway,避免客户端阻塞服务端线程。
  • 每个接口文件只能包含一个 interface 定义,回调接口需单独定义在另一个 .hal 文件中。
HIDL 方法调用流程与回调机制 客户端(Framework) 调用 getHealthInfo() 服务端(Vendor HAL) 实现 getHealthInfo() 同步调用(阻塞) generates (HealthInfo info) 注册回调接口 registerCallback() 状态变化时触发回调 onHealthStatusChanged() oneway 异步通知,不阻塞 回调接口必须单独定义,且方法建议声明为 oneway 以避免死锁

3.4 完整 IHealth 接口实战

综合以上知识点,下面给出完整的 IHealth 接口定义,包含类型导入、方法声明与回调接口。该定义将作为后续测试验证的基准接口。

// ============================================
// 文件:hardware/interfaces/health/2.0/types.hal
// ============================================
package android.hardware.health@2.0;

enum HealthStatus : int32_t {
    GOOD = 0,
    OVERHEAT = 1,
    COLD = 2,
    CRITICAL = 3,
    UNKNOWN = 4
};

struct HealthInfo {
    float batteryTemperature;
    int32_t batteryLevel;
    HealthStatus currentStatus;
    bool isCharging;
};

// ============================================
// 文件:hardware/interfaces/health/2.0/IHealthCallback.hal
// ============================================
package android.hardware.health@2.0;

interface IHealthCallback {
    oneway onHealthStatusChanged(HealthStatus newStatus);
    oneway onTemperatureAlert(float temperature);
};

// ============================================
// 文件:hardware/interfaces/health/2.0/IHealth.hal
// ============================================
package android.hardware.health@2.0;

import android.hardware.health@2.0::types;
import android.hardware.health@2.0::IHealthCallback;

interface IHealth {
    // 获取当前健康信息
    getHealthInfo() generates (HealthInfo info);

    // 设置温度告警阈值
    oneway setTemperatureThreshold(float threshold);

    // 注册回调,返回是否成功
    registerCallback(IHealthCallback callback) generates (bool success);

    // 注销回调
    unregisterCallback(IHealthCallback callback) generates (bool success);
};
编译验证:使用 hidl-gen 工具可以自动生成 C++ 或 Java 的客户端与服务端骨架代码。命令示例:
hidl-gen -o output -L c++ -randroid.hardware:hardware/interfaces android.hardware.health@2.0

3.5 接口定义常见错误与避坑

在实际开发中,接口定义阶段的错误往往在测试阶段才暴露。以下列出高频问题及解决方案:

错误类型 典型表现 根因 解决方案
包名路径不匹配 hidl-gen 报 "package path mismatch" .hal 文件所在目录与包名版本不一致 检查目录名是否与 @major.minor 完全一致
枚举缺少底层类型 编译报 "enum must specify underlying type" 未显式声明 : int32_t 为每个枚举添加底层整数类型
回调接口未单独定义 接口内嵌 interface 定义导致编译失败 HIDL 不允许接口嵌套 将回调接口提取到独立 .hal 文件
oneway 方法有返回值 编译报 "oneway methods cannot generate" oneway 方法不能使用 generates 移除 generates,或改为同步方法
结构体字段顺序变化 跨版本兼容性测试失败 结构体字段顺序影响内存布局 新增字段只能追加到末尾,不能插入中间
版本兼容性红线:一旦接口发布(如 health@2.0),已定义的字段顺序、类型、方法签名均不可修改。只能通过增加新版本(如 health@2.1)来扩展。测试验证必须覆盖接口的向后兼容性。