1. 链路上到底有哪几个角色,各自管什么
灭屏这件事,表面上看就是按一下电源键,屏幕黑了。但如果你真去跟一遍日志,会发现从按键到 panel 下电,中间跨了输入系统、电源管理、窗口管理、SystemUI、显示服务、合成器好几层,任何一层卡住,表现都不一样。我遇到过灭屏后屏幕又闪一下的,也遇到过亮屏黑屏半秒才出锁屏的,排查到最后往往不是同一个原因。所以先把角色认清楚,比上来就抓日志有用得多。
这条链路里最核心的关键词是锁屏灭屏加载流程,它其实包含两个动作:一个是“灭屏”,也就是显示状态从 ON 走到 OFF;另一个是“锁屏加载”,也就是 Keyguard 视图在灭屏前后被 show 出来、窗口被添加、首帧被合成。这两个动作在时间上是交叠的,但由不同的模块驱动,混在一起看很容易误判。下面我按职责把几个主要角色拆开讲。
1.1 输入层:电源键怎么变成一次 goToSleep
电源键首先进入的是 InputReader,它把按键事件交给 InputDispatcher,再往上到 PhoneWindowManager 的interceptKeyBeforeQueueing。这里有个关键细节:电源键默认不会传给应用,ACTION_PASS_TO_USER会被清掉,也就是说应用基本没机会直接消费它。短按走interceptPowerKeyDown,最终调用PowerManager.goToSleep,带上GO_TO_SLEEP_REASON_POWER_BUTTON这个 reason;长按则走全局动作,比如关机菜单。
这一步看起来简单,但它决定了灭屏的“起点时间”。你抓日志时看到的Going to sleep due to power button就是从这里发出来的。如果这个时间点比你预期晚,问题可能不在电源服务,而在输入层:比如按键消抖、拦截策略、无障碍服务拦截,甚至是某些定制 ROM 把电源键短按改成了别的行为。我见过一个定制版本,短按电源键先触发一个自定义快捷面板,面板关闭后才真正 goToSleep,用户感知就是“按了没反应,过一会儿才黑”。
注意:排查灭屏慢,先确认
goToSleep的调用时间,而不是直接看屏幕什么时候黑。这两个时间点中间还隔着电源状态机和显示状态机。
1.2 电源层:Wakefulness 状态机管的是“睡没睡”
PowerManagerService 里维护的是mWakefulness,取值大致是 Awake、Asleep、Dreaming、Dozing。它管的是设备整体的睡眠状态,不是屏幕亮不亮。很多人把这两个概念混为一谈,结果看日志时对不上。比如设备处于 Dreaming(屏保)时,屏幕可能还亮着,但 wakefulness 已经不是 Awake 了。
goToSleepNoUpdateLocked做的事包括:检查当前是不是已经 asleep,避免重复;更新mWakefulness;记录mLastSleepTime和 reason;置位DIRTY_WAKEFULNESS;通知Notifier回调;最后触发updatePowerStateLocked。这一整套是同步走的,但里面有些回调会跨进程,所以耗时不一定稳定。updatePowerStateLocked才是真正去算 display 该是什么状态、wakelock 该怎么统计、dream 要不要启动的地方。
这里有个容易忽略的点:灭屏不等于设备立刻挂起。屏幕 OFF 之后,系统还可能持有 wakelock 一段时间,比如音乐播放、下载、定位。你看到屏幕黑了,但 CPU 还在跑,这是正常的。真正进入 suspend 是另一条路径,和锁屏加载不是一回事。
1.3 显示层:Display 状态机管的是“屏幕怎么黑”
DisplayPowerController 维护的是另一套状态:SCREEN_ON、SCREEN_DIM、SCREEN_BRIGHT、SCREEN_OFF、DOZE、DOZE_SUSPEND 等。它接收 PowerManagerService 传来的DisplayPowerRequest,算出目标状态,然后协调亮度斜坡、proximity 传感器、屏幕 off 超时、动画。
灭屏时它并不是“啪”一下把屏幕关掉,正常流程是先做亮度 ramp,再切 screen state,最后通知 SurfaceFlinger 设置 display power mode。亮度 ramp 的时长由 overlay 配置控制,不同厂商差异很大,有的几十毫秒,有的几百毫秒。这个 ramp 是灭屏“渐暗”效果的来源,也是很多“灭屏闪一下”问题的根源:如果 ramp 还没结束就切了 OFF,或者 keyguard 窗口在 OFF 之后才添加,合成器可能又提交一帧,看起来就是黑了一下又亮了一下。
1.4 窗口层:KeyguardController 决定锁屏该不该出现
从 Android 10 左右开始,Keyguard 的显隐裁决权逐渐收到 WindowManagerService 的KeyguardController里。它关心的事情包括:当前 display 上 keyguard 是否 showing、是否 occluded、keyguardGoingAway 动画是否在进行、有没有 Activity 通过showWhenLocked覆盖锁屏。
灭屏时,WMS 会通过KeyguardServiceDelegate调用 SystemUI 的KeyguardService,通知它“设备要睡了”。SystemUI 的KeyguardViewMediator收到onStartedGoingToSleep后决定要不要doKeyguardLocked。注意,这里是跨进程 binder 调用,SystemUI 进程如果刚好被回收,还得先把它拉起来,这个冷启动时间就是“锁屏加载慢”的常见来源。
1.5 应用层:SystemUI 里锁屏视图的真正加载
锁屏界面本身是 SystemUI 进程里的视图,不是 system_server 画的。KeyguardViewMediator负责状态机,KeyguardViewController负责把视图挂到窗口上,具体窗口通常是NotificationShadeWindowView这一套。加载过程包括:确认锁屏是否禁用、判断是否需要 show、调用KeyguardViewManager.show()、添加窗口、inflate 布局、绑定监听器、等待首帧绘制。
如果你的项目里锁屏是深度定制的,比如加了自己的媒体面板、快捷入口、动态壁纸,那加载耗时还会更长。我见过一个版本在锁屏里塞了一个 WebView 做资讯卡片,结果每次灭屏后亮屏,锁屏首帧要等一两秒,用户以为手机卡了。这种问题从 PowerManagerService 日志里完全看不出来,必须看 SystemUI 的 trace。
2. 灭屏链路的逐段拆解:从 goToSleep 到 panel 下电
把角色认清楚之后,再按时间顺序走一遍。我习惯把灭屏分成四段:决策段、交接段、显示段、收尾段。每一段的日志 tag 和耗时特征都不一样,分段看更容易定位。
2.1 决策段:goToSleepNoUpdateLocked 里到底算了什么
goToSleep进来之后,先做权限检查,然后进goToSleepInternal,再到goToSleepNoUpdateLocked。这里面有几个判断值得注意:如果当前mWakefulness已经是 Asleep,直接返回 false,不会重复走流程;如果传入的 eventTime 比mLastWakeTime还早,也会被忽略,这是为了防止时间戳乱序导致状态错乱。
真正更新状态时,它会设置mWakefulness = WAKEFULNESS_ASLEEP,把mWakefulnessChanging置 true,记录mLastSleepTime、mLastSleepReason,然后调用mNotifier.onWakefulnessChangeStarted。这个通知会发给 battery stats、DreamManager、AppOps 等一堆监听者。最后updatePowerStateLocked被触发,开始算 display 和 wakelock。
这里有个实操经验:如果你在日志里看到goToSleep被调用了,但mWakefulness没变,或者mWakefulnessChanging一直是 true 不回落,那大概率是某个监听者在回调里卡住了。finishWakefulnessChangeIfNeededLocked要等所有回调完成才会把 changing 清掉,这期间 display 状态可能被阻塞。我曾经遇到一个第三方省电应用在onWakefulnessChangeStarted里做了同步 IO,导致灭屏延迟了将近一秒。
2.2 交接段:WMS 和 Keyguard 之间怎么打招呼
电源服务算完状态后,会通过DisplayManagerInternal.requestPowerState把请求发给 DisplayPowerController,同时 WMS 这边也会收到显示状态变化的通知。WMS 的KeyguardController会根据当前 display 的状态决定 keyguard 的显隐。灭屏时,它通常会先让 keyguard 进入 showing 状态,或者至少准备好。
SystemUI 侧的KeyguardViewMediator收到onStartedGoingToSleep后,会走doKeyguardLocked。这个方法里有一堆判断:锁屏是否被设置禁用、当前是不是已经 showing、是不是被 occluded、是不是有 trust agent 让设备保持解锁。如果决定要 show,就调用showLocked,再进KeyguardViewManager.show()。
注意:灭屏时 keyguard 的 show 和 display 的 OFF 是并行的,不是严格串行。如果你的定制版本把 keyguard show 放在 display OFF 之后,就可能出现“屏幕黑了,锁屏还没挂上去,合成器又提交一帧”的闪烁。
2.3 显示段:DisplayPowerController 的 OFF 迁移
DisplayPowerController 收到请求后,先更新mPendingRequest,置mDisplayReady = false,然后进updatePowerState。它会综合 proximity、brightness、screen off timeout、doze 状态,算出目标 power state。灭屏的目标通常是 SCREEN_OFF。
在切 OFF 之前,它会先处理亮度 ramp。如果当前亮度不是 0,会调用mBrightnessRampAnimator.animateTo(0, rate),让屏幕渐暗。ramp 结束后,setScreenState(SCREEN_OFF),再通过DisplayPowerState把状态下发到 DisplayManagerService,最终到 SurfaceFlinger 的setPowerMode。
这一段最常见的坑是ramp 和 OFF 的时序。有些版本为了追求“秒黑”,把 ramp 时长设成 0,结果屏幕直接跳变,观感很硬;有些版本 ramp 太长,用户按了电源键感觉半秒后才黑,以为失灵。我的建议是保留一个较短的 ramp,比如 100 到 200 毫秒,同时确保 keyguard 窗口在 ramp 开始前就已经添加,避免 OFF 之后还有窗口事务提交。
2.4 收尾段:SurfaceFlinger 与 HWC 的下电
SurfaceFlinger 收到setPowerMode(display, OFF)后,会通知 HWC,HWC 再调 display driver 的 power mode。不同 panel 的下电时间不一样,有的几毫秒,有的几十毫秒。这期间合成器可能还会处理最后一帧,尤其是当有窗口正在动画或者有 surface 正在更新时。
如果你在 trace 里看到setPowerMode之后还有onMessageInvalidate或者composite,说明灭屏后还有合成活动。这不一定有问题,但如果这一帧包含了 keyguard 的初始化内容,就可能出现视觉上的闪动。排查时重点看setPowerMode的时间点和 keyguard 窗口 first frame 的时间点,谁先谁后。
3. 锁屏界面自身的加载过程:为什么冷启动会慢
很多人把“灭屏加载”理解成电源流程,其实用户感知最强的是锁屏界面什么时候出来。尤其是亮屏那一刻,如果锁屏还没准备好,就会先看到黑屏或者桌面壁纸,然后锁屏才“跳”出来。这个体验问题在中低端机上特别常见,根因往往不是电源服务,而是 SystemUI 的加载。
3.1 SystemUI 进程与 KeyguardViewMediator 的初始化
SystemUI 是一个独立进程,里面跑着状态栏、导航栏、通知面板、锁屏、最近任务等一堆东西。KeyguardViewMediator只是其中一个组件,但它依赖的东西不少:KeyguardStateController、KeyguardUpdateMonitor、LockPatternUtils、TrustManager、StatusBarStateController、NotificationShadeWindowController等等。这些对象在 SystemUI 启动时通过依赖注入图创建,冷启动耗时可能达到几百毫秒甚至更长。
如果灭屏时 SystemUI 进程已经被系统回收,那么按键唤醒后,AMS 要先拉起 SystemUI,走Application.onCreate、SystemUIService.onCreate、构建组件、注册监听器,然后才轮到 keyguard show。这整段时间和电源服务没关系,但用户感知就是“亮屏慢”。我一般会先看dumpsys activity processes里 SystemUI 的 pid 有没有变,如果变了,基本就是冷启动问题。
3.2 show() 到窗口添加的路径
KeyguardViewMediator.showLocked最终会调用KeyguardViewManager.show()。这里面会做几件事:确保 view 已经创建、把 view 添加到 WindowManager、设置窗口的 flags 和 insets、通知状态栏更新。窗口添加是跨进程的,要经过 WMS 的addWindow,WMS 再为它创建 WindowState、分配 surface、通知 SurfaceFlinger。
如果此时 display 已经是 OFF,WMS 仍然可以添加窗口,但合成器不会显示。等 display 重新 ON 时,这一帧才会被合成。问题在于,如果窗口添加发生在 display ON 之后,用户就会先看到一帧没有锁屏的画面,然后锁屏才出现。这个“先黑后锁”或者“先桌面后锁”的现象,本质上是窗口添加时机和 display ON 时机没对齐。
3.3 视图 inflate 与首帧绘制
窗口添加只是第一步,真正的锁屏内容还要 inflate。默认锁屏包含时钟、日期、通知区、指纹图标、快捷入口、媒体面板等。如果定制版本加了复杂布局,inflate 时间会明显上升。inflate 发生在主线程,期间如果有其他消息排队,还会被延后。
首帧绘制完成之前,窗口可能是透明的或者只显示背景色。用户看到的就是“黑屏一下,然后锁屏淡入”。有些版本会给锁屏加 alpha 动画,进一步拉长感知时间。我的经验是,锁屏布局尽量扁平,避免在 onCreate 里做同步 IO,媒体面板和通知区可以懒加载,先把时钟和背景画出来。
3.4 为什么冷启动比热启动慢那么多
热启动时,SystemUI 进程还在,keyguard 的 view 可能只是被 detach 了,重新 attach 很快;冷启动时,整个进程从零开始,类加载、资源加载、依赖注入、binder 注册都要重来。中低端机上这个差距可能达到几百毫秒到一秒。
要缓解这个问题,常见做法是:让 SystemUI 常驻,避免被低内存回收;把 keyguard 的初始化提前到 SystemUI 启动阶段,而不是等到灭屏才做;减少启动路径上的同步调用。有些定制 ROM 会把锁屏做成一个单独的轻量进程,但这样又会带来状态同步的新问题,取舍要看产品定位。
4. 亮屏反向链路与 Doze、AOD 的插入点
灭屏流程讲完,必须把亮屏也带上,因为很多“灭屏加载”的问题其实是在亮屏时暴露的。另外,Doze 和 AOD 会在灭屏和亮屏之间插入额外的状态,打乱原本的时序,不理解这部分很容易误判。
4.1 wakeUp 之后为什么先看到黑屏
wakeUp的路径和goToSleep对称:电源键触发wakeUp,PowerManagerService 更新mWakefulness = AWAKE,updatePowerStateLocked算 display 请求,DisplayPowerController 把状态从 OFF 切到 ON,亮度 ramp 从 0 升到目标值。SurfaceFlinger 收到setPowerMode(ON)后开始合成。
黑屏出现在这个过程的早期:display 已经上电,但还没有可用的帧。如果此时 keyguard 窗口还没准备好,或者 SystemUI 还在冷启动,合成器就没有内容可显示。有些平台会用一张静态壁纸或者 splash 顶一下,但大多数原生流程就是黑一下。要缩短这个黑屏,关键不是调电源参数,而是让首帧更早准备好。
4.2 Keyguard 的隐藏与 bouncer 时序
亮屏后,KeyguardViewMediator会收到onStartedWakingUp,更新 keyguard 状态。如果设备没有锁屏密码,可能会直接 dismiss;如果有密码,就停留在锁屏,等用户验证。bouncer 的显示是另一条路径,涉及KeyguardSecurityContainer和KeyguardBouncer,它的加载又比普通锁屏更重。
这里常见的坑是:指纹解锁成功后,keyguard 已经 dismiss,但 bouncer 还在做退出动画,导致用户看到锁屏闪一下才进桌面。排查时要看KeyguardViewMediator的keyguardDone调用时间和StatusBarStateController的状态切换时间,两者不一致就会闪。
4.3 Doze 与 AOD 的插入
支持 AOD 的设备,灭屏后不会直接进 SCREEN_OFF,而是先进入 DOZE 或 DOZE_SUSPEND。DisplayPowerController 会把状态切到 DOZE,亮度降到很低,panel 保持低功耗刷新。这个状态下 keyguard 可能仍然 showing,只是内容变暗或者只显示时钟。
AOD 的坑主要集中在状态切换:比如 DOZE 和 OFF 之间反复跳、AOD 亮度跳变、指纹图标闪烁。原因通常是 proximity 传感器误触发、doze 的 brightness 配置不合理、或者 keyguard 在 DOZE 状态下还在更新视图。调这类问题,先看dumpsys display里的mDisplayState是不是稳定,再看dumpsys power里的 doze 相关字段。
4.4 动画与合成层的配合
锁屏的进入和退出通常带淡入淡出或者位移动画。动画由 SystemUI 的窗口动画或者 SurfaceControl 的 transaction 驱动,最终影响合成时机。如果动画时长和 display 状态切换没对齐,就会出现“动画还没结束屏幕就黑了”或者“屏幕亮了动画才开始”的割裂感。
我的做法是先把动画关掉,用settings put global window_animation_scale 0和transition_animation_scale 0排除动画干扰,确认基础链路没问题,再把动画加回来单独调。这样能快速区分是状态机问题还是动画问题。
5. 实操:抓日志、看状态、定位卡点
理论讲再多,最后还是要落到工具上。我排查这类问题一般分三步:先抓一份完整的 logcat,再用 dumpsys 看状态快照,最后用 Perfetto 看时序。三份数据对着看,基本能定位到具体模块。
5.1 必抓的日志与过滤命令
灭屏和亮屏的日志量很大,建议按 tag 过滤。我常用的组合是 PowerManagerService、DisplayPowerController、KeyguardViewMediator、WindowManager、ActivityManager、SurfaceFlinger。命令可以这样写:
adb logcat -b all -v threadtime | grep -E "PowerManagerService|DisplayPowerController|KeyguardViewMediator|WindowManager|ActivityManager|SurfaceFlinger"如果要看 SystemUI 的内部状态,还可以加上KeyguardStateController、KeyguardUpdateMonitor、NotificationShadeWindowController。注意 logcat 的 buffer 可能被冲掉,复现前先adb logcat -c清一下,抓完立刻保存。
关键日志大致长这样,时间戳是示意:
I PowerManagerService: Going to sleep due to power button (uid 1000)... I PowerManagerService: Sleeping (uid 1000)... D DisplayPowerController: Requesting power state: 0 I KeyguardViewMediator: onStartedGoingToSleep... I WindowManager: Keyguard showing...从Going to sleep到Requesting power state的间隔,反映的是电源状态机内部的耗时;从Requesting power state到实际setPowerMode的间隔,反映的是显示层的耗时;KeyguardViewMediator的日志时间点,反映的是锁屏加载的进度。
5.2 dumpsys 关键字段解读
dumpsys power我重点看这几个字段:
| 字段 | 含义 | 异常表现 |
|---|---|---|
mWakefulness | 当前睡眠状态 | 灭屏后仍为 Awake,说明状态没更新 |
mWakefulnessChanging | 状态是否在切换中 | 长时间 true,说明回调卡住 |
mDisplayReady | 显示是否就绪 | 长时间 false,说明显示层没完成 |
mHoldingDisplaySuspendBlocker | 是否持有显示锁 | 灭屏后仍为 true,可能阻止挂起 |
mScreenBrightnessOverrideFromWindowManager | 窗口层亮度覆盖 | 非 -1 可能干扰灭屏亮度 |
dumpsys window重点看 keyguard 相关:
mKeyguardShowing=true mKeyguardOccluded=false mDisplayFrozen=false如果mKeyguardShowing是 false,但用户看到锁屏,说明状态不同步;如果mKeyguardOccluded是 true,说明有 Activity 覆盖了锁屏,比如某些播放器或者相机。
dumpsys display看mDisplayState和mScreenState,确认是不是真的到了 OFF。dumpsys activity service com.android.systemui/.SystemUIService可以看 SystemUI 内部状态,包括 KeyguardViewMediator 的 showing、occluded、bouncer 状态。
5.3 用 Perfetto 看时序
日志只能看时间点,看不到线程和 CPU 调度。要看“为什么慢”,得上 Perfetto。我一般抓 10 秒左右的 trace,覆盖一次灭屏和一次亮屏,轨道开power、wm、am、sched、freq、surfaceflinger、systemui。
adb shell perfetto -o /data/misc/perfetto-traces/screen_trace -t 10s \ sched freq idle am wm power video抓完拉回本地,重点看几个区间:电源键事件到goToSleep的间隔、goToSleep到setPowerMode(OFF)的间隔、keyguard 窗口添加的时间点、SystemUI 主线程的首帧绘制。如果 SystemUI 主线程在 inflate 期间有长任务,或者 binder 调用被阻塞,trace 里一目了然。
5.4 一个完整的定位示例
之前遇到一个现象:灭屏后大约 200 毫秒,屏幕又亮了一下,然后才彻底黑。日志里goToSleep正常,DisplayPowerController也正常切了 OFF,但KeyguardViewMediator的 show 日志出现在 OFF 之后。用 Perfetto 看,setPowerMode(OFF)之后还有一次onMessageInvalidate,合成了一帧 keyguard 的初始化画面。
根因是 keyguard 的 show 被一个同步 binder 调用拖慢了,等窗口添加完成时 display 已经 OFF,但 WMS 又触发了一次事务提交,导致 panel 被短暂唤醒。处理方式是把 keyguard 的预加载提前到灭屏决策阶段,或者在 display OFF 之前加一个短等待,确保窗口事务已经 flush。改完之后闪烁消失,灭屏时间只增加了十几毫秒,用户感知不到。
6. 常见问题速查与避坑经验
这类问题翻来覆去就那几类,我把高频现象、优先排查点和处理方向整理成表,方便对着查。表格后面再补充几条只有实际踩过才知道的细节。
6.1 高频问题速查表
| 现象 | 优先看什么 | 常见根因 | 处理方向 |
|---|---|---|---|
| 灭屏慢,按了键要等一会儿才黑 | goToSleep日志时间、mWakefulnessChanging | 监听者回调卡住、动画未结束 | 查 battery stats、dream、第三方省电应用 |
| 灭屏后屏幕闪一下 | setPowerMode(OFF)与 keyguard 首帧时间 | 窗口事务在 OFF 后提交 | 提前预加载 keyguard,或延迟 OFF |
| 亮屏黑屏几百毫秒 | SystemUI 进程 pid 是否变化 | 进程冷启动、锁屏 inflate 慢 | 保活、精简布局、懒加载 |
| 锁屏不显示 | mKeyguardShowing、mKeyguardOccluded | showWhenLocked Activity 遮挡、状态不同步 | 检查窗口 flags、KeyguardController 状态 |
| AOD 闪烁或亮度跳变 | dumpsys display的 display state | doze 状态反复、亮度 ramp 冲突 | 固定 doze 亮度、关闭 ramp |
| 指纹解锁后锁屏闪一下 | keyguardDone与 bouncer 退出时间 | 状态切换不同步 | 对齐 StatusBarState 切换时序 |
6.2 灭屏闪一下:多数是窗口事务没对齐
这个现象我见过太多次,根因几乎都是 keyguard 窗口在 display OFF 之后才完成添加,WMS 又触发了一次事务,合成器把这一帧送了出去。表现就是黑一下、亮一下、再黑。排查时把setPowerMode(OFF)和 keyguard 窗口 first frame 的时间点标出来,如果后者更晚,基本就能确认。
处理方式有两种:一是把 keyguard 的 show 提前,在goToSleep决策阶段就开始准备视图,等 display OFF 时窗口已经挂好;二是在 display OFF 之前加一个短暂的等待,确保窗口事务已经 flush。第一种更治本,但改动大;第二种见效快,但会增加一点灭屏延迟。我的建议是优先做预加载,等待只作为兜底。
6.3 亮屏黑屏:先确认 SystemUI 是不是被杀了
亮屏黑屏的排查顺序应该是:先看 SystemUI 进程有没有被回收,再看 keyguard 首帧时间,最后看 display ON 的时序。如果 pid 变了,什么电源参数都不用调,直接去解决进程保活和启动优化。如果 pid 没变,再看 keyguard 的 view 是不是被 detach 后重新 attach,attach 本身也有成本。
有些定制版本为了省内存,会在灭屏后主动杀掉 SystemUI,亮屏再拉起来,结果就是每次亮屏都黑一下。这种设计在中低端机上很常见,取舍是内存和体验。如果产品定位偏体验,建议保留 SystemUI 常驻,把内存压力转移到其他地方。
6.4 锁屏不显示:不一定是加载失败
有时候锁屏“没显示”,不是没加载,而是被遮挡了。mKeyguardOccluded为 true 时,keyguard 仍然存在,只是被上面的 Activity 盖住了。常见于相机、视频播放器、支付页面。排查时先看dumpsys window里的 occluded 状态,再看顶层窗口是谁。
还有一种情况是锁屏被禁用,比如设置了“无密码直接进入桌面”,或者 trust agent 认为设备处于可信环境。这时候doKeyguardLocked会直接返回,不会 show。不要把它当成加载 bug。
6.5 定制改动的边界:别把 keyguard 当成普通窗口
Keyguard 窗口有很多特殊行为:它可能覆盖状态栏、影响 insets、阻止应用启动、参与 doze 状态。定制时如果把它当成普通全屏窗口处理,很容易踩坑。比如给它加FLAG_NOT_FOCUSABLE,bouncer 就收不到输入;给它加透明背景,下面桌面的内容会透出来;改它的 layer,可能被通知面板盖住。
我的经验是,锁屏的改动尽量走官方提供的扩展点,比如KeyguardSecurityContainer的插件、KeyguardUpdateMonitor的回调、StatusBarStateController的状态监听。直接改窗口 flags 和 layer 的,后期升级很容易冲突,而且问题很难复现。
6.6 参数调试的几条实用命令
调这类问题时,我常备几条命令。改息屏时间用settings put system screen_off_timeout 30000,单位是毫秒;关动画用settings put global window_animation_scale 0和transition_animation_scale 0;看 doze 状态用dumpsys deviceidle;看电源锁用dumpsys power里的 wake locks 段。复现前先清 log,复现后立刻 dump,避免状态被后续操作覆盖。
注意:改完参数记得恢复,尤其是动画缩放和息屏时间,不然下次排查会被自己挖的坑绊住。
我个人在实际操作中的体会是,这类问题最怕一上来就改代码。先把日志、dumpsys、trace 三份数据对齐时间轴,确认卡在哪一段,再决定改哪里。大部分“灭屏慢”“亮屏黑”的问题,根因都不在电源服务本身,而是在窗口事务和进程启动上。把 keyguard 的加载提前、把 SystemUI 的启动路径压短,往往比调一堆电源参数更有效。