安卓HAL AIDL经验笔记

在编写 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 的返回值。

🔍 客户端获取服务的完整流程

客户端获取服务的过程,可以分为以下步骤:

  1. 创建 Binder 代理对象 (BpBinder) :客户端首先通过 ServiceManager 获取到服务端的 Binder 代理对象 (BpBinder)。这个过程看似简单,但 Binder 驱动在底层做了大量工作(如引用计数管理等)。

  2. 封装为业务代理对象 (BpHelloTest) :拿到底层的 BpBinder 后,不能直接使用。需要将其进一步封装,创建业务层的代理对象,也就是 AIDL 自动生成的 BpHelloTest

    • 这个对象是关键 :它的内部保存了 BpBinder 的引用,并且实现了 IHelloTest 接口。

    • 但它不写任何业务逻辑 :它的职责非常纯粹------将你的每一次函数调用(如 getTestOne)及其参数,打包成一个数据包(Parcel),然后调用内部 BpBindertransact() 方法,将数据包交给 Binder 驱动,最终发送给服务端。

  3. 客户端调用 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 跨进程引用计数。绝不用 newstd::make_shared
std::shared_ptr<HelloTest> 智能指针,自动管理对象生命周期 当本地和所有 binder 客户端都释放后,自动析构对象。

六、日志输出(调试必备)

输出目标 查看命令
ALOGD("tag", ...) logd main 缓冲区 logcat -s YourTag
ALOGE(...) 同上,优先级 ERROR 更容易在大量日志中凸显
printf(...) stdout,直接显示在终端 适合测试小工具,不建议用于最终 HAL 服务

推荐 :在最终 HAL 服务中用 ALOGD/ALOGE;临时测试时用 printf 加速调试。

七、必须遵守的硬规则

  1. 服务端必须使用 /dev/vndbinder ,不能是 /dev/binder

  2. 服务名必须精确匹配接口全限定名 + "/default",客户端用同一个字符串查找。

  3. 必须先启动线程池 ,再注册服务,否则 addService 可能失败。

  4. 实现类(HelloTest)的命名空间 必须与 AIDL 生成的接口完全一致(aidl::android::hardware::testtld)。

  5. 不要手动 delete 服务对象 ,交给 shared_ptr 和 binder 引用计数管理。

八、一张图汇总操作流程

复制代码
编写 .aidl 文件
    ↓
编译生成: IHelloTest, BnHelloTest, BpHelloTest, descriptor
    ↓
手写 HelloTest.cpp (继承 BnHelloTest, 实现方法)
手写 service.cpp (初始化 vndbinder, 创建实例, 注册, join)
    ↓
编译出 testtld-service 可执行文件
    ↓
在 init.rc 中配置自动启动
    ↓
客户端通过 AServiceManager_waitForService + IHelloTest::fromBinder 获取服务并调用
相关推荐
智者知已应修善业5 小时前
【51单片机控制的交通信号灯三按键切换调节时分秒加减】2023-8-26
c++·经验分享·笔记·算法·51单片机
zhangrelay5 小时前
三分钟云课实践速通--数字电子技术-数电--SimulIDE
linux·笔记·学习·ubuntu·simulide
泽克5 小时前
3.5 电梯工程安装技术
笔记
05候补工程师5 小时前
【408计网笔记】传输层与应用层高频考点:TCP/UDP特性、端口映射与交互逻辑
网络·经验分享·笔记·网络协议·tcp/ip·考研·udp
阿Y加油吧5 小时前
二刷 LeetCode:152. 乘积最大子数组 & 416. 分割等和子集 复盘笔记
笔记·算法·leetcode
码途漫谈14 小时前
Easy-Vibe开发篇阅读笔记(四)——前端开发之结合 Agent Skills 美化界面
人工智能·笔记·ai·开源·ai编程
糖炒栗子032616 小时前
【笔记】高分卫星影像 TIF 切片处理
笔记
Nice_Fold16 小时前
Kubernetes DaemonSet、StatefulSet与Service(自用笔记)
笔记·容器·kubernetes
ZhiqianXia19 小时前
《The Design of Design》阅读笔记
前端·笔记·microsoft