10、实战:通话熄屏功能(上):监听电话状态(TelephonyManager)、结合距离传感器实现通话中自动熄屏。
各位同学,欢迎来到实战环节。
前面几章我们把距离传感器的原理、API 调用、数据校准都讲透了。说实话,光讲理论容易让人犯困。今天咱们直接上手一个真实场景——通话熄屏。
这个功能大家每天都在用:打电话时手机贴到耳朵上,屏幕自动黑掉;拿下来,屏幕又亮了。听起来简单,但里面坑不少。我当年第一次做这个功能时,就被各种边缘情况折腾得够呛。
10.1 整体思路:先监听电话,再管传感器
要实现通话熄屏,核心逻辑分两步:
- 知道什么时候在通话——用 TelephonyManager 监听电话状态。
- 知道手机是否贴近耳朵——用距离传感器判断。
说白了,就是电话一接通,开始看距离传感器;电话一挂断,停止监听传感器。
嗯,这里要注意:顺序不能反。必须先确认电话状态,再操作传感器。否则会出现「明明没在通话,传感器一遮就黑屏」的尴尬情况。
核心流程图:
10.2 第一步:用 TelephonyManager 监听电话状态
TelephonyManager 是 Android 系统提供的电话管理服务。通过它,我们能拿到当前的通话状态。
我个人习惯先注册一个 PhoneStateListener,然后在回调里处理三种状态:
- IDLE:空闲,没有通话。
- RINGING:来电响铃中。
- OFFHOOK:通话中(包括拨出和接听)。
来看代码:
// 获取 TelephonyManager 实例
TelephonyManager telephonyManager =
(TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
// 创建电话状态监听器
PhoneStateListener phoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String phoneNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
// 空闲状态,注销传感器
unregisterProximitySensor();
break;
case TelephonyManager.CALL_STATE_RINGING:
// 来电响铃,暂时不处理传感器
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
// 通话中,注册传感器
registerProximitySensor();
break;
}
}
};
// 开始监听
telephonyManager.listen(phoneStateListener,
PhoneStateListener.LISTEN_CALL_STATE);
小提示:记得在 Activity 或 Service 销毁时取消监听,否则会造成内存泄漏。
取消方式:telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
10.3 第二步:结合距离传感器实现熄屏
电话状态监听搞定了,接下来就是传感器部分。
我在项目中遇到过一个问题:传感器数据变化太快,导致屏幕频繁亮灭。你想想看,打电话时手机稍微晃一下,屏幕就跟着闪,用户体验极差。
解决办法是加一个防抖阈值。传感器值变化后,不要立刻操作屏幕,而是等一小段时间(比如 200ms)再执行。
核心代码:
private SensorEventListener proximityListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
float distance = event.values[0];
float maxRange = event.sensor.getMaximumRange();
// 判断是否贴近
boolean isNear = distance < maxRange / 2;
// 防抖处理:延迟 200ms 执行
handler.removeCallbacks(updateScreenTask);
handler.postDelayed(updateScreenTask, 200);
}
private Runnable updateScreenTask = new Runnable() {
@Override
public void run() {
if (isNear) {
// 熄屏
turnScreenOff();
} else {
// 亮屏
turnScreenOn();
}
}
};
};
private void registerProximitySensor() {
SensorManager sensorManager =
(SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor proximitySensor =
sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
sensorManager.registerListener(proximityListener,
proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
}
private void unregisterProximitySensor() {
SensorManager sensorManager =
(SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensorManager.unregisterListener(proximityListener);
// 恢复屏幕
turnScreenOn();
}
警告:熄屏操作需要 WAKE_LOCK 权限,并且要使用 PowerManager 的 goToSleep() 方法。
亮屏则用 wakeUp() 方法。注意这两个方法都需要 android.permission.DEVICE_POWER 权限,系统应用才能调用。
普通应用建议使用 FLAG_KEEP_SCREEN_ON 或 PROXIMITY_SCREEN_OFF_WAKE_LOCK 来实现类似效果。
10.4 避坑指南:我曾经踩过的三个坑
做这个功能时,我踩过不少坑。挑三个最典型的分享给大家:
| 坑 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| 坑一 | 通话结束屏幕不亮 | 挂断时没有注销传感器 | 在 IDLE 状态强制亮屏 |
| 坑二 | 屏幕频繁闪烁 | 传感器数据抖动 | 加 200ms 防抖延迟 |
| 坑三 | 某些手机传感器不工作 | 硬件差异或驱动问题 | 加空值判断和降级策略 |
我曾经在坑一上折腾了一整天。当时测试发现,通话结束后屏幕一直黑着,按电源键也没反应。后来才发现是 unregisterProximitySensor() 里忘了调用 turnScreenOn()。嗯,这种低级错误,犯过一次就再也不会忘了。
10.5 完整代码结构
最后,我把整个逻辑封装成一个工具类,方便大家直接使用:
public class CallScreenManager {
private Context context;
private TelephonyManager telephonyManager;
private SensorManager sensorManager;
private Sensor proximitySensor;
private PowerManager powerManager;
private Handler handler = new Handler();
// 电话状态监听
private PhoneStateListener phoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String phoneNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
unregisterProximity();
wakeScreen();
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
registerProximity();
break;
}
}
};
// 传感器监听
private SensorEventListener proximityListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
float distance = event.values[0];
float maxRange = proximitySensor.getMaximumRange();
boolean isNear = distance < maxRange / 2;
handler.removeCallbacksAndMessages(null);
handler.postDelayed(() -> {
if (isNear) {
sleepScreen();
} else {
wakeScreen();
}
}, 200);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
};
// 省略构造方法和具体实现...
}
好了,这一章的内容就到这里。代码逻辑其实不复杂,关键是把「电话状态」和「传感器状态」这两个维度协调好。下一章我们会继续深入,聊聊熄屏时的电源管理优化,以及如何应对各种奇葩机型的兼容性问题。
公众号:蓝海资料掘金营,微信deep3321