16、采样率设置:通过 registerListener 的第三个参数设置采样频率
好,咱们接着聊。上一章我们把传感器监听器的注册讲完了,但有个关键参数我一直没细说——就是 registerListener 的第三个参数,采样频率。
说实话,这个参数我刚开始接触时也踩过坑。你以为设得越高越好?不一定。设得太低?数据又不够用。今天我就把这块掰开揉碎讲清楚。
registerListener 的完整签名
先看一眼完整的方法签名,心里有个底:
SensorManager.registerListener(
SensorEventListener listener,
Sensor sensor,
int samplingPeriodUs,
Handler handler
)
第三个参数 samplingPeriodUs,单位是微秒(μs)。它决定了传感器多久上报一次数据。说白了,就是采样间隔。
核心公式:
采样频率(Hz)= 1,000,000 / samplingPeriodUs(微秒)
举个例子:设 10,000μs,就是每秒 100 次采样。
系统预定义的四种采样模式
Android 贴心地给了四个常量,你不用自己算微秒数。我个人习惯先用这些常量,不够再调。
| 常量 | 对应值 | 采样频率 | 适用场景 |
|---|---|---|---|
SENSOR_DELAY_NORMAL |
200,000μs | 5 Hz | 屏幕方向、简单计步 |
SENSOR_DELAY_UI |
60,000μs | 约 16 Hz | UI 动画、游戏界面 |
SENSOR_DELAY_GAME |
20,000μs | 50 Hz | 普通游戏、体感控制 |
SENSOR_DELAY_FASTEST |
0μs | 硬件最高 | VR、高精度运动追踪 |
嗯,这里要注意——SENSOR_DELAY_FASTEST 并不是真正的 0,而是「尽可能快」。具体多快,取决于硬件能力。我在项目里见过有的手机能跑到 1000Hz,有的只能到 200Hz。
实际代码怎么写?
直接上代码,你一看就明白:
SensorManager sensorManager =
(SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor gyroscope =
sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
// 用 GAME 模式,50Hz 采样
sensorManager.registerListener(
this,
gyroscope,
SensorManager.SENSOR_DELAY_GAME
);
如果你想自定义频率,比如想要 100Hz:
// 100Hz = 10,000μs
int customRate = 10000;
sensorManager.registerListener(
this,
gyroscope,
customRate
);
我的小技巧:
如果你不确定设多少合适,先从 SENSOR_DELAY_GAME 开始。50Hz 对大多数体感应用都够用。跑起来发现数据太卡,再往下调。我曾经有个项目,一开始直接上 FASTEST,结果手机发烫,电池哗哗掉——得不偿失。
采样率不是越高越好
你想想看,采样率高了,数据量就大。数据量大,CPU 就得频繁处理。CPU 忙了,功耗就上去了。这是个三角关系:
为什么会这样?因为传感器数据是硬件中断驱动的。你设了 10,000μs,硬件就每 10 毫秒产生一次中断。中断来了,CPU 就得从手头的工作切过来处理。切来切去,性能就损耗了。
我曾经踩过的坑:
有个 AR 项目,我图省事直接用了 SENSOR_DELAY_FASTEST。结果在低端机上,陀螺仪回调把 UI 线程堵死了,界面直接卡成 PPT。后来改成 SENSOR_DELAY_GAME,再配合后台线程处理数据,问题才解决。
所以我的建议是:先保守,再激进。从低频率开始,测试发现不够再往上加。
不同场景的推荐配置
根据我这些年做项目的经验,给你一个速查表:
| 应用场景 | 推荐采样率 | 理由 |
|---|---|---|
| 屏幕自动旋转 | 5-10 Hz | 人眼对旋转变化不敏感,低频足够 |
| 计步器 | 10-20 Hz | 步频最多每秒3步,20Hz绰绰有余 |
| 赛车游戏 | 50-100 Hz | 需要及时响应方向盘转动 |
| VR/AR 头部追踪 | 200-1000 Hz | 延迟超过20ms就会晕,必须高频 |
| 运动姿态分析 | 100-200 Hz | 人体动作频率一般不超过20Hz,但需要过采样做滤波 |
如何验证你设的采样率是否生效?
代码写完了,怎么知道实际跑的是多少?我一般用两种方法:
- 看时间戳差值:在
onSensorChanged里打印event.timestamp,相邻两次的差值就是实际采样间隔。 - 用 adb 命令:
adb shell dumpsys sensorservice,能看到每个传感器的当前采样率。
举个例子,你在回调里这么写:
long lastTimestamp = 0;
@Override
public void onSensorChanged(SensorEvent event) {
if (lastTimestamp != 0) {
long interval = event.timestamp - lastTimestamp;
// 注意:timestamp 是纳秒,要转成微秒
float actualRate = 1_000_000f / (interval / 1000);
Log.d("Gyro", "实际采样率: " + actualRate + " Hz");
}
lastTimestamp = event.timestamp;
// 处理陀螺仪数据...
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
}
一个小细节:
event.timestamp 的单位是纳秒(ns),不是微秒。很多新手在这里翻车,算出来的频率差了 1000 倍。你记住:1 秒 = 1,000,000 微秒 = 1,000,000,000 纳秒。
采样率设了,但硬件不听话怎么办?
说实话,你设的采样率只是一个「建议值」。硬件不一定完全照做。我遇到过的情况:
- 设了 50Hz,实际跑出 48Hz —— 正常,硬件有误差
- 设了 100Hz,实际只有 30Hz —— 硬件能力不够,被降级了
- 设了 200Hz,实际跑出 200Hz 但数据全是重复的 —— 传感器在「造假」
最后一种情况最坑。我记得有个项目,陀螺仪数据看起来频率很高,但仔细一看,连续 5 个数据一模一样。说白了,硬件能力跟不上,就重复上报旧数据充数。
怎么避免?我的做法是:永远不要完全相信采样率设置,要在代码里做校验。比如检查相邻数据是否完全相同,如果重复率超过 10%,就主动降低采样率。
好了,采样率这块就讲到这里。核心就一句话:够用就好,别贪高。下一章我们聊聊更实际的问题——数据来了之后,怎么处理才不卡顿。