4. HIDL服务端实现:服务端框架搭建、接口方法实现、服务注册与生命周期管理

好,咱们进入正题。这一章讲的是HIDL服务端的实现,说白了就是怎么让你的HAL服务真正跑起来。我见过不少开发者,接口定义写得挺漂亮,一到实现端就卡壳——要么服务注册不上,要么生命周期管理一团糟。今天咱们就把这块彻底捋清楚。

4.1 服务端框架搭建

先搭架子。一个标准的HIDL服务端,通常包含这么几个部分:

  • 服务实现类:继承自HIDL生成的接口桩代码
  • 服务注册逻辑:把实现类注册到hwservicemanager
  • 主入口函数:通常是main函数,启动服务循环

我个人习惯把服务实现和注册逻辑分开。这样代码清晰,也方便单元测试。来看一个典型的框架结构:

// IMyLight.hal 生成的接口头文件
#include <android/hardware/light/2.0/IMyLight.h>

// 服务实现类
class MyLight : public IMyLight {
public:
    MyLight();
    ~MyLight();

    // 接口方法实现
    Return<Status> setLightState(LightType type, LightState state) override;
    Return<void> getLightInfo(getLightInfo_cb _hidl_cb) override;

private:
    std::mutex mLock;
    LightState mCurrentState;
};

嗯,这里要注意一点:Return<> 模板是HIDL自动生成的,你实现的时候必须严格匹配签名。我在项目中遇到过有人把 Return<Status> 写成了 Return<int32_t>,编译能过,但运行时直接崩了——因为hwservicemanager检查类型不匹配。

4.2 接口方法实现

接口实现是服务端的核心。说白了,就是把你之前在HAL接口里声明的那些方法,一个一个填上真正的逻辑。

举个例子,假设我们要实现一个灯光控制服务:

Return<Status> MyLight::setLightState(LightType type, LightState state) {
    std::lock_guard<std::mutex> lock(mLock);

    // 参数校验
    if (type < LightType::MIN || type > LightType::MAX) {
        ALOGE("Invalid light type: %d", type);
        return Status::INVALID_ARGUMENT;
    }

    // 调用底层驱动接口
    int ret = hal_light_set(type, state.brightness, state.color);
    if (ret != 0) {
        ALOGE("Failed to set light, errno=%d", errno);
        return Status::INTERNAL_ERROR;
    }

    mCurrentState = state;
    return Status::SUCCESS;
}

你想想看,这里有几个关键点:

  • 线程安全:HIDL服务可能被多个客户端并发调用,锁是必须的
  • 参数校验:别信客户端传进来的数据,一定要做边界检查
  • 错误处理:底层驱动失败时,返回明确的错误码
我的经验: 回调接口(带_cb后缀的)实现时,记得在回调函数里检查返回值。我曾经遇到一个bug,回调里调用了另一个HIDL服务,那个服务挂了,结果整个回调线程卡死——因为没做超时处理。

4.3 服务注册

服务实现好了,怎么让系统知道它?答案是通过hwservicemanager注册。这是HIDL服务端最关键的一步。

注册流程其实不复杂:

  1. 创建服务实现实例
  2. 调用 configureRpcThreadpool 配置线程池
  3. 调用 registerAsService 注册到hwservicemanager
  4. 调用 joinRpcThreadpool 进入服务循环
int main(int argc, char** argv) {
    // 创建服务实例
    android::sp<IMyLight> service = new MyLight();

    // 配置RPC线程池,默认4个线程
    android::hardware::configureRpcThreadpool(4, true /* callerWillJoin */);

    // 注册服务,第二个参数是实例名
    android::status_t status = service->registerAsService("default");
    if (status != android::OK) {
        ALOGE("Failed to register service: %d", status);
        return 1;
    }

    ALOGI("MyLight service registered successfully");

    // 进入服务循环,等待客户端请求
    android::hardware::joinRpcThreadpool();

    // 正常情况下不会走到这里
    ALOGE("Service threadpool exited unexpectedly");
    return 1;
}
注意: configureRpcThreadpool 的第二个参数 callerWillJoin 必须为true,否则 joinRpcThreadpool 会直接返回。我见过有人把这个参数设成false,结果服务启动后马上就退出了——因为线程池没等住。

4.4 生命周期管理

服务注册上了,但怎么管理它的生命周期?这里有几个场景要考虑:

场景 处理方式
服务启动 init.rc中配置class,确保在正确阶段启动
客户端连接/断开 通过 onClientConnected/onClientDisconnected 回调感知
服务异常退出 配置 oneshot 或 respawn 策略
系统休眠/唤醒 注册 IPowerManager 回调,处理电源状态变化

我个人习惯在服务实现类里加一个 dump() 方法,方便调试时查看内部状态:

Return<void> MyLight::dump(int32_t fd, const hidl_vec<hidl_string>& args) {
    // 输出当前状态到fd
    dprintf(fd, "MyLight Service Dump:\n");
    dprintf(fd, "  Current state: brightness=%d, color=0x%x\n",
            mCurrentState.brightness, mCurrentState.color);
    dprintf(fd, "  Client count: %zu\n", mClientCount);
}

这个 dump 方法可以通过 lshal 命令调用,非常实用。

4.5 整体架构图

下面这张图展示了HIDL服务端的完整架构和调用流程:

HIDL服务端架构图 HIDL服务端架构与调用流程 客户端进程 调用 HIDL 接口 获取服务 hwservicemanager 服务注册与发现 返回代理 HIDL服务端 服务实现类 服务实现类内部 接口方法实现 线程安全(锁) 参数校验与错误处理 注册流程 configureRpcThreadpool registerAsService joinRpcThreadpool 生命周期管理 init.rc 启动配置 客户端连接回调 电源状态处理 底层硬件驱动层(Kernel / HAL) 通过 ioctl / sysfs / 内存映射 操作硬件 调用驱动接口 💡 关键说明: 1. 客户端通过 hwservicemanager 获取服务代理,不直接访问服务端 2. 服务端注册后进入 joinRpcThreadpool 循环,等待 RPC 请求

4.6 避坑指南

最后,分享几个我踩过的坑:

坑1:服务注册名冲突

同一个HAL接口可以注册多个实例名(比如"default"、"backup")。但如果你注册了同名实例,后面的会覆盖前面的。我曾经在调试时忘了关旧服务,新服务注册上去后,客户端拿到的还是旧服务的代理——折腾了半天才发现是实例名重复了。

坑2:线程池大小设置

configureRpcThreadpool 的线程数不是越大越好。我见过有人设成32,结果每个线程都在等锁,性能反而下降了。一般来说,4-8个线程就够用了。如果你的服务是纯计算型的,甚至可以设成1——用单线程避免竞态。

坑3:服务退出时的资源清理

HIDL服务退出时,不会自动释放你申请的资源。我习惯在析构函数里做清理:关闭驱动fd、释放内存、注销回调。否则服务重启时,可能会因为资源泄漏导致系统不稳定。

好了,这一章的内容就到这儿。服务端框架搭好了,接口实现了,服务注册上了,生命周期也管住了——接下来就可以安心写业务逻辑了。记住,HIDL服务端的核心就四个字:稳、准、轻、清。稳是线程安全,准是参数校验,轻是资源管理,清是代码结构。把这四点做到位,你的服务就不会出大问题。