4. HAL Service 开发实战:编写一个虚拟 Sensor HAL
这一章,我们直接动手。写一个虚拟的 Sensor HAL,把它注册成系统服务,再用客户端调一下验证。说白了,就是走通一条完整的链路——从 HAL 层到 Framework,再到 App。
我在项目里经常遇到这种情况:硬件还没回来,但上层开发不能等。这时候虚拟 HAL 就派上用场了。它能帮你提前验证接口设计、性能瓶颈,甚至用来做压力测试。
4.1 整体架构设计
先看下我们要搭的架子。整个链路分三层:
- HAL 层:虚拟 Sensor 实现,模拟加速度计数据
- Service 层:用 HIDL 或 AIDL 封装,注册为系统服务
- Client 层:一个测试 App,通过 ServiceManager 获取服务,读取数据
我习惯先把接口定死,再写实现。这样不容易跑偏。
核心原则:HAL Service 的接口设计要稳定。一旦注册到 ServiceManager,改接口的成本很高。
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 的开发流程有个直观认识。下次遇到真实硬件,直接套这个模板就行。