20、系统广播与传感器:监听系统广播(如屏幕亮/灭),结合传感器数据做智能场景判断

各位同学,今天我们来聊一个非常实用的组合技——系统广播加传感器。说白了,就是让我们的App能感知到手机当前的状态变化,比如屏幕亮了、灭了,然后结合光线传感器的数据,做出更智能的判断。

我刚开始做这个功能的时候,其实挺困惑的。心想:我直接读光线传感器不就行了吗?为什么还要监听屏幕广播?后来踩了坑才明白——你想想看,如果屏幕都灭了,你还在后台疯狂读传感器,那不是白白耗电吗?

为什么需要系统广播?

系统广播是Android系统用来通知App「发生了一些系统级事件」的机制。比如:

  • 屏幕亮起(ACTION_SCREEN_ON)
  • 屏幕熄灭(ACTION_SCREEN_OFF)
  • 用户解锁(ACTION_USER_PRESENT)
  • 电量变化(ACTION_BATTERY_CHANGED)

这些广播是系统主动发出来的,我们只需要注册一个广播接收器就能收到。我个人习惯把传感器和广播结合起来,做一个「场景感知引擎」。举个例子:

屏幕灭了 → 停止传感器监听 → 省电
屏幕亮了 → 开始传感器监听 → 判断当前环境光线 → 自动调节屏幕亮度

这个逻辑听起来简单,但实际做起来有不少细节。我曾经在一个项目里,因为没处理好屏幕灭掉后的传感器注销,导致App在后台持续耗电,被用户投诉了。嗯,从那以后我再也不敢忽略这个步骤了。

核心思路: 系统广播控制传感器生命周期,传感器数据驱动业务逻辑。

注册广播接收器

有两种方式注册广播接收器:

  • 静态注册:在AndroidManifest.xml里声明,App没启动也能收到广播。但Android 8.0以后,大部分隐式广播已经被限制了。
  • 动态注册:在代码里注册,随Activity或Service的生命周期走。我推荐用这种方式,更灵活,也更好控制。

来看一个动态注册的例子:

private BroadcastReceiver screenReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (Intent.ACTION_SCREEN_ON.equals(action)) {
            // 屏幕亮了,开始监听光线传感器
            startLightSensor();
        } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
            // 屏幕灭了,停止监听,省电
            stopLightSensor();
        }
    }
};

// 在onResume里注册
@Override
protected void onResume() {
    super.onResume();
    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_SCREEN_ON);
    filter.addAction(Intent.ACTION_SCREEN_OFF);
    registerReceiver(screenReceiver, filter);
}

// 在onPause里注销
@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(screenReceiver);
}
小提示: 记得在onPause里注销广播接收器,否则会引发内存泄漏。我见过不少新手在这里翻车。

结合光线传感器做智能判断

光线传感器(Light Sensor)返回的是环境光照强度,单位是lux。数值范围从0到几万不等。比如:

场景 光照强度(lux)
暗室 0 ~ 10
室内灯光 100 ~ 500
室外阴天 1000 ~ 5000
室外晴天 10000 ~ 50000

我们可以根据这些数值,结合屏幕状态,做场景判断。比如:

  • 屏幕亮起 + 光线很暗 → 用户可能在暗光环境下使用手机 → 建议开启护眼模式
  • 屏幕亮起 + 光线很强 → 用户可能在户外 → 建议提高屏幕亮度
  • 屏幕灭掉 → 不管光线如何,都停止传感器监听

我做过一个实际项目,就是利用这个逻辑做了一个「自动亮度增强」功能。用户反馈说,在阳光下终于能看清屏幕了。其实原理很简单,就是读取光线传感器数据,然后动态调整屏幕亮度参数。

private SensorEventListener lightSensorListener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent event) {
        float lux = event.values[0];
        if (lux < 10) {
            // 暗光环境,开启护眼模式
            enableEyeCareMode(true);
        } else if (lux > 10000) {
            // 强光环境,提高屏幕亮度
            setScreenBrightness(255);
        } else {
            // 正常环境,恢复自动亮度
            enableAutoBrightness(true);
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // 精度变化时,一般不用处理
    }
};

private void startLightSensor() {
    SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
    Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
    if (lightSensor != null) {
        sensorManager.registerListener(lightSensorListener, lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
    }
}

private void stopLightSensor() {
    SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
    sensorManager.unregisterListener(lightSensorListener);
}
注意: 不要在主线程里做耗时操作。传感器回调频率可能很快,如果处理逻辑太重,会导致界面卡顿。我建议把判断逻辑放到子线程,或者用Handler做异步处理。

整体流程

下面这张图展示了整个流程,从系统广播触发,到传感器监听,再到场景判断:

系统广播 + 传感器 智能场景判断流程 系统广播 判断广播类型 屏幕亮起 屏幕熄灭 注册光线传感器 开始监听环境光 注销光线传感器 停止监听,省电 根据lux值做场景判断 不做任何操作 系统事件 广播处理 传感器操作 业务逻辑

避坑指南

做这个功能的时候,有几个坑我不得不提一下:

  • 屏幕灭掉后传感器还在跑:我曾经犯过这个错,结果App在后台疯狂读传感器,电量哗哗地掉。一定要在屏幕灭掉后及时注销传感器监听。
  • 广播接收器没注销:Activity销毁了,广播接收器还活着,就会内存泄漏。记得在onPause或onDestroy里unregister。
  • 传感器数据太频繁:光线传感器的回调频率可能很高,如果你在回调里做UI更新,很容易卡顿。我建议用SENSOR_DELAY_NORMAL,或者自己做个节流。
  • Android 8.0以上广播限制:静态注册很多广播已经收不到了,所以尽量用动态注册。
我的经验: 如果你要做更复杂的场景判断,比如「用户从口袋拿出手机」这种,可以结合距离传感器和加速度传感器一起用。但今天我们先打好基础,把屏幕广播和光线传感器玩明白。

好了,这一节的内容就到这里。系统广播加传感器,说白了就是让App更懂用户的使用场景。你想想看,如果能在用户需要的时候自动调整屏幕亮度、开启护眼模式,体验是不是好很多?

下一节我们会继续深入,看看怎么把多个传感器的数据融合起来,做更精准的场景识别。到时候我会分享一个我实际项目中的案例,保证干货满满。


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