6、注册与注销监听器:registerListener() 参数详解

光线传感器要真正工作起来,关键一步就是注册监听器。这一步看似简单,但坑不少。我见过不少新手在这里翻车——要么忘记注销导致内存泄漏,要么采样率设置不合理把电池耗光。

今天我们就来把 registerListener() 的三个参数彻底讲透,再聊聊 onPause/onResume 里的最佳实践。

6.1 registerListener() 的三个参数

先看方法签名:

SensorManager.registerListener(
    SensorEventListener listener, 
    Sensor sensor, 
    int samplingPeriodUs
)

三个参数,一个都不能错。我们逐个拆解。

参数一:SensorEventListener

这是个接口,你需要实现两个回调方法:

  • onSensorChanged(SensorEvent event) —— 传感器数据变化时触发
  • onAccuracyChanged(Sensor sensor, int accuracy) —— 精度变化时触发

我个人习惯把 SensorEventListener 直接写在 Activity 或 Fragment 里实现,简单粗暴。但如果你有多个传感器要监听,建议单独抽一个类出来,不然代码会变得很臃肿。

小技巧: 如果你只关心数据变化,onAccuracyChanged 里可以什么都不写。但别空着,打个日志也好,方便调试。

参数二:Sensor 对象

这个就是你之前通过 getDefaultSensor() 拿到的传感器实例。注意一点:不要每次注册时都重新获取 Sensor 对象。我建议在 onCreate 里一次性获取并保存起来,反复使用。

// 推荐做法
private Sensor mLightSensor;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
    mLightSensor = sm.getDefaultSensor(Sensor.TYPE_LIGHT);
}

参数三:采样率(samplingPeriodUs)

这个参数最容易让人困惑。它决定了系统多久给你回调一次数据。单位是微秒,但实际开发中我们通常用 SensorManager 提供的常量:

常量 含义 典型延迟 适用场景
SENSOR_DELAY_NORMAL 普通延迟 约 200ms 屏幕亮度自动调节
SENSOR_DELAY_UI UI 更新频率 约 60ms 实时显示光照值
SENSOR_DELAY_GAME 游戏级延迟 约 20ms 快速响应场景
SENSOR_DELAY_FASTEST 最快频率 约 5ms 极少使用,耗电高
注意: 这些只是「建议值」。系统会根据当前负载和硬件能力做调整。你设了 NORMAL,但系统可能给你 300ms 一次。别太较真。

我曾经在一个项目里用了 FASTEST 来监听光线传感器,结果手机发烫严重。后来发现光线变化没那么快,改成 UI 就完全够用了。说白了,采样率不是越高越好,够用就行。

6.2 注销监听器:unregisterListener()

注册了就得注销,这是铁律。不注销会怎样?

  • Activity 销毁了,但 SensorManager 还持有你的回调引用
  • GC 无法回收你的 Activity,造成内存泄漏
  • 传感器持续工作,白白消耗电量

注销代码很简单:

sensorManager.unregisterListener(mLightSensorListener);

嗯,这里要注意:注销时传入的 listener 对象必须和注册时是同一个。如果你注册时 new 了一个匿名内部类,注销时又 new 了一个,那就注销不掉了。我刚开始就犯过这个错,排查了半天。

6.3 onPause/onResume 中的最佳实践

这是整个章节最核心的部分。为什么要在 onPauseonResume 里做注册注销?

你想想看,用户按了 Home 键,Activity 进入后台。如果传感器还在工作,那就是在浪费资源。等用户切回来,再重新注册。这样既省电又安全。

标准写法如下:

@Override
protected void onResume() {
    super.onResume();
    sensorManager.registerListener(
        mLightSensorListener,
        mLightSensor,
        SensorManager.SENSOR_DELAY_UI
    );
}

@Override
protected void onPause() {
    super.onPause();
    sensorManager.unregisterListener(mLightSensorListener);
}
最佳实践总结:
  • onResume 中注册监听器
  • onPause 中注销监听器
  • 不要在 onDestroy 中注销,因为 onDestroy 不一定被调用
  • 如果使用了多个传感器,统一管理注册注销逻辑

我曾经接手过一个项目,代码里在 onStart 注册、onStop 注销。看起来没问题对吧?但那个 App 有个画中画模式,onStart/onStop 的调用时机和 onResume/onPause 不一样,导致画中画模式下传感器还在跑。后来全部改成 onResume/onPause 就解决了。

6.4 知识体系总览

下面这张图帮你理清注册注销的整体流程:

光线传感器注册注销流程 注册流程 (onResume) 1. 获取 SensorManager 2. 获取 Sensor 对象 3. registerListener() ✅ 开始接收传感器数据 注销流程 (onPause) 1. 检查 listener 是否为空 2. unregisterListener() 3. 释放 Sensor 引用 ⛔ 停止接收数据 核心原则:onResume 注册 → onPause 注销,成对出现

6.5 避坑指南

最后分享几个我踩过的坑:

  • 重复注册:如果 onResume 被多次调用,记得先注销再注册,或者加个标志位判断。
  • 空指针:如果设备没有光线传感器,getDefaultSensor() 返回 null。注册前一定要判空。
  • Context 泄漏:如果你在 Fragment 里注册,记得用 getActivity() 获取 Context,不要持有 Activity 的强引用。

嗯,关于注册注销就讲这么多。记住一个口诀:成对出现,生命周期对齐,采样率够用就好。做到这三点,你的传感器代码基本就稳了。


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