17、锁屏(Keyguard)机制:KeyguardViewMediator的调度逻辑,锁屏窗口的显示与隐藏
锁屏,说白了就是手机从口袋掏出来,点亮屏幕后看到的那个界面。很多同学觉得它就是个简单的Activity,其实不是。锁屏在Android里是一个系统级窗口,它的生杀大权掌握在一个叫KeyguardViewMediator的家伙手里。
我个人习惯把KeyguardViewMediator比作一个「锁屏管家」。它不负责画界面,它只负责调度——什么时候显示锁屏,什么时候隐藏,什么时候该通知SystemUI去更新。今天我们就来扒一扒这个管家的底。
17.1 KeyguardViewMediator 是什么?
先看它的位置。它在com.android.systemui.keyguard包下,是SystemUI进程里的一个核心类。它的职责很明确:
- 监听系统状态(亮屏、熄屏、用户解锁、超时等)
- 决定锁屏窗口的显示/隐藏时机
- 协调WindowManager和SystemUI之间的通信
它不是单例,但整个SystemUI进程里通常只有一个实例。我在项目中遇到过有人试图new多个KeyguardViewMediator出来,结果锁屏状态乱成一锅粥——嗯,这种坑踩过一次就记住了。
17.2 核心调度逻辑
KeyguardViewMediator的调度逻辑,说白了就是一套状态机。它内部维护了几个关键状态:
| 状态 | 含义 | 触发条件 |
|---|---|---|
| HIDDEN | 锁屏已隐藏 | 用户解锁成功 |
| SHOWN | 锁屏正在显示 | 亮屏且未解锁 |
| SHOWING | 锁屏正在显示中(动画中) | 从HIDDEN到SHOWN的过渡 |
| HIDING | 锁屏正在隐藏(动画中) | 用户输入正确密码 |
你想想看,为什么需要这么多状态?因为锁屏的显示和隐藏不是瞬间完成的,它涉及窗口动画、过渡效果。如果直接跳转,用户会看到闪一下,体验很差。
我举个例子。当用户按下电源键亮屏时,流程是这样的:
// 伪代码,展示核心逻辑
onScreenTurnedOn() {
if (shouldShowKeyguard()) {
// 状态从HIDDEN -> SHOWING
mState = SHOWING;
// 通知WindowManager添加锁屏窗口
showLockScreenWindow();
// 等动画完成后,状态变为SHOWN
mState = SHOWN;
}
}
这里有个细节:shouldShowKeyguard()会检查很多条件——用户是否已经解锁、是否设置了锁屏、是否处于安全模式等等。我曾经调试过一个bug,用户明明设置了密码,但锁屏就是不显示。最后发现是shouldShowKeyguard()里有个flag被误重置了。
17.3 锁屏窗口的显示与隐藏
锁屏窗口不是普通的Activity窗口,它是一个系统级窗口,类型是TYPE_KEYGUARD。它的添加和移除是通过WindowManager.addView()和WindowManager.removeView()来完成的。
具体来说,KeyguardViewMediator会调用KeyguardViewManager来操作窗口。KeyguardViewManager内部持有WindowManager的引用,负责创建和销毁锁屏的View。
关键点:锁屏窗口的层级非常高,高于普通应用窗口,但低于状态栏和导航栏。这是为了保证锁屏不被普通应用遮挡,同时系统UI(如状态栏)仍然可见。
显示流程大致如下:
- KeyguardViewMediator收到亮屏事件
- 判断是否需要显示锁屏
- 调用KeyguardViewManager.show()
- KeyguardViewManager通过WindowManager.addView()添加锁屏View
- 锁屏View开始播放进入动画
- 动画结束后,通知KeyguardViewMediator状态变为SHOWN
隐藏流程则相反:
- 用户输入正确密码/指纹
- KeyguardViewMediator收到解锁成功回调
- 调用KeyguardViewManager.hide()
- 锁屏View播放退出动画
- 动画结束后,通过WindowManager.removeView()移除窗口
- 状态变为HIDDEN
避坑指南:我曾经遇到过锁屏窗口移除后,桌面Activity仍然无法响应触摸事件的问题。原因是锁屏窗口虽然移除了,但WindowManager的焦点没有及时切换。解决方案是在removeView之后,手动调用WindowManager.updateViewLayout()刷新焦点。
17.4 锁屏窗口的层级结构
锁屏窗口本身是一个FrameLayout,里面包含多个子View:
- 锁屏主体:显示时间、日期、通知等
- 解锁区域:密码输入框、图案解锁、指纹图标等
- 紧急按钮:紧急呼叫、关机等
- 壁纸层:如果设置了锁屏壁纸,这里会显示
这些子View的添加和更新,也是通过KeyguardViewMediator来协调的。比如用户输入密码错误时,KeyguardViewMediator会通知锁屏View显示错误提示。
17.5 与SystemUI的交互
KeyguardViewMediator不是孤军奋战。它和SystemUI里的多个模块有交互:
| 模块 | 交互内容 |
|---|---|
| StatusBar | 锁屏状态下,状态栏需要显示特定图标(如锁图标) |
| NotificationPanel | 锁屏状态下,通知面板只能显示部分内容 |
| BiometricPrompt | 指纹/面部识别成功后,通知KeyguardViewMediator解锁 |
| DreamManager | 进入屏保时,锁屏需要隐藏 |
这些交互都是通过回调或广播来实现的。KeyguardViewMediator内部维护了一个Callback列表,各个模块注册进来,当锁屏状态变化时,逐一通知。
嗯,这里要注意:回调的顺序很重要。比如必须先通知StatusBar更新状态,再通知NotificationPanel更新内容。如果顺序反了,用户可能会看到状态栏还显示着锁图标,但锁屏已经消失了——这种不一致的体验很糟糕。
17.6 知识体系总览
下面这张图总结了KeyguardViewMediator的核心调度逻辑:
从这张图可以看得很清楚:KeyguardViewMediator处于中心位置,接收各种系统事件,通过内部状态机决定下一步动作,最终输出到锁屏窗口和SystemUI。
17.7 总结
KeyguardViewMediator的调度逻辑,说白了就是一套事件驱动+状态机的经典设计。它不复杂,但细节很多。我建议你在阅读源码时,重点关注以下几点:
- 状态转换的边界条件(什么时候不该显示锁屏?)
- 窗口添加/移除的时机(动画开始前还是结束后?)
- 与SystemUI其他模块的通信顺序(谁先谁后?)
把这些搞清楚了,锁屏机制对你来说就没有秘密了。