HIDL 语法详细总结
HIDL语法详细总结
一、HIDL基础概述
HIDL(HAL Interface Definition Language)是Android 8.0(Oreo)随Treble项目引入的硬件抽象层接口定义语言,核心目标是实现Android Framework与HAL的彻底解耦,解决Android碎片化OTA升级难题,让厂商无需重新编译HAL即可完成Framework升级。
- 运行模式:分为绑定式模式(Binderized)(基于Binder IPC跨进程通信,主流模式)和直通模式(Passthrough)(同进程dlopen加载,仅C++支持,用于兼容旧版HAL)。
- 生命周期:Android 10起官方宣布逐步弃用,推荐使用AIDL替代,但仍是Android 8-13版本HAL开发的核心规范,存量设备仍大量使用。
- 核心设计原则:接口发布后即冻结,仅可通过版本升级扩展,严格保证前向兼容;RPC仅使用in参数,避免内存所有权歧义;数据结构采用C++标准布局,无需解包即可直接用于IPC传输。
二、核心语法规则与文件结构
1. 文件规范
- 后缀名:统一为
.hal - 两类核心文件:
- 接口文件:如
IFoo.hal,每个文件仅能定义一个接口,接口名必须与文件名一致,以I开头。 - 类型定义文件:
types.hal,仅能定义用户自定义类型(结构体、枚举、联合体等),不能定义接口方法,包内所有接口自动导入该文件,无需显式import。
- 接口文件:如
- 目录结构:
根目录/模块名/[版本号]/,例如hardware/interfaces/nfc/1.0/;核心系统包根目录为hardware/interfaces,厂商包根目录为vendor/[厂商名]/interfaces。
2. 基础语法规则
- 语句终止:所有语句必须以分号
;结尾,包括最后一个元素。 - 空白符:换行符与空格、制表符等效,除单行注释外,换行不影响语法解析。
- 预处理器:不支持C/C++预处理器(无#define、#include等)。
- 大小写敏感:标识符、关键字、类型名均大小写敏感。
- 标识符规则:遵循C标准标识符规则,由字母、数字、下划线组成,不能以数字开头。
3. 注释规范
- 文档注释:
/** */,仅可用于类型、方法、字段、枚举值声明,会被编译到生成的代码文档中。 - 多行注释:
/* */,用于普通说明,不会生成到文档中。 - 单行注释:
//,行尾注释,不推荐用于文档说明,不会生成到输出代码中。
三、包声明与导入
1. 包声明(Package)
- 语法:
package 包名@主版本号.次版本号; - 命名规范:
- 核心系统包:
android.hardware.子系统名[.子模块]@x.y,全小写,多单词用snake_case或子模块拆分。 - 厂商包:
vendor.厂商名.hardware.子系统名@x.y
- 核心系统包:
- 位置:必须位于文件首行(许可证注释之后),每个.hal文件有且仅有一个包声明。
- 命名空间映射:包名会自动转换为对应语言的命名空间,例如
android.hardware.nfc@1.0在C++中转为android::hardware::nfc::V1_0。
2. 导入声明(Import)
用于导入其他包或同包内的类型、接口,避免全限定名重复书写。
- 语法格式:
- 全量包导入:
import 包名@x.y;,导入该包下所有类型和接口。 - 指定类型导入:
import 包名@x.y::类型名;,仅导入指定的用户自定义类型或接口。 - 同包导入:
import 接口名/类型名;,导入同包内的其他接口或类型。 - 仅导入类型:
import 包名@x.y::types;,仅导入该包的types.hal中定义的类型。
- 全量包导入:
- 核心规则:
- 当前包的types.hal会被自动导入,无需显式import。
- 导入语句必须位于包声明之后,接口/类型定义之前。
- 导入分组按规范排序:系统包→厂商包→同包内导入,组内按字母序排列,组间空行分隔。
四、完整数据类型系统
HIDL数据类型分为基础类型、复合类型、特殊类型三大类,所有类型均有明确的跨语言映射规则,保证IPC传输的一致性。
1. 基础标量类型
| HIDL类型 | 说明 | C++映射 | Java映射 |
|---|---|---|---|
| int8_t | 8位有符号整数 | int8_t | byte |
| uint8_t | 8位无符号整数 | uint8_t | byte(无符号语义需自行处理) |
| int16_t | 16位有符号整数 | int16_t | short |
| uint16_t | 16位无符号整数 | uint16_t | short |
| int32_t | 32位有符号整数 | int32_t | int |
| uint32_t | 32位无符号整数 | uint32_t | int |
| int64_t | 64位有符号整数 | int64_t | long |
| uint64_t | 64位无符号整数 | uint64_t | long |
| float | 32位单精度浮点数 | float | float |
| double | 64位双精度浮点数 | double | double |
| bool | 布尔值 | bool | boolean |
| string | UTF-8编码字符串 | std::string | String |
2. 定长数组
- 语法:
类型[数组长度],数组长度必须是大于0的C风格常量表达式constexpr。 - 示例:
int32_t[4] position;、string[10] nameList; - 规则:长度固定,编译期确定,支持多维数组,如
uint8_t[3][3] matrix;
3. 动态向量vec
- 语法:
vec<元素类型>,对应C++的std::vector,动态长度数组。 - 示例:
vec<uint8_t> dataBuffer;、vec<vec<int32_t>> multiDimVector; - 特殊规则:接口类型仅支持单层vec嵌套,即
vec<IFoo>合法,vec<vec<IFoo>>非法。 - 复合用法:支持向量与数组嵌套,如
vec<bool[4]> arrayVector;
4. 特殊类型
memory:对应Ashmem共享内存句柄,用于跨进程大数据传输,映射为hidl_memory类型。pointer:无类型指针,仅用于直通模式,绑定式模式下不支持跨进程传输,映射为void*。native_handle_t:原生句柄,用于传递文件描述符、驱动句柄等,支持跨进程传输。bitfield<T>:位域类型,T必须是用户定义的枚举类型,用于按位存储枚举值,实现位掩码功能。fmq_sync<T>/fmq_unsync<T>:快速消息队列(Fast Message Queue),分别对应同步/无锁异步模式,用于高频率低延迟的跨进程数据传输,T必须是可序列化的固定大小类型。
5. 接口类型
- 所有自定义interface都是合法的类型,可作为方法参数、返回值、结构体字段。
- 通用接口类型
interface,等价于android.hidl.base@1.0::IBase,是所有HIDL接口的基类。
五、用户自定义类型
HIDL支持丰富的用户自定义类型,所有类型均可用于方法参数、返回值和复合类型嵌套,不支持前向声明,类型必须在使用前定义(同文件内后续定义编译器会自动解析)。
1. 枚举(enum)
- 语法:
enum 枚举名 : 底层标量类型 {
枚举值1 [= 常量表达式],
枚举值2 [= 常量表达式],
...
};- 核心规则:
- 底层类型必须是标量整数类型,必须显式声明。
- 枚举值默认从0开始递增,也可手动赋值,支持位运算常量表达式(如
1 << 3)。 - 枚举值命名规范:全大写,下划线分隔。
- 支持枚举嵌套,可定义在结构体、接口内部。
- 全限定名规则:
包名@x.y::枚举名:枚举值,例如android.hardware.nfc@1.0::NfcStatus:STATUS_OK。
- 示例:
/** NFC操作状态码 */
enum NfcStatus : int32_t {
STATUS_OK = 0,
STATUS_ERR_ARG = 1,
STATUS_ERR_TIMEOUT = 2,
STATUS_ERR_UNKNOWN = -1,
};2. 结构体(struct)
- 语法:
struct 结构体名 {
类型1 字段名1;
类型2 字段名2;
...
// 支持嵌套类型定义
struct 嵌套结构体名 {
...
};
};- 核心规则:
- 字段命名采用lowerCamelCase,类型名采用UpperCamelCase。
- 支持嵌套结构体、枚举、联合体定义,嵌套类型通过
.访问,例如Foo.Bar。 - 生成的C++代码为标准布局结构体,内存布局固定,可直接用于IPC传输。
- 不支持前向声明,结构体不能直接或间接引用自身。
- 示例:
struct Point {
int32_t x;
int32_t y;
};
struct Rect {
Point topLeft;
Point bottomRight;
vec<uint8_t> extraData;
};3. 联合体(union)与安全联合体(safe_union)
- 普通union语法:
union 联合体名 {
类型1 字段名1;
类型2 字段名2;
...
};- safe_union:HIDL扩展的安全联合体,自动包含tag字段标识当前有效的成员,避免内存越界,Java仅支持safe_union,推荐优先使用。
- 核心规则:
- 联合体所有成员共享同一块内存,大小为最大成员的大小。
- 普通union不支持包含接口类型、string、vec等动态大小类型,safe_union无此限制。
- 示例:
union DataUnion {
int32_t intValue;
float floatValue;
int64_t longValue;
};
safe_union SafeDataUnion {
int32_t intValue;
float floatValue;
vec<uint8_t> buffer;
};4. 类型别名(typedef)
- 语法:
typedef 原类型 别名; - 作用:为复杂类型定义简短别名,提升代码可读性。
- 示例:
typedef vec<uint8_t> ByteBuffer;
typedef int32_t[4] FourIntArray;
typedef android.hardware.nfc@1.0::NfcStatus StatusCode;六、接口与方法定义
接口是HIDL的核心,定义了HAL提供的服务能力,每个接口对应一个独立的.hal文件,编译后生成对应的C++/Java客户端与服务端代码。
1. 接口声明
- 基础语法:
[注解]
interface 接口名 [extends 父接口全限定名] {
// 类型定义(枚举、结构体、typedef等)
// 方法定义
};- 核心规则:
- 接口名必须以
I开头,采用UpperCamelCase,必须与文件名一致。 - 支持单继承,仅能继承一个其他HIDL接口,父接口必须是全限定名导入的接口。
- 所有HIDL接口都隐式继承自
android.hidl.base@1.0::IBase,无需显式声明。 - 接口内部可定义局部类型,仅在该接口内部可见。
- 接口名必须以
- 最简示例:
package android.hardware.foo@1.0;
/** 示例服务接口 */
interface IFoo {
/** 无返回值方法 */
close();
/** 带返回值方法 */
open() generates (int32_t status);
};2. 方法定义
- 基础语法:
[注解] [oneway] 方法名(参数列表) [generates (返回值列表)];-
核心组成与规则:
- 方法名:采用lowerCamelCase,同一个接口内不支持方法重载。
- 参数列表:
类型 参数名格式,多个参数用逗号分隔,所有参数均为in方向,无out/inout参数,避免内存所有权歧义。 - oneway关键字:标记方法为异步单向调用,客户端调用后立即返回,不等待服务端执行,不能有generates返回值,服务端异常客户端无法感知。
- generates关键字:用于定义返回值,支持多个返回值,是HIDL与AIDL的核心区别之一。
-
方法示例:
/** 无返回值同步方法 */
reset();
/** 单返回值同步方法 */
getVersion() generates (string versionName);
/** 多返回值同步方法 */
readData(int32_t address, uint32_t length) generates (int32_t status, vec<uint8_t> data);
/** oneway异步方法 */
oneway reportHeartbeat(int64_t timestamp);3. 回调接口设计
HIDL通过接口类型参数实现回调,服务端可反向调用客户端实现的接口方法,实现异步事件通知。核心设计模式:
- 定义回调接口,声明服务端需要调用的回调方法。
- 主接口提供注册方法,接收回调接口实例作为参数。
- 客户端实现回调接口,传递给服务端,服务端在事件触发时调用回调方法。
- 示例:
// IDataCallback.hal
package android.hardware.foo@1.0;
interface IDataCallback {
/** 数据就绪回调 */
oneway onDataReady(vec<uint8_t> data);
};
// IFoo.hal
package android.hardware.foo@1.0;
import IDataCallback;
interface IFoo {
/** 注册数据回调 */
registerDataCallback(IDataCallback callback) generates (int32_t status);
};七、注解系统
HIDL支持Java风格的注解,用于为类型、方法、参数添加额外的语义信息,编译器和工具链会根据注解做对应的校验和代码生成。
1. 注解基础语法
- 无参数注解:
@注解名 - 单参数注解:
@注解名(值) - 多参数注解:
@注解名(键1=值1, 键2=值2, ...) - 数组参数:
@注解名(键={值1, 值2, ...}) - 位置:注解必须位于目标元素之前,单独占一行,多个注解按字母序排列。
2. 常用核心注解
| 注解名 | 适用目标 | 核心作用 |
|---|---|---|
| @entry | 方法 | 标记为接口核心入口方法,VTS测试优先覆盖 |
| @exit | 方法 | 标记为资源释放、注销类退出方法 |
| @callback | 方法 | 标记为回调方法,提示由服务端反向调用客户端实现 |
| @nullable | 接口类型参数/字段 | 标记该接口类型参数可以为null,默认不允许为null |
| @noreturn | 方法 | 标记该方法不会返回,编译器做死代码检查 |
| @deprecated | 接口/方法/类型 | 标记已废弃,编译器生成警告 |
| @async | 方法 | 标记为异步方法,结果通过回调传递 |
3. 注解使用示例
@deprecated
interface IOldFoo {
@entry
open() generates (int32_t status);
@exit
close();
registerCallback(@nullable ICallback callback);
};八、版本控制规则
HIDL的核心设计之一是版本化管理,接口发布后即冻结,所有修改必须通过版本升级实现,严格遵循语义化版本规范。
1. 版本号格式
- 格式:
主版本号.次版本号,无补丁版本号。 - 次版本号升级(Minor):必须保证前向兼容,仅可新增类型、新增接口、为已有接口新增方法,不能修改/删除已有方法、不能修改已有类型的定义。
- 主版本号升级(Major):不保证前向兼容,可修改、删除、重构接口和类型,与旧版本完全独立。
2. 核心版本规则
- 包级版本控制:版本号作用于整个包,包内所有接口和类型共享同一个版本号。
- 接口冻结:发布后的包不可修改,任何修改必须创建新版本的包。
- 继承扩展:新版本接口可继承旧版本接口,复用已有能力,新增扩展方法。
- 导入规则:次版本升级的包必须导入同主版本的上一个次版本包,保证兼容。
3. 版本升级示例
- 兼容升级(1.0→1.1):
package android.hardware.nfc@1.1;
import android.hardware.nfc@1.0::INfc;
interface INfc extends @1.0::INfc {
// 仅新增方法,不修改原有定义
getNfcVersion() generates (string version);
};- 不兼容升级(1.1→2.0):
package android.hardware.nfc@2.0;
// 完全重构,无需兼容旧版本
interface INfc {
init(NfcConfig config) generates (NfcStatus status);
deinit();
};九、代码风格与命名规范
基于AOSP官方HIDL代码风格指南,核心规范如下:
| 元素类型 | 命名规范 | 示例 |
|---|---|---|
| 包名 | 全小写,多单词用snake_case或子模块拆分 | android.hardware.nfc@1.0 |
| 接口名 | I开头,UpperCamelCase大驼峰 | INfc、IPower |
| 方法名 | lowerCamelCase小驼峰 | openDevice、readData |
| 结构体/枚举/typedef名 | UpperCamelCase大驼峰 | NfcConfig、NfcStatus |
| 枚举值 | 全大写,下划线分隔 | STATUS_OK、ERROR_TIMEOUT |
| 字段/参数名 | lowerCamelCase小驼峰 | dataBuffer、maxLength |
| 文件名 | 与接口名一致,UpperCamelCase,.hal后缀 | INfc.hal、types.hal |
- 格式规范:使用4个空格缩进,单行代码最大长度100列;左大括号与声明位于同一行,右大括号单独占一行;文档注释多行内容每行以
*开头,对齐排列。
十、高级特性
1. 直通模式(Passthrough)
仅支持C++,用于将旧版HAL快速适配到HIDL框架,无需跨进程Binder通信。编译时生成BsFoo.h头文件,实现dlopen加载的入口函数HIDL_FETCH_IFoo;客户端通过getService(name, true)获取直通模式服务,同进程内直接函数调用。
2. 快速消息队列FMQ
基于共享内存实现的无Binder IPC开销的通信机制,分为fmq_sync<T>(同步模式,多写单读)和fmq_unsync<T>(无锁模式,单写单读,性能最高),用于高频率低延迟的跨进程数据传输(如传感器、音视频数据流)。
3. 共享内存memory
基于Ashmem匿名共享内存,用于传输大体积数据,突破Binder IPC的4MB大小限制。通过hidl_memory句柄在进程间传递,对方映射到进程地址空间,实现零拷贝数据共享。
4. 嵌套类型访问
嵌套定义的类型通过.访问,例如接口内的结构体IFoo.InnerStruct,全限定名示例:android.hardware.foo@1.0::IFoo.InnerStruct。
5. 权限控制
每个HIDL方法对应一个Binder事务,支持SELinux权限控制,可通过SELinux策略限制进程的服务访问权限和方法调用权限。