20、动态传感器管理:在 onResume 注册、onPause 注销监听器
说实话,这个话题我特别想跟你好好聊聊。
为什么?因为我在早期做传感器开发时,就因为这个看似简单的「注册/注销」问题,踩过一个大坑。那会儿刚接手一个运动追踪App,用户反馈说手机发烫、电量掉得飞快。我排查了半天,最后发现——传感器监听器在Activity退到后台后,居然还在疯狂上报数据。
嗯,从那以后,我再也不敢小看「动态管理」这四个字了。
为什么非要动态管理?
你想想看,传感器一旦注册了监听器,它就会持续不断地往CPU扔数据。哪怕你的界面已经退到后台,哪怕用户已经把手机揣兜里了,它还在工作。
这会带来三个直接后果:
- 电量浪费——传感器芯片和CPU都在空转
- CPU负载升高——回调函数被频繁调用,可能引发卡顿
- 数据污染——后台采集的数据对前台界面毫无意义
说白了,这就是一种资源泄漏。跟忘记关闭Cursor、忘记释放Bitmap是一个道理。
核心原则:传感器监听器的生命周期,必须与Activity/Fragment的可见生命周期绑定。
可见时注册,不可见时注销。就这么简单。
标准做法:onResume / onPause 配对
我个人习惯把传感器注册放在 onResume(),注销放在 onPause()。为什么不是 onStart() / onStop()?
因为 onResume() 和 onPause() 才真正对应「界面可见且可交互」的状态。举个例子,如果系统弹出一个透明对话框,Activity会走 onPause() 但不会走 onStop()。这时候传感器就该停掉,因为用户已经看不到你的界面了。
public class SensorActivity extends AppCompatActivity {
private SensorManager sensorManager;
private Sensor accelerometer;
private SensorEventListener listener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sensor);
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
listener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
// 处理传感器数据
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
// 更新UI...
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 精度变化时处理
}
};
}
@Override
protected void onResume() {
super.onResume();
if (accelerometer != null) {
sensorManager.registerListener(listener, accelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
}
}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(listener);
}
}
小提示:在 onPause() 中注销时,不需要判空。因为 unregisterListener() 方法内部已经做了空安全处理。传一个 null 进去也不会崩溃。
避坑指南:我曾经犯过的错
我曾经在 onDestroy() 里注销监听器,觉得「反正Activity要销毁了,这时候注销最保险」。结果呢?用户快速旋转手机时,Activity重建走了 onPause() → onDestroy() → onCreate() → onResume()。如果 onPause() 里没注销,而 onDestroy() 里才注销,中间那段时间传感器一直在跑。更糟的是,如果 onDestroy() 没被调用(比如系统杀进程时),监听器就永远泄漏了。
还有一次,我忘了在 onPause() 里注销,结果用户把App切到后台,传感器还在疯狂上报。那款手机的陀螺仪功耗本来就高,半小时后手机直接过热保护了。嗯,用户给了个一星差评,说「这App是暖手宝吗?」
警告:千万不要在 onSaveInstanceState() 中注销传感器。这个方法不保证在Activity销毁前被调用,而且它的设计目的是保存UI状态,不是释放资源。
多传感器场景下的管理策略
如果你的页面同时使用了多个传感器(比如加速度计+陀螺仪+磁力计),我建议统一管理,而不是分散注册。
private List<Sensor> sensorList = new ArrayList<>();
private void registerAllSensors() {
sensorList.clear();
addSensor(Sensor.TYPE_ACCELEROMETER);
addSensor(Sensor.TYPE_GYROSCOPE);
addSensor(Sensor.TYPE_MAGNETIC_FIELD);
for (Sensor sensor : sensorList) {
sensorManager.registerListener(listener, sensor,
SensorManager.SENSOR_DELAY_GAME);
}
}
private void addSensor(int type) {
Sensor s = sensorManager.getDefaultSensor(type);
if (s != null) {
sensorList.add(s);
}
}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(listener);
}
这样做的好处是:注销时只需要一行代码,所有传感器一次性解绑。而且 unregisterListener() 会遍历内部列表,把该监听器绑定的所有传感器都注销掉,不用担心遗漏。
关于采样频率的补充
注册传感器时,第三个参数是采样延迟。我见过有人为了省电,在 onResume() 里用 SENSOR_DELAY_UI,在 onPause() 里注销。这没问题。
但如果你需要在后台保持低频率采集(比如计步器场景),那就不能简单地在 onPause() 里注销了。这时候应该考虑 前台服务(Foreground Service),把传感器管理放到Service里,用通知告诉用户「我正在后台使用传感器」。这是另一个话题,我们后面会讲到。
知识体系总览
下面这张图,帮你理清动态传感器管理的核心逻辑:
总结一下
动态传感器管理,说白了就是两件事:
- 什么时候注册?——界面可见且可交互时,也就是
onResume() - 什么时候注销?——界面不可见时,也就是
onPause()
记住这个配对关系,你的App就不会出现传感器泄漏的问题。我见过太多新手在 onCreate() 里注册、在 onDestroy() 里注销,结果各种诡异Bug。其实只要遵循这个简单的生命周期规则,90%的问题都能避免。
嗯,下次如果你遇到手机发烫、电量异常,不妨先检查一下——传感器监听器是不是忘了注销?
公众号:蓝海资料掘金营,微信deep3321