3. HIDL 接口定义语言:语法、数据类型与规范
HIDL 这玩意儿,说白了就是 Android 系统里用来定义硬件接口的“契约语言”。你写好了 .hal 文件,客户端和服务端就得按这个契约来沟通。我刚开始接触 HIDL 的时候,觉得它跟 AIDL 长得挺像,但踩过几次坑之后才发现——细节上差别大了去了。
3.1 HIDL 语法基础
一个 .hal 文件,结构其实很清晰。我习惯把它拆成三块看:包声明、导入语句、接口定义。
// 包声明:反向域名,必须跟文件路径一致
package vendor.example.hardware.foo@1.0;
// 导入依赖的类型
import android.hardware.graphics.common@1.0::PixelFormat;
// 接口定义
interface IFoo {
// 方法声明
doSomething(uint32_t input) generates (Result result);
};
嗯,这里要注意:包路径必须跟文件系统路径完全对应。比如上面这个文件,就得放在 vendor/example/hardware/foo/1.0/IFoo.hal。我曾经见过有同事把版本号写错了,结果编译死活过不去,查了半天才发现是路径跟声明不匹配。
3.2 数据类型详解
HIDL 的数据类型,我把它分成四类。你想想看,搞清楚了这四类,写接口基本不会卡壳。
| 类别 | 类型 | 说明 |
|---|---|---|
| 基础类型 | uint8_t, int32_t, float, bool 等 |
跟 C++ 基本一致,长度固定 |
| 容器类型 | vec<T>, string |
动态数组和字符串 |
| 复合类型 | struct, enum, union |
自定义数据结构 |
| 句柄类型 | handle, memory, fmq_sync |
用于传递文件描述符、共享内存 |
这里我想重点说一下 vec<T>。它跟 C++ 的 std::vector 很像,但有个坑:HIDL 的 vec 不支持嵌套太深。我记得有一次,我在接口里定义了一个 vec<vec<vec<int32_t>>>,结果编译直接报错。后来查文档才知道,HIDL 限制嵌套最多三层。
vec<uint8_t>,结果性能惨不忍睹。后来换成 memory 类型走共享内存,速度直接提升了一个数量级。记住:大数据走 memory,小数据走 vec。
3.3 包结构与版本管理
HIDL 的包结构,我建议你把它想象成一个“文件夹+版本号”的组合。每个包都是一个独立的目录,版本号用 @major.minor 表示。
vendor/example/hardware/foo/
├── 1.0/
│ ├── IFoo.hal
│ ├── types.hal
│ └── Android.bp
├── 2.0/
│ ├── IFoo.hal
│ ├── types.hal
│ └── Android.bp
版本号怎么定?我个人的经验是:
- major 版本:接口不兼容时升级。比如删了方法、改了参数类型。
- minor 版本:只增加不删除。比如加了个新方法,旧方法保持不变。
为什么要这么严格?因为 Android 的 Treble 架构要求 HAL 实现跟系统框架解耦。你改了一个接口,所有依赖它的模块都得跟着改。所以,能不升版本就别升,能加方法就别删方法。
3.4 接口与方法定义规范
接口定义,说白了就是告诉别人“我能干什么”。每个方法都要明确输入、输出,以及是否会产生阻塞。
interface ICamera {
// 同步方法:调用方会阻塞等待结果
openCamera(uint32_t cameraId) generates (Status status);
// 单向方法:调用方不等待返回
oneway notifyCallback(CallbackData data);
// 回调接口:服务端主动通知客户端
interface ICallback {
onFrameAvailable(uint32_t frameId);
};
};
这里有几个规范,我建议你记住:
- 方法名用驼峰命名:首字母小写,比如
openCamera,不要用下划线。 - 参数名要有意义:别用
a、b、tmp这种,别人看不懂。 - 返回值用
generates关键字:别用return,那是 C++ 的写法。 - 单向方法用
oneway修饰:适用于日志、通知等不需要确认的场景。
3.5 HIDL 知识体系结构图
下面这张图,是我整理 HIDL 知识体系时画的。你可以把它当成一张“地图”,学完这章后对照着检查自己掌握了多少。
3.6 实战中的避坑指南
最后,我分享几个实战中遇到的坑,希望能帮你少走弯路。
- 别用
string传大文本:HIDL 的string本质上是vec<uint8_t>的封装,传大文本性能很差。我遇到过传 1MB 的 JSON 字符串,直接导致 ANR。后来改成memory走共享内存,问题解决。 - 注意线程安全:HIDL 服务端的方法默认是在 Binder 线程池里执行的。如果你的实现里有共享数据,记得加锁。我曾经因为没加锁,导致两个客户端同时调用时数据错乱,查了整整两天。
- 别滥用
oneway:单向方法虽然不阻塞,但也不保证送达。如果你需要确认对方收到了,就别用oneway。我见过有人把所有方法都写成oneway,结果丢消息了都不知道。