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);
}
结合光线传感器做智能判断
光线传感器(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);
}
整体流程
下面这张图展示了整个流程,从系统广播触发,到传感器监听,再到场景判断:
避坑指南
做这个功能的时候,有几个坑我不得不提一下:
- 屏幕灭掉后传感器还在跑:我曾经犯过这个错,结果App在后台疯狂读传感器,电量哗哗地掉。一定要在屏幕灭掉后及时注销传感器监听。
- 广播接收器没注销:Activity销毁了,广播接收器还活着,就会内存泄漏。记得在onPause或onDestroy里unregister。
- 传感器数据太频繁:光线传感器的回调频率可能很高,如果你在回调里做UI更新,很容易卡顿。我建议用SENSOR_DELAY_NORMAL,或者自己做个节流。
- Android 8.0以上广播限制:静态注册很多广播已经收不到了,所以尽量用动态注册。
好了,这一节的内容就到这里。系统广播加传感器,说白了就是让App更懂用户的使用场景。你想想看,如果能在用户需要的时候自动调整屏幕亮度、开启护眼模式,体验是不是好很多?
下一节我们会继续深入,看看怎么把多个传感器的数据融合起来,做更精准的场景识别。到时候我会分享一个我实际项目中的案例,保证干货满满。