2. HAL接口定义语言(AIDL/HIDL)
在 Treble 架构中,HAL 接口定义语言是连接 Framework(系统框架)与 Vendor(供应商)实现的契约。Android 先后引入了 HIDL(HAL Interface Definition Language)和 AIDL(Android Interface Definition Language)作为接口定义工具。理解两者的差异、适用场景以及文件结构,是编写可测试、可维护 HAL 接口的前提。
2.1 HIDL 与 AIDL 的核心对比
HIDL 是 Android 8.0(Treble 首发版本)引入的专用 HAL 接口语言,旨在严格分离 Framework 和 Vendor 实现。AIDL 则是 Android 传统的跨进程通信(IPC)接口语言,从 Android 11 开始被官方推荐用于 HAL 开发。下表从多个维度对比两者的关键差异:
| 对比维度 | HIDL | AIDL(HAL 模式) |
|---|---|---|
| 引入版本 | Android 8.0(Treble 首发) | Android 11(HAL 场景推荐) |
| 设计目标 | 严格分离 system.img 与 vendor.img | 统一 Framework 与 HAL 的 IPC 语言 |
| 接口稳定性 | 通过 .hal 文件版本号(如 1.0、2.0)保证 | 通过 @VintfStability 注解和版本化包名保证 |
| 后端实现 | 生成 C++(passthrough/binderized) | 生成 Java / C++ / NDK(native) |
| 数据类型 | HIDL 原生类型 + struct/enum/union/vec | Parcelable + 原生类型 + 泛型 + 接口回调 |
| 错误处理 | Return<T> + 异常状态码 | 异常(Java)/ Status(C++) |
| 工具链 | hidl-gen、hidl2aidl 转换工具 | aidl(支持 --lang=cpp/ndk/java) |
| 测试友好度 | 需额外编写 VTS 测试用例 | 原生支持单元测试 + 模拟桩(Mock) |
关键认知:HIDL 是 Treble 架构的“原配”语言,但 AIDL 从 Android 11 起成为 Google 推荐的统一接口语言。新项目优先选择 AIDL(HAL 模式),存量 HIDL 接口可通过
hidl2aidl 工具迁移。
2.2 选择策略:何时用 HIDL,何时用 AIDL?
选择接口定义语言需要综合考虑项目阶段、兼容性要求和团队技术栈。以下决策流程可以帮助你做出合理选择:
- 项目启动时间:若项目基于 Android 11+,优先选择 AIDL(HAL 模式)。
- 存量接口:已有 HIDL 接口且无重构计划,继续使用 HIDL;若需统一技术栈,使用
hidl2aidl迁移。 - 跨进程性能:两者性能接近,但 AIDL 的 NDK 后端在零拷贝场景下略优。
- 测试需求:AIDL 原生支持 Mock 和单元测试,适合测试驱动开发(TDD)。
- 生态兼容:HIDL 在 Android 8-10 设备上更成熟,AIDL 在 Android 11+ 上更受官方支持。
推荐策略:新 HAL 模块(Android 11+)使用 AIDL + @VintfStability;存量 HIDL 模块保持 HIDL 直至迁移窗口;混合场景可通过
hidl2aidl 桥接。
下图展示了两种语言在 Treble 架构中的位置与选择路径:
2.3 HIDL 接口文件基本结构
HIDL 接口文件以 .hal 为扩展名,遵循严格的包名和版本号规则。一个典型的 HIDL 接口文件包含以下要素:
- 包名声明:格式为
package vendor.xxx.hardware.xxx@1.0; - 接口定义:使用
interface IMyHAL { ... };包裹方法声明 - 数据类型:支持 struct、enum、union、vec<T> 等
- 方法签名:返回类型为
Return<T>,参数支持 in/out 方向
以下是一个完整的 HIDL 接口文件示例:
// IMySensor.hal
package vendor.example.hardware.sensor@1.0;
interface IMySensor {
// 枚举定义
enum Status : int32_t {
OK = 0,
ERROR = 1,
TIMEOUT = 2
};
// 结构体定义
struct SensorData {
int32_t x;
int32_t y;
int32_t z;
int64_t timestamp;
};
// 接口方法
Return<Status> getSensorData(out SensorData data);
Return<void> setSamplingRate(int32_t rateHz);
Return<vec<SensorData>> getBatchData(int32_t count);
};
HIDL 文件注意事项:
- 版本号必须与目录结构一致(如
1.0对应sensor/1.0/) - 接口名必须以
I开头(如IMySensor) - 方法返回类型必须使用
Return<>包装 - 不支持方法重载(overload)
2.4 AIDL 接口文件基本结构(HAL 模式)
从 Android 11 开始,AIDL 支持 @VintfStability 注解,使其能够用于定义稳定的 HAL 接口。AIDL 文件以 .aidl 为扩展名,结构更接近 Java 接口风格:
- 包名声明:与 Java 包名一致,如
package vendor.example.hardware.sensor; - 稳定性注解:在接口前添加
@VintfStability - 接口定义:使用
interface IMySensor { ... } - 数据类型:支持 Parcelable、List、Map、接口回调等
- 方法签名:支持异常抛出,参数支持 in/out/inout 方向
以下是一个 AIDL(HAL 模式)接口文件示例:
// IMySensor.aidl
package vendor.example.hardware.sensor;
@VintfStability
interface IMySensor {
// 枚举定义(AIDL 使用 @IntDef 或直接 int 常量)
const int STATUS_OK = 0;
const int STATUS_ERROR = 1;
const int STATUS_TIMEOUT = 2;
// 自定义 Parcelable 类型(需单独定义 .aidl 文件)
// 此处使用 in/out 参数传递
/**
* 获取传感器数据
* @param data 输出参数,填充传感器数据
* @return 状态码
*/
int getSensorData(out SensorData data);
/**
* 设置采样率
* @param rateHz 采样率(Hz)
*/
void setSamplingRate(int rateHz);
/**
* 批量获取数据
* @param count 请求数量
* @return 传感器数据列表
*/
List<SensorData> getBatchData(int count);
}
对应的 SensorData.aidl 文件:
// SensorData.aidl
package vendor.example.hardware.sensor;
@VintfStability
parcelable SensorData {
int x;
int y;
int z;
long timestamp;
}
AIDL 优势:支持
in/out/inout 方向参数,支持异常抛出,支持接口回调(oneway),且与 Android Framework 的 IPC 机制完全统一,测试时可直接使用 Mockito 或 aidl-mock 工具。
2.5 接口文件结构对比图
下图直观对比 HIDL 与 AIDL 接口文件的结构差异:
2.6 测试验证视角的关键差异
从测试验证角度,HIDL 和 AIDL 对测试策略的影响显著:
| 测试维度 | HIDL | AIDL |
|---|---|---|
| 单元测试 | 需手动编写 Mock 桩(C++) | 原生支持 Mock(Java Mockito / C++ GMock) |
| 集成测试 | 依赖 VTS 框架 + 测试用例 | 可直接使用 Android Test 框架 |
| 接口变更检测 | 版本号 + hidl-gen 编译检查 | @VintfStability + aidl 编译检查 |
| 跨语言测试 | C++ 为主,Java 需 JNI 桥接 | Java / C++ / NDK 原生支持 |
| 自动化生成 | hidl-gen 生成测试桩 | aidl --gen-test 生成测试框架 |
测试建议:若团队以 Java/Kotlin 为主,优先选择 AIDL 以降低测试门槛;若团队以 C++ 为主且项目基于 Android 10 以下,HIDL 仍是稳妥选择。无论选择哪种语言,接口定义阶段就应同步编写测试用例,实现“测试先行”。