第二十一章 传感器系统(SensorService):从硬件到应用的全链路解析
传感器系统,说白了就是Android设备感知物理世界的「神经末梢」。从手机自动旋转屏幕,到计步器记录你的步数,再到游戏中的体感操控,背后都是SensorService在默默调度。今天我就带你从HAL层一路走到应用层,把这条链路彻底打通。
核心知识点速览
- 传感器类型定义与HAL接口规范
- 传感器注册流程与数据上报机制
- 批处理(Batch)与唤醒传感器(Wake-up)
- SensorManager应用层使用全攻略
21.1 传感器类型与HAL层设计
Android支持的传感器类型,其实远比你想象的多。我记得刚接触SensorService时,看到sensors.h里那一长串枚举值,第一反应是「这得有多少种硬件?」。但实际上,很多类型是软件模拟出来的。
先看核心定义:
// hardware/libhardware/include/hardware/sensors.h
#define SENSOR_TYPE_ACCELEROMETER 1
#define SENSOR_TYPE_MAGNETIC_FIELD 2
#define SENSOR_TYPE_GYROSCOPE 4
#define SENSOR_TYPE_LIGHT 5
#define SENSOR_TYPE_PROXIMITY 8
// ... 还有几十种
HAL层的核心结构体是sensor_t和sensors_poll_device_t。每个传感器用sensor_t描述其能力:
typedef struct {
const char* name; // 传感器名称
const char* vendor; // 厂商
int version; // 版本
int handle; // 唯一标识
int type; // 类型
float maxRange; // 量程
float resolution; // 分辨率
float power; // 功耗(mA)
int32_t minDelay; // 最小采样间隔(微秒)
uint32_t fifoReservedEventCount; // FIFO保留事件数
uint32_t fifoMaxEventCount; // FIFO最大事件数
const char* stringType; // 字符串类型
const char* requiredPermission; // 所需权限
int32_t maxDelay; // 最大采样间隔
int32_t flags; // 标志位
} sensor_t;
这里有个坑:flags字段在Android 8.0之后才引入,用来标识是否是唤醒传感器。我曾经在移植老款传感器驱动时,忘了设置这个标志,结果导致设备无法进入深度休眠——传感器一直在唤醒系统。
避坑指南:我曾经在某个项目中,厂商提供的HAL实现里fifoReservedEventCount填了0,结果批处理功能完全失效。后来查文档才发现,这个值必须大于0才能启用硬件FIFO批处理。如果你遇到传感器数据无法批量上报,先检查这个字段。
21.2 传感器注册与数据上报流程
应用层调用SensorManager.registerListener()后,背后发生了什么?我画了一张图帮你理清:
注册流程的核心代码在SensorService.cpp中:
// frameworks/native/services/sensorservice/SensorService.cpp
status_t SensorService::enable(const sp<SensorEventConnection>& connection,
const sp<SensorInterface>& sensor,
nsecs_t samplingPeriodNs, nsecs_t maxBatchReportLatencyNs,
int reservedFlags) {
// 1. 检查权限
// 2. 计算实际采样率(硬件限制)
// 3. 调用HAL的activate()
// 4. 启动数据采集线程
// 5. 注册到SensorEventConnection
}
数据上报的核心机制:HAL层通过poll()函数返回传感器事件,Native层的SensorService在一个独立线程中循环调用poll()。每次拿到数据后,通过Binder回调到Java层的SensorEventConnection,最终分发到应用注册的SensorEventListener。
个人经验:我建议你在分析数据上报延迟问题时,重点关注SensorService::threadLoop()中的mSensorDevice->poll()调用。如果这里阻塞时间过长,说明HAL层处理不过来。我曾经遇到一个案例,厂商的HAL在poll()里加了日志打印,导致每次调用耗时增加2ms,最终传感器数据帧率直接腰斩。
21.3 批处理与唤醒传感器
批处理(Batch)是Android 4.4引入的重要特性。说白了,就是让传感器硬件先把数据缓存到FIFO里,攒够一批再一次性上报。这样做的好处很明显——减少CPU唤醒次数,降低功耗。
批处理的核心参数有两个:
| 参数 | 含义 | 典型值 |
|---|---|---|
| samplingPeriodNs | 采样周期(纳秒) | 5,000,000(200Hz) |
| maxBatchReportLatencyNs | 最大批处理延迟(纳秒) | 100,000,000(100ms) |
当maxBatchReportLatencyNs为0时,表示不启用批处理,数据实时上报。这个参数在SensorManager.registerListener()的最后一个参数中设置。
唤醒传感器(Wake-up Sensor)是另一个容易搞混的概念。普通传感器在系统休眠时不会上报数据,但唤醒传感器会——它会强制唤醒系统来传递数据。
关键区别:
- 普通传感器:系统休眠时,数据丢失或缓存到FIFO,系统醒来后一次性上报
- 唤醒传感器:无论系统处于什么状态,只要有数据就唤醒AP上报
判断一个传感器是否是唤醒类型,看sensor_t.flags:
// 检查是否是唤醒传感器
bool isWakeUpSensor(const sensor_t& sensor) {
return (sensor.flags & SENSOR_FLAG_WAKE_UP) != 0;
}
// 在SensorService中,唤醒传感器需要特殊处理
// 它会持有一个唤醒锁,防止系统在处理数据前再次休眠
我曾经踩过的坑:在某个平板项目中,我们需要用加速度计实现屏幕旋转。但加速度计被配置成了唤醒传感器,结果平板在桌上静止时,系统每秒钟被唤醒50次,待机功耗直接翻倍。后来改成非唤醒模式,配合批处理延迟100ms,功耗才降下来。
避坑指南:我曾经见过一个第三方应用,同时注册了唤醒传感器和普通传感器,但忘记在onPause()中注销。结果应用切到后台后,唤醒传感器依然在唤醒系统,导致手机发热严重。记住:唤醒传感器一定要在不需要时及时注销。
21.4 SensorManager使用全攻略
应用层使用传感器,核心就是SensorManager。我总结了一套最佳实践:
21.4.1 获取SensorManager
SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
注意:不要自己new SensorManager,一定要通过系统服务获取。每个应用进程只有一个SensorManager实例。
21.4.2 获取传感器列表
// 获取所有传感器
List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
// 获取特定类型传感器(通常取第一个)
Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// 获取所有加速度计(可能有多个)
List<Sensor> accelList = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
这里有个细节:getDefaultSensor()返回的是系统认为「最适合」的传感器。如果存在唤醒和非唤醒两个版本,默认返回非唤醒版本。要获取唤醒版本,需要:
Sensor wakeUpAccel = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER, true);
21.4.3 注册监听器
// 推荐使用Handler方式,避免回调在未知线程
SensorEventListener listener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
// 注意:event.values数组长度取决于传感器类型
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
// 处理数据...
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 精度变化回调,一般用于校准提示
}
};
// 注册:采样率200Hz,批处理延迟100ms
sensorManager.registerListener(listener, accelerometer,
SensorManager.SENSOR_DELAY_GAME, // 约200Hz
100_000_000, // 100ms批处理延迟
new Handler(Looper.getMainLooper()));
个人建议:我习惯在onSensorChanged()里做三件事:
- 检查
event.timestamp是否合理(防止陈旧数据) - 用
System.nanoTime()和event.timestamp做时间对齐 - 数据滤波(简单移动平均或卡尔曼滤波)
特别是时间戳对齐,很多开发者会忽略。event.timestamp是CLOCK_BOOTTIME时间,而System.nanoTime()也是CLOCK_BOOTTIME,两者可以直接比较。
21.4.4 注销监听器
// 必须在onPause()或onStop()中注销
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(listener);
}
不注销的后果:传感器会持续工作,CPU无法进入深度休眠。我见过一个极端案例,某个应用在后台持续注册加速度计,导致手机一晚上耗电30%。
21.4.5 采样率选择策略
| 常量 | 采样率 | 适用场景 |
|---|---|---|
| SENSOR_DELAY_NORMAL | 约50Hz(20ms) | 屏幕旋转、计步器 |
| SENSOR_DELAY_UI | 约67Hz(15ms) | UI交互、游戏 |
| SENSOR_DELAY_GAME | 约200Hz(5ms) | 高帧率游戏 |
| SENSOR_DELAY_FASTEST | 硬件最高频率 | 专业测量、VR |
实际采样率可能低于请求值,因为硬件有上限。你可以通过Sensor.getMinDelay()获取硬件支持的最小采样间隔。
核心总结:
- HAL层定义传感器能力,
sensor_t.flags决定是否是唤醒传感器 - 注册流程:App → SensorService → HAL,数据上报反向
- 批处理通过
maxBatchReportLatencyNs控制,0表示实时上报 - 唤醒传感器会强制唤醒系统,务必谨慎使用
- 应用层一定要在生命周期结束时注销监听器
传感器系统看似简单,但涉及功耗、性能、实时性的权衡。嗯,我建议你在实际项目中,先用dumpsys sensorservice命令查看当前传感器状态,再结合systrace分析数据流延迟。这样定位问题会快很多。
公众号:蓝海资料掘金营,微信deep3321