Android 系统锁屏监听与悬浮窗实现指南
在 Android 应用开发中,有时需要在用户锁屏状态下执行特定任务,例如闹钟响铃、安全监控或紧急通知。这涉及到两个核心技术点:如何准确监听系统的屏幕状态变化,以及如何在不干扰用户正常解锁流程的前提下弹出悬浮窗口。本文将详细解析这两个环节的实现原理、代码实践及注意事项。
一、如何监听系统屏幕锁屏
监听屏幕状态是基础,主要有两种方式:通过系统 API 直接轮询判定,或通过广播接收器被动接收系统事件。推荐优先使用广播方式,因为它更省电且响应及时。
1. 代码直接判定
通过系统服务获取当前屏幕状态,适用于需要即时判断的场景。
a) PowerManager 检测
PowerManager 提供了 isScreenOn() 方法,用于判断屏幕是否处于点亮状态。
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
boolean isScreenOn = pm.isScreenOn();
状态说明:
- 屏幕'亮':包含两种情况,a) 未锁屏(桌面可见);b) 已解锁但屏幕保持开启。这两种状态下
isScreenOn 均为 true。
- 屏幕'暗':表示屏幕熄灭,通常意味着设备进入休眠或锁屏界面显示前。
b) KeyguardManager 检测
KeyguardManager 提供了 inKeyguardRestrictedInputMode() 方法,专门用于判断是否处于锁屏输入模式。
KeyguardManager mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
boolean flag = mKeyguardManager.inKeyguardRestrictedInputMode();
状态说明:
- flag 为 true:表示屏幕是黑的,或者正处于锁屏密码/图案输入界面。
- flag 为 false:表示目前未锁屏,用户可以自由操作。
注意:上述方法在不同 Android 版本中表现一致,但在高版本系统中,建议结合广播使用以提高准确性。部分旧代码可能涉及反射调用,现代开发中应直接使用公开 API。
2. 接收广播
当安卓系统发生锁屏、屏幕亮起或解锁时,会发送相应的广播。这是最标准且推荐的方式。
注册广播的伪代码如下:
private ScreenBroadcastReceiver mScreenReceiver;
private class ScreenBroadcastReceiver extends BroadcastReceiver {
private String action = null;
@Override
public void onReceive(Context context, Intent intent) {
action = intent.getAction();
if (Intent.ACTION_SCREEN_ON.equals(action)) {
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
}
}
}
private void startScreenBroadcastReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
context.registerReceiver(mScreenReceiver, filter);
}
最佳实践:
- 务必在
onDestroy 或 onPause 中注销广播,防止内存泄漏。
- 对于后台保活需求,需注意 Android 8.0+ 对后台广播的限制,建议使用
JobScheduler 或前台服务配合广播。
二、如何在锁屏界面弹出悬浮窗
知道屏幕状态后,下一步是在锁屏界面展示内容。实现方式主要有两种:使用 WindowManager 添加 View,或使用 Activity 启动新界面。两者各有优劣,需根据业务场景选择。
1. 使用 WindowManager
这种方式灵活性高,适合展示简单的提示框、倒计时或状态指示器。
核心配置代码如下:
private void init(Context mContext) {
this.mContext = mContext;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(dm);
mScreenWidth = dm.widthPixels;
mScreenHeight = dm.heightPixels;
this.mWmParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mWmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
mWmParams.format = PixelFormat.RGBA_8888;
mWmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
mWmParams.gravity = Gravity.LEFT | Gravity.TOP;
mWmParams.x = 0;
mWmParams.y = mScreenHeight / 2;
mWmParams.width = LayoutParams.WRAP_CONTENT;
mWmParams.height = LayoutParams.WRAP_CONTENT;
addView(createView(mContext));
mWindowManager.addView(this, mWmParams);
}
Window Type 详解:
TYPE_APPLICATION:普通应用窗口,通常被锁屏遮挡。
TYPE_SYSTEM_ALERT:系统警告窗口,权限要求较高。
TYPE_APPLICATION_OVERLAY:Android 8.0+ 新增,用于覆盖在其他应用之上,需申请 SYSTEM_ALERT_WINDOW 权限。
TYPE_KEYGUARD_DIALOG:专门用于锁屏对话框。
重要提示:在 Android 6.0+ 及以上版本,若要使用 TYPE_APPLICATION_OVERLAY,必须在设置页面引导用户开启'显示悬浮窗'权限,否则 addView 会抛出异常。
2. 使用 Activity
如果需要展示复杂布局或交互,启动一个特殊的 Activity 更为合适。
Activity 的设置
Activity 需要在 onCreate 中添加特定的 Window Flag,才能在锁屏状态下显示。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
}
Manifest 声明
在 AndroidManifest.xml 中配置 Activity 属性,确保其独立于任务栈。
<activity android:name=".AlarmHandlerActivity"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:taskAffinity=""
android:theme="@android:style/Theme.Wallpaper.NoTitleBar"/>
背景处理:
- 若背景设为默认白色,弹窗后周围一片白,视觉效果差。
- 若背景透明,可透出底层界面,但可能泄露隐私。
- 建议设置为壁纸主题或自定义半透明遮罩,平衡美观与安全。
在广播中启动锁屏弹窗
在接收到锁屏广播后,判断当前状态再启动 Activity。
@Override
public void onReceive(Context context, Intent intent) {
Log.d(LOG_TAG, intent.getAction());
KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
if (km.inKeyguardRestrictedInputMode()) {
Intent alarmIntent = new Intent(context, AlarmActivity.class);
alarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(alarmIntent);
}
}
唤醒屏幕处理:
再次亮起屏幕时,如果 Activity 未退出但被手动锁屏,需重新唤醒屏幕。
PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
if (!pm.isScreenOn()) {
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP |
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright");
try {
wl.acquire();
wl.release();
} finally {
if (wl.isHeld()) {
wl.release();
}
}
}
所需权限
在 AndroidManifest.xml 中添加以下权限:
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
第一条是解锁屏幕需要的,第二条是申请电源锁需要的。第三条用于悬浮窗权限。
三、常见问题与优化建议
1. 电池优化与后台限制
Android 5.0+ 引入了 Doze 模式和 App Standby,可能会拦截后台广播。建议在电量优化设置中将应用加入白名单,或使用前台服务维持运行。
2. 内存泄漏风险
WindowManager 和 BroadcastReceiver 容易引发内存泄漏。务必在组件销毁时调用 removeView 和 unregisterReceiver。建议使用 WeakReference 持有 Context。
3. 用户体验影响
频繁唤醒屏幕或弹出悬浮窗可能打扰用户。建议仅在必要时触发,并提供便捷的关闭入口。对于锁屏弹窗,尽量做到无感或低干扰。
4. 安全性考量
悬浮窗功能常被恶意软件利用。Google Play 政策对 SYSTEM_ALERT_WINDOW 权限有严格审核。请确保你的应用用途正当,并在隐私政策中明确说明。
四、总结
本文详细介绍了 Android 下监听锁屏状态及实现锁屏悬浮窗的两种主流方案。通过合理使用 PowerManager、KeyguardManager 及 WindowManager,开发者可以构建出稳定且合规的锁屏交互体验。在实际开发中,请务必关注不同 Android 版本的兼容性差异,严格遵守系统权限规范,以确保应用的稳定性和安全性。