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(如状态栏)仍然可见。

显示流程大致如下:

  1. KeyguardViewMediator收到亮屏事件
  2. 判断是否需要显示锁屏
  3. 调用KeyguardViewManager.show()
  4. KeyguardViewManager通过WindowManager.addView()添加锁屏View
  5. 锁屏View开始播放进入动画
  6. 动画结束后,通知KeyguardViewMediator状态变为SHOWN

隐藏流程则相反:

  1. 用户输入正确密码/指纹
  2. KeyguardViewMediator收到解锁成功回调
  3. 调用KeyguardViewManager.hide()
  4. 锁屏View播放退出动画
  5. 动画结束后,通过WindowManager.removeView()移除窗口
  6. 状态变为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 调度逻辑总览 KeyguardViewMediator 亮屏/熄屏事件 用户解锁事件 超时/锁屏策略 显示锁屏窗口 隐藏锁屏窗口 通知SystemUI更新 内部状态机 HIDDEN → SHOWING → SHOWN → HIDING → HIDDEN (状态转换驱动窗口显示/隐藏)

从这张图可以看得很清楚:KeyguardViewMediator处于中心位置,接收各种系统事件,通过内部状态机决定下一步动作,最终输出到锁屏窗口和SystemUI。

17.7 总结

KeyguardViewMediator的调度逻辑,说白了就是一套事件驱动+状态机的经典设计。它不复杂,但细节很多。我建议你在阅读源码时,重点关注以下几点:

  • 状态转换的边界条件(什么时候不该显示锁屏?)
  • 窗口添加/移除的时机(动画开始前还是结束后?)
  • 与SystemUI其他模块的通信顺序(谁先谁后?)

把这些搞清楚了,锁屏机制对你来说就没有秘密了。


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