3、监听加速度数据:注册与注销 SensorEventListener
好,现在传感器拿到了,也确认它活着了。下一步干嘛?
监听数据。
说白了,就是让系统在加速度变化时主动通知你。你不能自己写个死循环去轮询——那太傻了,而且耗电。Android 提供了 SensorEventListener 这个接口,你实现它,然后注册给传感器管理器。数据一来,系统回调你。
3.1 实现 SensorEventListener 接口
这个接口只有两个方法需要实现:
onSensorChanged(SensorEvent event)—— 传感器数值变化时触发onAccuracyChanged(Sensor sensor, int accuracy)—— 传感器精度变化时触发
我个人的习惯是,把监听器写成内部类或者直接在 Activity/Fragment 里实现接口。简单项目我直接让 Activity 实现,方便管理上下文。
public class MainActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor accelerometer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
// 这里处理你的业务逻辑
// 比如更新UI、判断摇一摇、记录数据等
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 精度变化时回调,一般不太常用
// accuracy 取值:SENSOR_STATUS_UNRELIABLE, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH
}
}
嗯,这里要注意:onSensorChanged 是在 子线程 里被调用的。你不能在里面直接更新 UI,否则会崩溃。我早期踩过这个坑,一运行就闪退,找了半天才发现是线程问题。
onSensorChanged 运行在子线程。更新 UI 请使用 Handler、runOnUiThread 或 View.post()。
3.2 注册监听器
实现完接口,接下来就是注册。一般在 onResume() 里注册,因为这时候 Activity 可见,用户正在使用,传感器数据才有意义。
@Override
protected void onResume() {
super.onResume();
if (accelerometer != null) {
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
}
第三个参数是采样频率,Android 提供了四个级别:
| 常量 | 说明 | 典型延迟 |
|---|---|---|
SENSOR_DELAY_NORMAL |
适合普通场景,比如屏幕旋转 | 约 200ms |
SENSOR_DELAY_UI |
适合更新 UI,比如游戏 | 约 60ms |
SENSOR_DELAY_GAME |
适合游戏,响应快 | 约 20ms |
SENSOR_DELAY_FASTEST |
最快频率,耗电高 | 约 5ms |
你想想看,如果只是判断手机是否平放,用 NORMAL 就够了。要是做体感游戏,那得用 GAME 甚至 FASTEST。我在做一个跑步 App 时,用了 FASTEST,结果手机半小时就没电了……后来改成 UI 级别,完全够用。
不要盲目追求高频。频率越高,CPU 唤醒越频繁,耗电越严重。先想清楚你的业务需要多快的响应,再选合适的延迟。
3.3 注销监听器
这个步骤很多人会忘。但忘了的后果很严重——传感器会一直工作,即使 App 退到后台。电量就是这么悄悄溜走的。
我一般会在 onPause() 里注销。为什么不是 onStop()?因为 onPause() 更早触发,能更快释放资源。
@Override
protected void onPause() {
super.onPause();
if (sensorManager != null) {
sensorManager.unregisterListener(this);
}
}
注意:unregisterListener 会注销当前 Activity 注册的 所有 传感器监听。如果你只注册了加速度计,那没问题。如果注册了多个传感器,只想注销其中一个,可以传第二个参数指定 Sensor 对象:
sensorManager.unregisterListener(this, accelerometer);
我曾经在
onDestroy() 里注销,结果发现用户按 Home 键回到桌面,传感器还在跑。因为 onDestroy() 不一定立即调用。所以记住:在 onPause() 里注销,这是最佳实践。
3.4 完整生命周期管理
把注册和注销放到生命周期里,形成一个闭环:
@Override
protected void onResume() {
super.onResume();
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(this);
}
这样,用户切到后台时,传感器停止工作。用户切回来时,重新注册。既省电又安全。
3.5 核心逻辑流程图
下面这张图,帮你理清整个注册与注销的流程:
3.6 常见问题与避坑
- 忘记注销:传感器持续工作,耗电飙升。我见过一个 App 后台跑了一整夜,手机直接没电关机。
- 在 onDestroy 里注销:不靠谱。按 Home 键不会立即触发 onDestroy,传感器会一直运行。
- 多次注册:同一个 Listener 多次注册,会导致回调多次触发。每次 onResume 只注册一次就好。
- 空指针:如果 getDefaultSensor 返回 null,说明设备没有加速度传感器。注册前一定要判空。
- 在 onResume() 里注册监听器
- 在 onPause() 里注销监听器
- onSensorChanged 里不要直接更新 UI
- 根据业务需求选择合适的采样频率
- 记得判空,防止无传感器设备崩溃
好了,注册与注销就这些。说白了就是两行代码的事,但放对位置很重要。你想想看,一个 App 里几十个页面,每个都忘了注销,那用户的电量可就遭殃了。
我早期做第一个传感器项目时,就犯过这个错。测试的时候没感觉,上线后用户反馈手机发烫、掉电快。查了半天,发现是传感器没注销。从那以后,我每次写传感器代码,第一件事就是把 onResume 和 onPause 的配对写好。