3. HIDL 服务端开发:从零搭建你的第一个系统服务

好,咱们进入实战环节。前面两章讲了 HIDL 是什么、为什么用它,现在该动手了。这一章的核心就一件事:创建一个 HIDL 服务端,让它跑在系统里,能被客户端调用。

我刚开始接触 HIDL 时,觉得这东西挺绕——又是 package 又是接口文件,还得注册成系统服务。但说白了,它就是个跨进程调用的壳,你只要把流程走一遍,后面就顺了。

3.1 创建 HIDL Package:命名有讲究

HIDL package 不是随便起的。它决定了你的接口在系统中的位置,也影响后续的代码生成路径。我个人习惯按这个规则来:

组成部分 示例 说明
vendor 名 vendor.nice 公司或组织标识,小写
hardware 名 sensor 硬件模块名称,小写
版本号 1.0 主版本.次版本,整数

举个例子,我曾在项目中做过一个温控传感器服务,package 就叫 vendor.nice.thermalsensor@1.0。注意版本号是 @1.0 这种写法,不是 v1.0。

小技巧: package 路径要和目录结构一致。比如 vendor.nice.thermalsensor@1.0,对应的目录就是 vendor/nice/thermalsensor/1.0。Android 编译系统会按这个路径找文件。

3.2 定义接口文件:.hal 文件怎么写

接口文件是 HIDL 的核心。它用 .hal 后缀,语法有点像 C++ 和 Java 的混合体。我建议你从最简单的开始:

// IThermalSensor.hal
package vendor.nice.thermalsensor@1.0;

interface IThermalSensor {
    /**
     * 获取当前温度值
     * @return 温度值,单位摄氏度
     */
    getTemperature() generates (float temp);

    /**
     * 设置温度阈值
     * @param threshold 阈值
     * @return 是否成功
     */
    setThreshold(float threshold) generates (bool success);
};

这里有几个关键点:

  • package 声明:必须和目录结构一致
  • interface 关键字:定义接口名称,建议以 I 开头
  • generates:返回值用这个关键字,不是 return
  • 方法名:小驼峰,语义清晰
注意: .hal 文件里不能有实现代码,只声明接口。实现是在服务端 C++ 代码里完成的。我曾经见过新手把业务逻辑写进 .hal 文件,编译直接报错。

3.3 实现服务端接口:C++ 代码怎么写

接口定义好了,接下来是实现。Android 提供了 hidl-gen 工具,能自动生成 C++ 骨架代码。你只需要填充业务逻辑就行。

我个人习惯手动创建实现类,这样更可控。来看一个完整的实现:

// ThermalSensor.cpp
#include <vendor/nice/thermalsensor/1.0/IThermalSensor.h>
#include <hidl/LegacySupport.h>
#include <thread>
#include <chrono>

using namespace vendor::nice::thermalsensor::V1_0;
using namespace android::hardware;
using namespace std::chrono_literals;

class ThermalSensor : public IThermalSensor {
public:
    ThermalSensor() : mCurrentTemp(25.0f), mThreshold(80.0f) {
        // 启动一个线程模拟温度变化
        mSimThread = std::thread([this]() {
            while (true) {
                std::this_thread::sleep_for(1s);
                // 模拟温度波动
                mCurrentTemp += (rand() % 10 - 5) * 0.1f;
                if (mCurrentTemp < 0) mCurrentTemp = 0;
                if (mCurrentTemp > 100) mCurrentTemp = 100;
            }
        });
        mSimThread.detach();
    }

    Return<float> getTemperature() override {
        return mCurrentTemp;
    }

    Return<bool> setThreshold(float threshold) override {
        if (threshold < 0 || threshold > 100) {
            return false;
        }
        mThreshold = threshold;
        return true;
    }

private:
    float mCurrentTemp;
    float mThreshold;
    std::thread mSimThread;
};

这段代码做了三件事:

  1. 继承 IThermalSensor 并实现所有方法
  2. 用线程模拟温度变化(实际项目里会读硬件寄存器)
  3. 返回值和参数类型必须和 .hal 文件一致
核心要点: 所有接口方法必须返回 Return<T> 类型,这是 HIDL 的 RPC 机制要求的。如果返回类型是 void,也要写 Return<void>。

3.4 注册为系统服务:让客户端能找到你

实现写好了,但客户端怎么找到它?答案是注册到 hwservicemanager。这是 Android 的 HIDL 服务管理器,类似 DNS 的角色。

注册代码通常写在 main() 函数里:

// main.cpp
#include <hidl/LegacySupport.h>
#include "ThermalSensor.h"

using namespace android::hardware;

int main() {
    // 创建服务实例
    auto service = new ThermalSensor();

    // 注册到 hwservicemanager
    // 第一个参数是服务名称,客户端用这个名字查找
    status_t status = service->registerAsService("default");
    
    if (status != OK) {
        ALOGE("Failed to register ThermalSensor service: %d", status);
        return 1;
    }

    ALOGI("ThermalSensor service registered successfully");
    
    // 进入事件循环,保持服务运行
    joinRpcThreadpool();
    return 0;
}

这里有个坑,我踩过:registerAsService 的字符串参数是服务实例名。同一个接口可以有多个实例,比如 "default"、"fast"、"lowpower" 等。客户端通过这个名字选择用哪个实例。

经验之谈: 如果你的服务需要开机自启,记得在 device.mk 或 init.rc 里添加启动脚本。否则每次都要手动启动,调试时还好,量产就麻烦了。

3.5 完整流程:一张图看懂

说了这么多,咱们用一张 SVG 图把整个流程串起来。从创建 package 到注册服务,每一步都清晰可见:

HIDL 服务端开发完整流程 HIDL 服务端开发流程 步骤1 创建 HIDL Package 步骤2 定义 .hal 接口文件 步骤3 实现 C++ 服务端 步骤4 注册为系统服务 步骤5 客户端通过名称调用 关键文件清单: • vendor/nice/.../1.0/ - IThermalSensor.hal - ThermalSensor.cpp - main.cpp

3.6 避坑指南:我踩过的那些坑

开发 HIDL 服务时,有几个地方特别容易出问题。我列出来,你遇到时可以少走弯路:

问题 现象 解决方案
服务注册失败 客户端找不到服务 检查 SELinux 权限,添加 hal_thermal_sensor 域
接口版本不匹配 编译报类型错误 确保 .hal 和 C++ 代码版本号一致
服务进程崩溃 客户端调用返回 -32 检查 joinRpcThreadpool() 是否调用
内存泄漏 服务运行一段时间后变慢 使用 sp<> 智能指针管理对象
曾经我犯过的错: 有一次我忘了在 main() 里调用 joinRpcThreadpool(),结果服务启动后立刻退出。客户端那边一直报 "transport error",我查了半天才发现是主线程跑完就结束了。记住,HIDL 服务需要保持进程存活。

3.7 编译与测试:验证你的服务

代码写完了,怎么编译?在 Android 源码树里,你需要写一个 Android.bp 文件:

// Android.bp
cc_binary {
    name: "vendor.nice.thermalsensor@1.0-service",
    relative_install_path: "hw",
    srcs: [
        "ThermalSensor.cpp",
        "main.cpp",
    ],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "vendor.nice.thermalsensor@1.0",
    ],
    init_rc: ["vendor.nice.thermalsensor@1.0-service.rc"],
}

编译命令:

source build/envsetup.sh
lunch your_device-userdebug
mma vendor.nice.thermalsensor@1.0-service

测试时,我习惯用 lshal 命令查看服务是否注册成功:

adb shell lshal | grep thermal
# 输出类似:
# vendor.nice.thermalsensor@1.0::IThermalSensor/default

看到这个输出,说明你的服务已经成功注册到系统里了。客户端就可以通过 IThermalSensor::getService() 来调用它。