6、实现 SensorEventListener:onSensorChanged 和 onAccuracyChanged 回调

好,到了这一步,我们终于要跟传感器数据「正面交锋」了。

前面几章我们注册了传感器、拿到了 SensorManager,但说白了,那些都是准备工作。真正让 App 感知到手机在动、在转、在倾斜的,就是这两个回调方法:onSensorChangedonAccuracyChanged

我个人习惯把这两个方法比作「传感器数据的水龙头」——一个负责流水(数据),一个负责告诉你水质好不好(精度)。

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 的整个工作流程:

SensorEventListener 回调流程 注册 SensorEventListener 传感器有数据? onSensorChanged 被调用 event 对象携带数据 精度检查 可靠 不可靠 处理数据(积分/显示) 丢弃数据(return) onAccuracyChanged 精度等级变化时触发

从图上你能看到,整个流程其实不复杂。关键就两点:

  1. 数据来了,先别急着用,看一眼精度。
  2. 精度没问题,再拿 values 去做后续处理。

说白了,onSensorChanged 是数据入口,onAccuracyChanged 是质量监控。两者配合好了,你的陀螺仪应用才能稳定可靠。

我记得有一次做 AR 导航项目,陀螺仪数据偶尔会跳变,导致画面抖动。排查了半天,发现是 onAccuracyChanged 回调里没有做任何处理,精度降到 LOW 时数据还在被使用。后来加上精度判断,问题就消失了。嗯,细节决定成败啊。


公众号:蓝海资料掘金营,微信 deep3321