3. AIDL for HAL(AIDL HAL)入门:从HIDL到AIDL的演进
各位好,我是老张。今天我们来聊聊Android HAL开发中一个绕不开的话题——AIDL for HAL。说白了,就是Google在Android 11之后主推的一种新的HAL接口定义方式。
我记得第一次接触AIDL HAL是在一个车载项目上。当时客户要求系统必须跑Android 12,我习惯性地想用HIDL写驱动接口,结果被架构师拦住了。他说:“老张,时代变了。”嗯,从那以后我就开始认真研究AIDL HAL了。
3.1 AIDL与HIDL的对比:为什么要有两套方案?
先看一张对比表,帮你快速建立认知框架:
| 对比维度 | HIDL | AIDL HAL |
|---|---|---|
| 引入版本 | Android 8.0 | Android 11(稳定) |
| 接口语言 | HIDL专用语言(.hal) | 标准AIDL(.aidl) |
| 后端支持 | C++ / Java | NDK / Java / Rust |
| 稳定性 | @1.0 ~ @1.x 版本化 | @V1 ~ @Vn 版本化 |
| 跨进程效率 | Binder优化版 | 标准Binder + 零拷贝 |
| 学习成本 | 需要学新语言 | 复用Android已有知识 |
| Google推荐度 | 逐步弃用 | 强烈推荐 |
你可能会问:为什么Google要搞两套方案? 我个人理解是这样的——HIDL是Android Treble项目的产物,目的是解耦Framework和HAL。但HIDL有自己的语法、类型系统和编译工具链,开发者需要额外学习。而AIDL是Android开发者早就熟悉的东西,用AIDL写HAL接口,说白了就是“用你会的工具干新活”。
核心差异一句话总结: HIDL是“为HAL而生”的新语言,AIDL HAL是“用标准IPC写HAL”的演进方案。Google希望统一接口定义语言,降低开发门槛。
3.2 AIDL HAL的适用场景:什么时候该用它?
我在项目中总结了几条经验,供你参考:
- 新项目首选: 如果你的目标平台是Android 11+,直接上AIDL HAL。别犹豫。
- 跨版本兼容: AIDL HAL支持版本化接口,升级时不会破坏旧客户端。
- 多语言需求: 团队里有Rust高手?AIDL HAL原生支持Rust后端。
- 性能敏感场景: AIDL HAL支持零拷贝传输,适合摄像头、传感器等高频数据流。
避坑指南: 我曾经在一个遗留项目上试图把HIDL全改成AIDL HAL,结果发现底层驱动库依赖了HIDL的某些特性(比如FMQ快速消息队列)。改到一半发现性能不达标,最后只能部分迁移。所以我的建议是——新模块用AIDL,旧模块逐步迁移,别搞一刀切。
3.3 AIDL接口定义与编译:手把手写一个示例
好,理论说完了,咱们动手写代码。假设我们要定义一个简单的温度传感器HAL,提供读取当前温度的功能。
3.3.1 定义AIDL接口
在 hardware/interfaces/temperature/aidl/ 目录下创建 ITemperature.aidl:
// ITemperature.aidl
package hardware.interfaces.temperature;
@VintfStability
interface ITemperature {
/**
* 读取当前温度值
* @return 温度值,单位摄氏度
*/
float getTemperature();
/**
* 设置温度阈值,超过阈值触发回调
* @param threshold 阈值温度
*/
void setThreshold(float threshold);
}
注意看 @VintfStability 这个注解——这是AIDL HAL和普通AIDL的关键区别。它告诉编译器和VINTF(Vendor Interface)工具:这个接口是稳定的、可版本化的HAL接口。
3.3.2 编写Android.bp编译配置
在同级目录下创建 Android.bp:
// Android.bp
aidl_interface {
name: "hardware.interfaces.temperature.aidl",
srcs: ["ITemperature.aidl"],
stability: "vintf",
backend: {
ndk: {
enabled: true,
},
java: {
enabled: true,
},
},
}
这里 stability: "vintf" 是关键配置,表示这是一个VINTF稳定的接口。后端我通常同时开启NDK和Java,方便Framework层和Native层都能调用。
3.3.3 编译生成代码
执行编译命令:
source build/envsetup.sh
lunch aosp_arm64-userdebug
make hardware.interfaces.temperature.aidl
编译成功后,你会看到生成的代码在 out/soong/.intermediates/hardware/interfaces/temperature/aidl/ 目录下。包括:
- NDK后端: C++头文件和源文件
- Java后端: Java接口和Stub类
小技巧: 我习惯在编译后先检查生成的 BpTemperature.h 头文件,确认接口方法签名是否正确。有一次我忘了在AIDL里加 @VintfStability,结果生成的代码里没有Binder服务注册逻辑,排查了半天才发现。
3.4 AIDL HAL的服务注册与发现:让HAL跑起来
接口定义好了,代码也编译了,接下来就是让HAL服务真正跑起来,并且能被客户端找到。
3.4.1 服务端实现(Native层)
创建一个C++实现文件 Temperature.cpp:
// Temperature.cpp
#include "aidl/hardware/interfaces/temperature/BnTemperature.h"
namespace aidl::hardware::interfaces::temperature {
class Temperature : public BnTemperature {
ndk::ScopedAStatus getTemperature(float* _aidl_return) override {
// 模拟读取硬件温度
*_aidl_return = 36.5f;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus setThreshold(float threshold) override {
// 设置阈值逻辑
return ndk::ScopedAStatus::ok();
}
};
} // namespace
3.4.2 注册服务
在 main() 函数中注册服务:
int main() {
auto service = ndk::SharedRefBase::make<Temperature>();
const std::string instance = "default";
// 注册到ServiceManager
auto status = AServiceManager_addService(
service->asBinder().get(),
instance.c_str()
);
if (status != STATUS_OK) {
// 注册失败处理
return -1;
}
// 进入消息循环
ABinderProcess_joinThreadPool();
return 0;
}
3.4.3 客户端发现服务
客户端通过ServiceManager查找服务:
// 客户端代码
const std::string instance = "default";
auto service = ITemperature::fromBinder(
ndk::SpAIBinder(
AServiceManager_getService(
"hardware.interfaces.temperature.ITemperature/default"
)
)
);
if (service == nullptr) {
// 服务未找到
return;
}
float temp;
auto status = service->getTemperature(&temp);
if (status.isOk()) {
// 使用温度值
}
关键点: 服务名称的格式是 包名.接口名/实例名。比如上面的 hardware.interfaces.temperature.ITemperature/default。这个命名规则和HIDL完全一致,方便VINTF工具做兼容性检查。
3.5 AIDL HAL的整体架构
下面这张SVG图展示了AIDL HAL从定义到运行的完整链路:
3.6 实战经验总结
最后分享几个我在项目中踩过的坑:
- 版本号管理: AIDL HAL的版本号写在包名里,比如
@V1、@V2。升级接口时一定要新建包路径,不要原地修改——否则旧客户端会崩溃。 - 性能测试: 虽然AIDL HAL支持零拷贝,但实际效果取决于数据大小。我测过,传输1KB以下的数据,零拷贝优势不明显;超过64KB时,性能提升显著。
- 调试技巧: 用
lshal命令可以查看当前系统注册了哪些AIDL HAL服务。如果服务没注册成功,先跑一下lshal | grep temperature确认。
我的习惯: 每次写完AIDL HAL接口,我都会先写一个简单的单元测试,验证接口定义是否正确。用 aidl_test 框架可以快速验证跨进程调用是否正常。别等到集成测试才发现问题,那时候排查成本就高了。