📁 顶层目录
testtld/
├── aidl/ ← ① AIDL 接口定义 + 服务端实现
└── test_aidl_hal/ ← ② 客户端测试程序
① aidl/ --- 接口定义 + 服务端
这是最核心的部分,Android HAL 的 "三层结构" 都在这里:
第 1 层:AIDL 接口定义
aidl/
├── Android.bp ← 构建配置:定义 aidl_interface
└── android/hardware/testtld/
└── IHelloTest.aidl ← ★ 接口定义文件
IHelloTest.aidl 定义了服务接口:
@VintfStability
interface IHelloTest {
int getTestOne(int event, String name);
}
关键点:@VintfStability 表示这是稳定的 Vendor 接口,Android 框架会对其做版本冻结管理。
aidl/Android.bp 把这个接口注册为 android.hardware.testtld,并声明它支持 VINTF、生成 C++/NDK/Java 三种后端代码。frozen: true 表示版本 1 已冻结。
第 1.5 层:API 快照(版本控制)
aidl/aidl_api/
├── android.hardware.testtld/1/... ← 版本 1 的 frozen API 副本
└── android.hardware.testtld/current/... ← 当前开发中的最新版本
Android 的 aidl_interface 编译时自动生成,用来做接口兼容性检查 。你改了 .aidl 文件之后,编译系统会对比 current/ 和已冻结版本,防止破坏兼容性。
第 2 层:服务端实现(default/)
aidl/default/
├── Android.bp ← 构建配置:编译成 cc_binary 服务程序
├── HelloTest.h ← IHelloTest 的实现类声明
├── HelloTest.cpp ← IHelloTest 的实现类代码
├── service.cpp ← ★ 服务入口:main(),注册服务到 vndbinder
├── HelloTest-default.rc ← init.rc 脚本(开机自启)
└── HelloTest-default.xml ← VINTF manifest 片段(声明设备有这个 HAL)
各文件的作用:
|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| HelloTest.h/cpp | 继承 BnHelloTest,实现 getTestOne() 方法。当前实现就打了个 log,返回 2。 |
| service.cpp | 服务进程入口 。用 initWithDriver("/dev/vndbinder") 注册到 vendor binder,然后 AServiceManager_addService() 把服务暴露出去,最后 joinThreadPool() 等待请求。 |
| HelloTest-default.rc | Android init 脚本,告诉系统开机时启动这个服务(class hal) |
| HelloTest-default.xml | VINTF manifest,声明本设备实现了 android.hardware.testtld HAL v1,FQName 是 IHelloTest/default,没有它,系统就不知道 testtld-service 是合法的 HAL |
| Android.bp | 把 service.cpp + HelloTest.cpp 编译成 android.hardware.testtld-service,依赖 android.hardware.testtld-V1-ndk(由上层 aidl_interface 生成的 NDK 绑定库) |
为什么要有HelloTest.h/cpp还要有service.cpp:
1. service.cpp ------ 服务进程的入口
它是整个 HAL 服务的启动器和注册器,负责把一个可执行程序变成系统服务。
-
启动 Binder 线程池 :通过
ABinderProcess_setThreadPoolMaxThreadCount()和ABinderProcess_startThreadPool()初始化 Binder 通信环境。 -
创建服务实例 :使用
HelloTest类(或其他实现类)实例化一个服务对象(通常ndk::SharedRefBase::make<HelloTest>())。 -
注册到 ServiceManager :调用
AServiceManager_addService(),把实例和一个服务名(如"vendor.hello.test.IHello/default")绑定,让客户端能通过名字找到它。 -
进入循环等待 :调用
ABinderProcess_joinThreadPool()阻塞等待请求。
没有 service.cpp,服务只是一堆代码,无法独立运行,也无法被系统识别和调用。
2. HelloTest.h/cpp ------ 接口的具体实现
这是业务逻辑的核心,实现了 AIDL 接口定义的方法。
-
继承生成的 Stub 类 :AIDL 工具会根据
.aidl文件生成一个抽象类(如IHello::Stub或BnHello),HelloTest需要继承它并覆写纯虚函数。 -
编写实际功能 :比如
sayHello()方法里返回一段字符串,或者操作硬件设备。 -
与
service.cpp解耦 :service.cpp不关心具体怎么实现,只创建HelloTest对象并注册;将来如果想替换实现,只需修改HelloTest.cpp而无需动服务入口代码。
3. 为什么不能合成一个文件?
当然可以写成单文件,但分开的好处是:
-
符合单一职责原则:服务注册(进程管理)和业务实现分离,代码更清晰。
-
便于测试 :可以对
HelloTest类单独做单元测试,而不需要启动完整的服务进程。 -
方便扩展 :如果后续要支持多个接口实现(比如一个服务同时实现
IHello和IGoodbye),可以在service.cpp里注册多个实例,而实现分散在不同文件中。 -
框架推荐 :Android 官方 HAL 模板(如
hardware/interfaces下的参考实现)基本都是这种结构。
② test_aidl_hal/ --- 客户端测试
test_aidl_hal/
├── Android.bp ← 构建 cc_binary: test_aidl_hal
└── main.cpp ← 客户端代码
main.cpp 是服务的消费者,流程:
- 通过
AServiceManager_getService()查找已注册的服务 - 拿到
IHelloHal的 binder 代理 - 调用
service->hello_write("hello")
🔗 整体调用链路
┌─────────────────────────────────────────────────┐
│ 客户端程序 (test_aidl_hal) │
│ main.cpp → AServiceManager_getService() │
│ → IHelloTest::fromBinder() │
│ → getTestOne() │
└────────────────────┬────────────────────────────┘
│ vendor binder (/dev/vndbinder)
▼
┌─────────────────────────────────────────────────┐
│ 服务程序 (android.hardware.testtld-service) │
│ service.cpp → AServiceManager_addService() │
│ → ABinderProcess_joinThreadPool() │
│ HelloTest.cpp → getTestOne() 具体处理 │
└─────────────────────────────────────────────────┘
一句话总结
这是一个 Android HAL 的 Hello World,展示了:
- 用 AIDL 定义一个带
@VintfStability的硬件接口 - 用 C++ 实现服务端,注册到 vendor binder
- 写 init.rc + VINTF manifest 让系统自动拉起服务
- 写客户端通过 binder 远程调用
结构本身很标准,就是 接口定义(Android.bp + .aidl) → 服务实现(default/) → 客户端(test_aidl_hal/) 三层。客户端代码引用了不同接口名,属于还没对齐的中间状态,不影响理解整体架构。