6、实现 SensorEventListener:onSensorChanged 和 onAccuracyChanged 回调
好,到了这一步,我们终于要跟传感器数据「正面交锋」了。
前面几章我们注册了传感器、拿到了 SensorManager,但说白了,那些都是准备工作。真正让 App 感知到手机在动、在转、在倾斜的,就是这两个回调方法:onSensorChanged 和 onAccuracyChanged。
我个人习惯把这两个方法比作「传感器数据的水龙头」——一个负责流水(数据),一个负责告诉你水质好不好(精度)。
6.1 onSensorChanged:数据从这里来
这个方法,是整堂课最核心的入口。只要传感器有新的数据产生,系统就会调用它。
它的签名长这样:
@Override
public void onSensorChanged(SensorEvent event) {
// event 里装着传感器数据
}
你想想看,这个 event 对象里都有什么?
- event.sensor:告诉你这是哪个传感器发来的数据。比如 TYPE_GYROSCOPE 还是 TYPE_ACCELEROMETER。
- event.values:一个 float 数组,长度取决于传感器类型。陀螺仪一般是 3 个值:绕 X、Y、Z 轴的角速度。
- event.timestamp:数据产生的时间戳,单位是纳秒。这个在做时间差计算时特别有用。
- event.accuracy:当前数据的精度等级。嗯,这个跟 onAccuracyChanged 是联动的。
重点记住:onSensorChanged 是在 UI 线程 里被调用的。如果你在里面做耗时操作,比如写文件、网络请求,App 会卡顿甚至 ANR。
我在项目中遇到过一个问题:有个同事直接在 onSensorChanged 里做矩阵运算,结果手机发热严重,界面掉帧。后来改成用 Handler 把数据丢到子线程处理,问题就解决了。
6.2 陀螺仪数据长什么样?
陀螺仪返回的 values 数组,三个元素分别代表:
| 索引 | 含义 | 单位 |
|---|---|---|
| values[0] | 绕 X 轴的角速度 | rad/s |
| values[1] | 绕 Y 轴的角速度 | rad/s |
| values[2] | 绕 Z 轴的角速度 | rad/s |
注意单位是弧度每秒,不是角度每秒。如果你需要角度值,记得做转换:角度 = 弧度 × 180 / π。
我刚开始做陀螺仪开发时,直接拿原始数据去算角度,结果发现数值小得离谱。后来才反应过来——哦,原来单位是弧度。嗯,这个坑我踩过,你们就别再踩了。
6.3 onAccuracyChanged:别忽视精度变化
这个方法说实话,平时用得不多。但一旦用上,往往就是救命的场景。
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// accuracy 取值:SENSOR_STATUS_UNRELIABLE, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH
}
精度等级有四个:
- SENSOR_STATUS_UNRELIABLE(0):数据不可靠,别用了。我建议直接丢弃当前帧。
- SENSOR_STATUS_ACCURACY_LOW(1):精度低,但还能用。适合做一些粗略的方向判断。
- SENSOR_STATUS_ACCURACY_MEDIUM(2):中等精度,大部分场景够用。
- SENSOR_STATUS_ACCURACY_HIGH(3):精度高,放心用。
注意:陀螺仪在刚启动时,精度可能不稳定。我曾经遇到过手机放在桌上没动,但 onSensorChanged 却一直输出非零值。后来发现是精度等级降到了 UNRELIABLE,但代码里没做判断,导致数据被错误地使用了。
所以我的建议是:在 onSensorChanged 里,先检查一下 event.accuracy,如果太低就跳过这次数据。代码大概这样:
@Override
public void onSensorChanged(SensorEvent event) {
if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
return; // 数据不可靠,直接扔掉
}
// 正常处理数据
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
// ...
}
6.4 完整实现示例
把上面两个回调串起来,一个完整的 SensorEventListener 实现大概长这样:
public class GyroscopeListener implements SensorEventListener {
private static final String TAG = "GyroListener";
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() != Sensor.TYPE_GYROSCOPE) {
return;
}
// 精度检查
if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
Log.w(TAG, "陀螺仪数据不可靠,已丢弃");
return;
}
float angularSpeedX = event.values[0];
float angularSpeedY = event.values[1];
float angularSpeedZ = event.values[2];
// 这里可以回调给 UI 层或者做积分计算
Log.d(TAG, String.format("角速度: X=%.3f, Y=%.3f, Z=%.3f",
angularSpeedX, angularSpeedY, angularSpeedZ));
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
if (sensor.getType() == Sensor.TYPE_GYROSCOPE) {
String accStr;
switch (accuracy) {
case SensorManager.SENSOR_STATUS_UNRELIABLE:
accStr = "不可靠";
break;
case SensorManager.SENSOR_STATUS_ACCURACY_LOW:
accStr = "低";
break;
case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM:
accStr = "中";
break;
case SensorManager.SENSOR_STATUS_ACCURACY_HIGH:
accStr = "高";
break;
default:
accStr = "未知";
}
Log.i(TAG, "陀螺仪精度变为: " + accStr);
}
}
}
小技巧:如果你在调试时发现 onSensorChanged 调用频率太高(比如每秒几百次),可以在回调里加一个简单的限频逻辑。比如每 50 毫秒只处理一次数据,这样能减少 CPU 开销。
6.5 核心逻辑流程图
下面这张图,帮你理清 SensorEventListener 的整个工作流程:
从图上你能看到,整个流程其实不复杂。关键就两点:
- 数据来了,先别急着用,看一眼精度。
- 精度没问题,再拿 values 去做后续处理。
说白了,onSensorChanged 是数据入口,onAccuracyChanged 是质量监控。两者配合好了,你的陀螺仪应用才能稳定可靠。
我记得有一次做 AR 导航项目,陀螺仪数据偶尔会跳变,导致画面抖动。排查了半天,发现是 onAccuracyChanged 回调里没有做任何处理,精度降到 LOW 时数据还在被使用。后来加上精度判断,问题就消失了。嗯,细节决定成败啊。