3. HAL 接口定义语言 (HIDL) 基础
好,咱们今天聊聊 HIDL。说实话,我刚接触 Android 的时候,觉得这玩意儿就是个「中间人」,没啥了不起的。直到我在一个项目里,因为接口定义不规范,导致 Camera HAL 和 Framework 之间来回扯皮,调试了整整三天……嗯,从那以后,我再也不敢小看 HIDL 了。
HIDL,全称是 HAL Interface Definition Language。说白了,它就是用来定义 HAL 层接口的一种语言。你想想看,Framework 是 Java 写的,HAL 是 C++ 写的,两个世界怎么沟通?HIDL 就是那座桥。
3.1 HIDL 语法速览
HIDL 的语法其实挺简单的。我个人习惯把它看作是「带类型约束的 C++ 头文件」。它支持基本类型、结构体、枚举、接口等。
先看一个最简单的例子:
package android.hardware.camera@3.4;
interface ICameraDevice {
/**
* 打开相机设备
* @return status 操作状态
*/
open() generates (Status status);
/**
* 关闭相机设备
*/
close();
/**
* 配置数据流
* @param streams 数据流配置列表
* @return status 操作状态
*/
configureStreams(vec<Stream> streams) generates (Status status);
};
这里有几个关键点:
- package:包名,后面跟的是版本号。版本号很重要,我见过有人因为版本号写错,编译死活过不去。
- interface:接口定义。每个接口就是一个服务。
- generates:表示这个方法有返回值。注意,HIDL 的方法返回值是异步的,通过 callback 返回。
- vec<T>:向量类型,相当于 C++ 的 vector。
重要提示:HIDL 的版本号是递增的,不能回退。比如你发布了 3.4 版本,就不能再发布 3.3 版本。这是 Google 的硬性规定,为了保持向前兼容。
3.2 .hal 文件编写实战
写 .hal 文件,其实就是在定义「契约」。Framework 和 HAL 都要遵守这个契约。我在项目中遇到过最坑的事情,就是 .hal 文件里定义了一个方法,但 HAL 端没实现,结果 Framework 调用时直接 crash。
来看一个更完整的例子,定义一个 Camera 设备相关的接口:
package android.hardware.camera@3.4;
import android.hardware.camera.common@1.0::types;
interface ICameraDevice {
/**
* 初始化相机设备
*/
init() generates (Status status);
/**
* 获取相机特性
* @return characteristics 相机特性数据
*/
getCameraCharacteristics() generates (CameraCharacteristics characteristics);
/**
* 设置输出流
* @param outputStreams 输出流列表
* @param configMode 配置模式
*/
configureStreams(vec<Stream> outputStreams,
StreamConfigurationMode configMode)
generates (Status status,
vec<HalStream> halStreams);
/**
* 处理请求
* @param request 捕获请求
* @return sequenceId 序列号
*/
processCaptureRequest(CaptureRequest request)
generates (Status status,
int32_t sequenceId);
/**
* 刷新所有待处理请求
*/
flush() generates (Status status);
};
写 .hal 文件时,有几个坑要注意:
- 类型要明确:HIDL 是强类型的,int 就是 int32_t,别偷懒写 int。
- 注释要写清楚:每个方法、每个参数都要有注释。我见过一个项目,.hal 文件里全是英文注释,结果印度同事看不懂,又重写了一遍。
- 版本号要一致:package 里的版本号,要和 Android.mk 或 Android.bp 里的版本号一致。
我的小技巧:写 .hal 文件时,先画个接口图,把方法、参数、返回值都列出来。这样不容易漏掉东西。我曾经因为少定义了一个回调方法,导致整个 Camera HAL 无法正常工作,排查了整整两天。
3.3 服务端与客户端通信模型
HIDL 的通信模型,说白了就是「服务端提供接口,客户端调用接口」。但这里有个关键点:HIDL 是跨进程的。Framework 和 HAL 运行在不同的进程里,它们通过 Binder 通信。
通信流程大概是这样的:
- 服务端注册:HAL 服务启动后,会向 ServiceManager 注册自己。
- 客户端获取:Framework 通过 ServiceManager 获取 HAL 服务的代理。
- 方法调用:Framework 调用代理对象的方法,参数通过 Binder 传递到 HAL 端。
- 结果返回:HAL 端处理完后,通过 callback 或 generates 返回结果。
来看一个服务端的实现示例:
// CameraDevice.cpp
class CameraDevice : public ICameraDevice {
public:
Return<Status> init() override {
// 初始化硬件
// 我习惯在这里加个日志,方便调试
ALOGD("CameraDevice::init()");
return Status::OK;
}
Return<void> close() override {
// 关闭硬件
ALOGD("CameraDevice::close()");
}
Return<Status, vec<HalStream>> configureStreams(
const vec<Stream>& outputStreams,
StreamConfigurationMode configMode) override {
// 配置数据流
// 这里要注意,outputStreams 可能为空
if (outputStreams.size() == 0) {
ALOGE("configureStreams: no output streams");
return {Status::ILLEGAL_ARGUMENT, {}};
}
// 实际配置逻辑...
vec<HalStream> halStreams;
// ...
return {Status::OK, halStreams};
}
};
客户端的调用方式:
// CameraService.cpp
sp<ICameraDevice> cameraDevice =
ICameraDevice::getService("default");
// 调用方法
Status status = cameraDevice->init();
if (status != Status::OK) {
ALOGE("Failed to init camera device");
return;
}
// 配置数据流
vec<Stream> streams;
// ... 填充 streams
auto result = cameraDevice->configureStreams(streams,
StreamConfigurationMode::NORMAL);
if (result.isOk()) {
Status s = result[0];
vec<HalStream> halStreams = result[1];
// 处理结果
}
注意:HIDL 的调用是同步的,但底层是通过 Binder 异步实现的。如果你在 HAL 端做耗时操作,比如 100ms 以上的操作,一定要用异步回调的方式,否则会阻塞 Framework 的主线程。我曾经因为一个 200ms 的初始化操作,导致整个系统 ANR,被测试小姐姐追着骂了一周。
3.4 避坑指南
做 HIDL 开发这几年,我踩过的坑不少,总结几个最常见的:
- 版本号冲突:同一个包名不能有两个版本同时存在。如果你要升级版本,必须保证所有依赖都同步升级。
- 接口不兼容:修改 .hal 文件后,一定要重新生成代码。我见过有人手动修改生成的代码,结果编译报错,排查了半天才发现是手改的。
- 内存泄漏:HIDL 的跨进程调用,对象是序列化传输的。如果你在 HAL 端 new 了一个对象,记得在 Framework 端 delete。否则,内存泄漏是迟早的事。
- 死锁问题:HIDL 的回调是在 Binder 线程中执行的。如果你在回调里又调用了 HAL 的方法,可能会造成死锁。我建议回调里只做简单的状态更新,不要做复杂操作。
核心要点:HIDL 的本质是「契约」。写好 .hal 文件,就是定好契约。服务端和客户端都要严格遵守这个契约。任何一方的违约,都会导致系统崩溃。所以,写 .hal 文件时,一定要想清楚每个方法、每个参数、每个返回值。
好了,HIDL 的基础就聊这么多。下一章,咱们会深入 HIDL 的高级特性,比如回调、死锁检测、性能优化等。到时候我会分享更多实战中的坑和技巧。