4. HAL Service 开发实战:编写一个虚拟 Sensor HAL

这一章,我们直接动手。写一个虚拟的 Sensor HAL,把它注册成系统服务,再用客户端调一下验证。说白了,就是走通一条完整的链路——从 HAL 层到 Framework,再到 App。

我在项目里经常遇到这种情况:硬件还没回来,但上层开发不能等。这时候虚拟 HAL 就派上用场了。它能帮你提前验证接口设计、性能瓶颈,甚至用来做压力测试。

4.1 整体架构设计

先看下我们要搭的架子。整个链路分三层:

  • HAL 层:虚拟 Sensor 实现,模拟加速度计数据
  • Service 层:用 HIDL 或 AIDL 封装,注册为系统服务
  • Client 层:一个测试 App,通过 ServiceManager 获取服务,读取数据

我习惯先把接口定死,再写实现。这样不容易跑偏。

核心原则:HAL Service 的接口设计要稳定。一旦注册到 ServiceManager,改接口的成本很高。

虚拟 Sensor HAL 架构图 Client App (测试验证) Sensor HAL Service (AIDL/HIDL) Virtual Sensor HAL (模拟数据) 数据流:Client → ServiceManager → HAL Service → Virtual HAL

4.2 定义 AIDL 接口

我选 AIDL,因为它在 Android 11 之后是主流。HIDL 虽然还能用,但新项目基本都切到 AIDL 了。

先定义接口文件 IVirtualSensor.aidl:

// IVirtualSensor.aidl
package com.example.virtualsensor;

interface IVirtualSensor {
    /**
     * 打开传感器
     * @return 0 成功,其他失败
     */
    int open();

    /**
     * 关闭传感器
     */
    void close();

    /**
     * 读取加速度计数据
     * @return float[3] 分别对应 x, y, z 轴
     */
    float[] readAccelerometer();

    /**
     * 获取传感器状态
     * @return 状态码
     */
    int getStatus();
}

小技巧:接口方法不要太多。我一般控制在 5 个以内,方便后续扩展。多了容易耦合。

4.3 实现虚拟 Sensor HAL

底层 HAL 用 C++ 写。模拟加速度计数据,每隔 10ms 生成一组正弦波数据。这样看起来像真实传感器,又不依赖硬件。

// VirtualSensor.cpp
#include <cmath>
#include <thread>
#include <mutex>

class VirtualSensor {
private:
    bool mEnabled;
    std::mutex mMutex;
    float mData[3];
    std::thread mWorker;
    int mStatus;

    void dataGenerator() {
        float t = 0.0f;
        while (mEnabled) {
            {
                std::lock_guard<std::mutex> lock(mMutex);
                // 模拟正弦波加速度数据
                mData[0] = 9.8f * sin(t);       // X 轴
                mData[1] = 9.8f * sin(t + 1.0f); // Y 轴
                mData[2] = 9.8f * sin(t + 2.0f); // Z 轴
                t += 0.1f;
            }
            std::this_thread::sleep_for(
                std::chrono::milliseconds(10));
        }
    }

public:
    VirtualSensor() : mEnabled(false), mStatus(0) {
        mData[0] = mData[1] = mData[2] = 0.0f;
    }

    int open() {
        std::lock_guard<std::mutex> lock(mMutex);
        if (mEnabled) return -1; // 已经打开
        mEnabled = true;
        mStatus = 1;
        mWorker = std::thread(
            &VirtualSensor::dataGenerator, this);
        return 0;
    }

    void close() {
        std::lock_guard<std::mutex> lock(mMutex);
        if (mEnabled) {
            mEnabled = false;
            mStatus = 0;
            if (mWorker.joinable()) {
                mWorker.join();
            }
        }
    }

    float* read() {
        std::lock_guard<std::mutex> lock(mMutex);
        return mData;
    }

    int getStatus() { return mStatus; }
};

注意:数据生成线程一定要处理好生命周期。我曾经遇到过 Service 退出时线程没回收,导致内存泄漏。用 RAII 或者智能指针管理。

4.4 实现 AIDL Service 端

Service 端负责把 HAL 层包装成 AIDL 接口,然后注册到 ServiceManager。

// VirtualSensorService.cpp
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include "com/example/virtualsensor/IVirtualSensor.h"

using namespace android;
using namespace com::example::virtualsensor;

class VirtualSensorService : public IVirtualSensor {
private:
    VirtualSensor mSensor;

public:
    ndk::ScopedAStatus open() override {
        int ret = mSensor.open();
        return ndk::ScopedAStatus::fromExceptionCode(ret);
    }

    ndk::ScopedAStatus close() override {
        mSensor.close();
        return ndk::ScopedAStatus::ok();
    }

    ndk::ScopedAStatus readAccelerometer(
            std::vector<float>* _aidl_return) override {
        float* data = mSensor.read();
        _aidl_return->clear();
        _aidl_return->push_back(data[0]);
        _aidl_return->push_back(data[1]);
        _aidl_return->push_back(data[2]);
        return ndk::ScopedAStatus::ok();
    }

    ndk::ScopedAStatus getStatus(int32_t* _aidl_return) override {
        *_aidl_return = mSensor.getStatus();
        return ndk::ScopedAStatus::ok();
    }
};

int main() {
    // 设置 binder 线程池
    ABinderProcess_setThreadPoolMaxThreadCount(4);

    // 创建服务实例
    auto service = ndk::SharedRefBase::make<VirtualSensorService>();

    // 获取服务名称
    const std::string instance = std::string() + 
        IVirtualSensor::descriptor + "/default";

    // 注册到 ServiceManager
    binder_status_t status = AServiceManager_addService(
        service->asBinder().get(), instance.c_str());

    if (status != STATUS_OK) {
        LOG(ERROR) << "Failed to register service";
        return -1;
    }

    LOG(INFO) << "VirtualSensorService registered successfully";

    // 进入消息循环
    ABinderProcess_joinThreadPool();
    return 0;
}

这里有个关键点:ABinderProcess_setThreadPoolMaxThreadCount 设置线程池大小。我一般设 4 个,够用。太多反而浪费。

4.5 客户端调用验证

客户端用 Java 写,方便测试。通过 ServiceManager 获取服务,然后调用接口。

// TestClient.java
package com.example.testclient;

import android.os.RemoteException;
import com.example.virtualsensor.IVirtualSensor;

public class TestClient {
    private static final String TAG = "TestClient";
    private IVirtualSensor mService;

    public boolean connect() {
        // 通过 ServiceManager 获取服务
        mService = IVirtualSensor.Stub.asInterface(
            android.os.ServiceManager
                .getService("com.example.virtualsensor.IVirtualSensor/default"));
        
        if (mService == null) {
            Log.e(TAG, "Failed to get service");
            return false;
        }
        return true;
    }

    public void testRead() {
        if (mService == null) return;

        try {
            // 打开传感器
            int ret = mService.open();
            Log.d(TAG, "open result: " + ret);

            // 读取数据
            float[] data = mService.readAccelerometer();
            Log.d(TAG, "Accel: x=" + data[0] 
                + ", y=" + data[1] 
                + ", z=" + data[2]);

            // 获取状态
            int status = mService.getStatus();
            Log.d(TAG, "Status: " + status);

            // 关闭
            mService.close();

        } catch (RemoteException e) {
            Log.e(TAG, "Remote exception: " + e.getMessage());
        }
    }
}

验证要点:

  • 检查 ServiceManager 是否能找到服务
  • 确认数据读取正常(正弦波数据应该有规律变化)
  • 测试异常场景:重复 open、未 open 就 read

4.6 性能调优要点

虚拟 HAL 虽然简单,但性能问题一样存在。我整理了几个常见坑:

问题 原因 解决方案
数据延迟高 线程切换开销 使用双缓冲 + 原子变量
CPU 占用高 轮询频率过高 根据实际需求调整采样率
Binder 调用慢 大数据传输 使用共享内存或批量传输
内存泄漏 线程未正确退出 RAII 管理线程生命周期

我的经验:虚拟 HAL 的采样率不要设太高。10ms 间隔已经能模拟大部分场景。设到 1ms 反而会因为线程调度抖动,数据看起来不真实。

4.7 避坑指南

做这个实战,有几个坑我踩过,分享给你:

  • Service 注册失败:检查 SELinux 策略。我曾经在 userdebug 版本上跑得好好的,切到 user 版本就跪了。原因是缺少对应的 te 文件。
  • 客户端拿不到服务:确认 ServiceManager 的 instance name 完全一致。多一个空格都不行。
  • 数据全是 0:检查线程是否真的在跑。我遇到过线程被异常终止,但标志位没重置的情况。
  • Binder 死锁:不要在 Service 方法里做耗时操作。如果必须做,另起线程。

特别提醒:虚拟 HAL 的数据不能直接用于生产。它只是帮你验证流程。真实传感器有噪声、温漂、校准等问题,虚拟环境模拟不了。

嗯,这一章的内容就到这。代码量不大,但链路完整。你跟着敲一遍,应该能对 HAL Service 的开发流程有个直观认识。下次遇到真实硬件,直接套这个模板就行。