
在编写 AIDL HAL 服务时,开发者并不需要完全弄懂所有底层类的实现,但必须清楚以下几个核心类/函数的作用和使用方式。这里提炼一份精简的"开发者必备知识表",覆盖了你将反复打交道的 C++ 实体。
一、你直接编写/继承的类
| 类/概念 | 手写还是自动生成 | 你必须知道的事 |
|---|---|---|
HelloTest |
手写 | 你的业务实现类,必须继承 BnHelloTest,覆写 AIDL 定义的所有方法。 |
IHelloTest |
自动生成 | AIDL 接口的"抽象面孔"。客户端用它获得代理,并调用方法。你通常不需要继承它(服务端用 BnHelloTest)。 |
BnHelloTest |
自动生成 | 服务端 Stub,帮你处理 binder 解包/打包。你只需继承它,实现纯虚函数(如 getTestOne),无需关心通讯细节。 |
BpHelloTest |
自动生成(隐藏) | 客户端代理,IHelloTest::fromBinder 实际返回它的实例。开发者不直接使用,但调用 service->method() 时,就是它在 binder 发请求。 |
编写服务端时,你只需要写这样的类:
cpp
class HelloTest : public BnHelloTest {
public:
ndk::ScopedAStatus getTestOne(int32_t in, const std::string& name, int32_t* _return) override;
};
然后实现方法,并在 _return 指针里填入业务结果。
二、服务注册与发现:你必须调用的 NDK 函数
| 函数/类 | 作用 | 用法要点 |
|---|---|---|
AServiceManager_addService |
将服务对象注册到 vndservicemanager |
参数:(AIBinder*, 服务名)。服务名格式:"接口全限定名/default",例如 "android.hardware.testtld.IHelloTest/default" |
AServiceManager_waitForService |
阻塞等待并获取服务 binder | 返回 AIBinder*,需要包装为 ndk::SpAIBinder。超时返回 nullptr。 |
IHelloTest::fromBinder |
把原始 binder 转为类型化接口代理 | 返回 std::shared_ptr<IHelloTest>,失败返回 nullptr。 |
HelloTest::descriptor |
静态字符串,值为接口全限定名 | 用于构造服务名:descriptor + "/default" |
服务端注册示例 (在 service.cpp 中):
cpp
auto service = ndk::SharedRefBase::make<HelloTest>();
std::string name = HelloTest::descriptor + "/default"s;
AServiceManager_addService(service->asBinder().get(), name.c_str());
客户端获取示例:
cpp
auto binder = ndk::SpAIBinder(AServiceManager_waitForService("android.hardware.testtld.IHelloTest/default"));
auto service = IHelloTest::fromBinder(binder);
if (service) service->getTestOne(...);
三、Binder 基础设施:进程与线程管理
| 函数/类 | 作用 | 注意事项 |
|---|---|---|
android::ProcessState::initWithDriver("/dev/vndbinder") |
绑定 vendor binder 域 | 服务端必须调用 ,且必须用 /dev/vndbinder(vendor HAL 专用)。客户端通常不需要手动调用(由 shell 或 init 提供)。 |
ABinderProcess_setThreadPoolMaxThreadCount(0) |
设置最大线程数,0 为自动 | 在 startThreadPool 之前调用。 |
ABinderProcess_startThreadPool() |
启动 binder 线程池 | 允许处理并发 binder 请求。 |
ABinderProcess_joinThreadPool() |
将当前线程加入线程池,阻塞等待请求 | 服务端最后调用,进入服务循环。 |
ndk::SpAIBinder |
智能指针,管理 AIBinder* |
自动增减 binder 引用计数,避免泄漏。通常用于包装 waitForService 的返回值。 |
🔍 客户端获取服务的完整流程
客户端获取服务的过程,可以分为以下步骤:
-
创建 Binder 代理对象 (
BpBinder) :客户端首先通过ServiceManager获取到服务端的 Binder 代理对象 (BpBinder)。这个过程看似简单,但 Binder 驱动在底层做了大量工作(如引用计数管理等)。 -
封装为业务代理对象 (
BpHelloTest) :拿到底层的BpBinder后,不能直接使用。需要将其进一步封装,创建业务层的代理对象,也就是 AIDL 自动生成的BpHelloTest。-
这个对象是关键 :它的内部保存了
BpBinder的引用,并且实现了IHelloTest接口。 -
但它不写任何业务逻辑 :它的职责非常纯粹------将你的每一次函数调用(如
getTestOne)及其参数,打包成一个数据包(Parcel),然后调用内部BpBinder的transact()方法,将数据包交给 Binder 驱动,最终发送给服务端。
-
-
客户端调用
fromBinder方法 :你需要手动调用的IHelloTest::fromBinder(binder)方法,正是完成第二步转换的关键API 。它的内部会精确地执行这个封装过程,返回给你一个可以像本地调用一样使用的HelloTest代理对象。
cpp
// 伪代码,展示IHelloTest::fromBinder的内部逻辑
static std::shared_ptr<IHelloTest> fromBinder(const ndk::SpAIBinder& binder) {
// 1. 检查传入的binder是否为空
if (!binder) return nullptr;
// 2. 验证binder的服务端接口描述符是否匹配
// ...验证逻辑...
// 3. 创建并返回业务代理对象
return std::make_shared<BpHelloTest>(binder);
}
📦 客户端与服务的完整通信图
客户端进程 内核空间 服务进程
┌─────────────────┐ ┌─────────┐ ┌──────────────────────┐
│ test_aidl_hal │ │ │ │ android.hardware. │
│ │ │ │ │ testtld-service │
│ getTestOne(1, │ -- 打包数据--> │ Binder │ -- 解包--> │ │
│ "hello", │ │ 驱动 │ │ HelloTest:: │
│ &result) │ <-- 打包结果--│ │ <-- 打包-- │ getTestOne(...) │
│ │ │ │ │ │
│ result = 2 │ │ │ │ return ok; │
└─────────────────┘ └─────────┘ └──────────────────────┘
✅ 客户端开发自查清单
-
🧩 AIDL 定义一致:确保客户端链接的 AIDL 库版本与服务端严格一致,接口签名不匹配是常见的运行时错误。
-
🧵 线程池与同步/异步:明确你的客户端是同步调用还是异步调用。如果涉及回调,需要确保客户端进程也配置了 binder 线程池来处理服务端的反向调用。
-
🏷️ 服务名称 :
AServiceManager_waitForService中使用的服务名称(例如"android.hardware.testtld.IHelloTest/default")必须与服务端注册时使用的名称完全一致。 -
🪝 死亡通知 (Death Recipient):对于需要长期保持连接的服务,建议注册死亡通知监听器。这能让客户端在服务异常退出时进行优雅处理,避免调用失败时无响应。
四、方法签名与返回值:你每次都要写的
| 项目 | 说明 |
|---|---|
ndk::ScopedAStatus |
每个 AIDL 方法必须返回 该类型,表示通信状态。调用成功返回 ndk::ScopedAStatus::ok()。 |
输出参数 _aidl_return |
AIDL 方法的实际返回值通过指针传递。你在服务端需要给它赋值,如 *_aidl_return = 2;。 |
| 输入参数 | 按照 AIDL 定义的类型使用,如 int32_t in_event, const std::string& in_name。它们由 binder 从客户端序列化而来。 |
五、对象创建与生命周期
| 函数/类 | 用途 | 为什么用它 |
|---|---|---|
ndk::SharedRefBase::make<T>(args...) |
创建 HAL 服务对象 | 返回 std::shared_ptr<T>,且对象支持 binder 跨进程引用计数。绝不用 new 或 std::make_shared。 |
std::shared_ptr<HelloTest> |
智能指针,自动管理对象生命周期 | 当本地和所有 binder 客户端都释放后,自动析构对象。 |
六、日志输出(调试必备)
| 宏 | 输出目标 | 查看命令 |
|---|---|---|
ALOGD("tag", ...) |
logd main 缓冲区 | logcat -s YourTag |
ALOGE(...) |
同上,优先级 ERROR | 更容易在大量日志中凸显 |
printf(...) |
stdout,直接显示在终端 | 适合测试小工具,不建议用于最终 HAL 服务 |
推荐 :在最终 HAL 服务中用 ALOGD/ALOGE;临时测试时用 printf 加速调试。
七、必须遵守的硬规则
-
服务端必须使用
/dev/vndbinder,不能是/dev/binder。 -
服务名必须精确匹配 :
接口全限定名 + "/default",客户端用同一个字符串查找。 -
必须先启动线程池 ,再注册服务,否则
addService可能失败。 -
实现类(HelloTest)的命名空间 必须与 AIDL 生成的接口完全一致(
aidl::android::hardware::testtld)。 -
不要手动 delete 服务对象 ,交给
shared_ptr和 binder 引用计数管理。
八、一张图汇总操作流程
编写 .aidl 文件
↓
编译生成: IHelloTest, BnHelloTest, BpHelloTest, descriptor
↓
手写 HelloTest.cpp (继承 BnHelloTest, 实现方法)
手写 service.cpp (初始化 vndbinder, 创建实例, 注册, join)
↓
编译出 testtld-service 可执行文件
↓
在 init.rc 中配置自动启动
↓
客户端通过 AServiceManager_waitForService + IHelloTest::fromBinder 获取服务并调用