4. HAL Service 实现:生命周期、注册与客户端获取

好,咱们进入第四章。这一章讲的是 HAL Service 的具体实现。说白了,就是让硬件服务真正跑起来,能被别人找到、调用。

我刚开始接触 HAL 开发时,觉得 Service 不就是个后台进程嘛,有什么难的?结果第一次写就踩了坑——服务注册成功了,客户端却死活连不上。后来才发现,是生命周期回调没处理好。

咱们一步步来,先搞清楚 Service 的生命周期,再讲怎么注册到 hwservicemanager,最后看客户端怎么拿到这个 Service。

4.1 Service 生命周期:从生到死

一个 HAL Service 的生命周期,其实就四个阶段:创建、初始化、运行、销毁。听起来简单,但每个阶段都有坑。

阶段 触发时机 关键操作 常见问题
创建 进程启动 分配内存、初始化成员变量 资源泄漏
初始化 onCreate() 回调 打开硬件设备、建立连接 设备打开失败
运行 注册成功后 处理客户端请求、轮询硬件 死锁、ANR
销毁 进程退出或异常 关闭设备、释放资源 资源未释放

嗯,这里要注意:Android 的 HAL Service 通常跑在独立的进程中。进程挂了,hwservicemanager 会自动感知,然后清理注册信息。但如果你在销毁阶段没做好清理,下次启动时可能就出问题了。

我曾经遇到过:一个传感器 HAL Service,每次重启后都报设备忙。查了半天,发现是销毁阶段没调用 close() 释放硬件资源。第二次启动时,硬件还处于被占用的状态。

4.2 注册 Service 到 hwservicemanager

注册这一步,是让系统知道「我在这儿,快来用我」。hwservicemanager 就像一个服务黄页,所有 Service 都要在上面登记。

注册的核心代码其实不多,但顺序很重要。我习惯这样写:

// 1. 创建 Service 实例
sp<IYourHardware> service = new YourHardwareService();

// 2. 注册到 hwservicemanager
status_t status = defaultServiceManager()->addService(
    String16("vendor.yourcompany.hardware.yourdevice"), 
    service
);

// 3. 检查注册结果
if (status != OK) {
    ALOGE("Failed to register service: %d", status);
    return -1;
}

// 4. 进入消息循环
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();

你想想看,这里最关键的是第 4 步。很多人注册完就以为完事了,结果进程直接退出。因为主线程没有进入消息循环,addService 返回后进程就结束了。

个人习惯:我会在 addService 之前先调用 ProcessState::self()->setThreadPoolMaxThreadCount(4),给线程池预留几个工作线程。这样客户端并发请求时不会卡住。

注册的服务名有讲究。Google 推荐用「vendor.公司名.硬件类型.设备名」的格式。比如:

  • vendor.google.sensors.gyroscope
  • vendor.qualcomm.camera.back
  • vendor.yourcompany.display.primary

这样做的好处是避免命名冲突。我见过有人直接用「test_service」,结果跟系统服务重名了,调试了半天才发现。

4.3 客户端获取 Service

客户端获取 Service 的过程,其实就是去 hwservicemanager 查黄页。拿到 Service 的代理对象,然后通过 Binder 通信调用方法。

获取方式有两种:同步和异步。我一般用同步方式,代码更直观:

// 客户端获取 Service
sp<IYourHardware> service = nullptr;

// 尝试获取服务,最多等待 5 秒
for (int retry = 0; retry < 5; retry++) {
    service = interface_cast<IYourHardware>(
        defaultServiceManager()->getService(
            String16("vendor.yourcompany.hardware.yourdevice")
        )
    );
    
    if (service != nullptr) {
        break;
    }
    
    // 没拿到就等一会儿再试
    usleep(1000 * 1000); // 1秒
}

if (service == nullptr) {
    ALOGE("Failed to get service after 5 retries");
    return -1;
}

// 调用服务方法
int result = service->doSomething();

为什么要重试?因为 Service 可能还没启动完。hwservicemanager 是立即返回的,如果 Service 还没注册,getService 就返回 null。我建议至少重试 3-5 次,每次间隔 1 秒。

避坑指南:不要用 getService 的阻塞版本(参数为 true)。那个版本会一直等,如果 Service 挂了,你的客户端也会卡死。用非阻塞版本加重试,更安全。

4.4 整体流程:一张图看懂

说了这么多,咱们用一张图把整个流程串起来。从 Service 启动到客户端调用,每一步都清晰可见。

HAL Service 注册与获取完整流程 Service 进程启动 创建 Service 实例 addService 注册 进入消息循环 hwservicemanager 注册 Client 进程启动 getService 获取 获取 Service 代理 调用服务方法 查询 返回代理 关键点: 1. Service 必须先注册,Client 才能获取到 2. 注册后必须进入消息循环,否则进程退出 3. Client 获取时建议加重试机制,防止 Service 未就绪 4. 所有通信走 Binder,跨进程调用是透明的

从图上能看出来,整个流程是单向的。Service 先注册,Client 后获取。如果顺序反了,Client 就拿不到 Service。

4.5 实战中的几个坑

最后,我总结几个实战中容易踩的坑,你们写代码时多留意:

  1. 服务名拼写错误——注册和获取用的名字必须完全一致,大小写敏感。我建议把服务名定义成常量,两边引用同一个。
  2. 权限问题——Service 进程可能需要 SELinux 权限才能注册。如果注册失败,先看 logcat 里有没有 avc: denied 的提示。
  3. 多线程死锁——Service 的回调函数里不要调用 getService,否则可能死锁。因为 hwservicemanager 的锁是全局的。
  4. 进程保活——HAL Service 如果挂了,hwservicemanager 会自动清理。但客户端不会自动重连,需要自己实现重连逻辑。
我建议:在 Service 的 onFirstRef() 里做初始化,而不是构造函数。因为 onFirstRef 能保证在第一次被引用时执行,这时候 Binder 线程池已经准备好了。