☰
滑动解锁黑屏优化
2026/10/14 15:29:06 网站建设 项目流程

Android 滑动解锁概率性黑屏优化 —— SystemUI + Launcher 实践

适用场景:Android 12+ / AOSP SystemUI Keyguard 解锁动画 / Launcher3
关键词:滑动解锁、黑屏、KeyguardViewMediator、RemoteAnimation、Launcher、SurfaceFlinger
难度:中级

文章目录

  • Android 滑动解锁概率性黑屏优化 —— SystemUI + Launcher 实践
    • 一、需求背景
    • 二、整体方案
      • 2.1 模块职责
      • 2.2 问题根因(日志对比)
      • 2.3 方案思路
    • 三、SystemUI 解锁动画闸门实现
      • 3.1 文件与改动点
      • 3.2 关键代码:defer 过早 exit
      • 3.3 关键代码:修复 Launcher 等待
    • 四、Launcher 首帧就绪标志实现
      • 4.1 文件与改动点
      • 4.2 关键代码
      • 4.3 模块衔接
    • 五、关键改动文件清单
    • 六、验证建议
    • 七、总结
    • 八、参考:端到端时序

一、需求背景

从锁屏界面滑动解锁进入桌面时,概率性闪黑一下:锁屏层已经消失,但桌面/壁纸尚未完成首帧绘制,合成器中间出现“无内容可画”的空窗。

现象特点:

  • 快滑、短滑时更容易复现;
  • 同一轮操作可能第一次黑、第二次正常,具备明显竞态特征;
  • 黑屏持续时间通常在几十到一两百毫秒量级。
项说明
入口锁屏界面上滑解锁到桌面
期望行为锁屏 → 桌面过渡连续,无黑帧
适用范围无密码滑动解锁;重点覆盖解锁到 Launcher 场景

二、整体方案

2.1 模块职责

模块职责
SystemUIKeyguardUnlockAnimationController根据滑动 dismiss 进度决定何时显示背后 Surface、何时真正结束 Keyguard
SystemUIKeyguardViewMediator发起keyguardGoingAway,在 RemoteAnimation 结束后 hide Keyguard
WindowManager / Shell创建 TO_BACK Transition,回调handleStartKeyguardExitAnimation
Launcher3从STOPPED恢复并完成首帧绘制;通过 Settings 标记“桌面已就绪”

2.2 问题根因(日志对比)

异常解锁(出现黑屏)典型时序:

keyguardGoingAway ↓(约 30ms) exitKeyguard(此时 RemoteAnimation 尚未 start) ↓ hide Keyguard + Wallpaper Surface hide ↓ Launcher onStart / Start draw(仍未就绪) ↓ 中间约 90~170ms:锁屏没了、壁纸没了、Launcher 未画完 → 闪黑 ↓ handleStartKeyguardExitAnimation(已晚)

正常解锁时序:

keyguardGoingAway ↓ Launcher HAS_DRAWN ↓ handleStartKeyguardExitAnimation / fadeInSurfaceBehind ↓ 再 exitKeyguard / hide Keyguard

关键结论:

  1. 滑动 dismiss 量很快超过DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD(0.3)时,会在surface-behind RemoteAnimation 尚未 running时就调用exitKeyguard;
  2. 既有的launcher_oncreated等待逻辑存在缺陷:Settings 只读一次且不重读,Launcher 又只在onCreate置位,灭屏onStop后标志仍为 1,等待形同虚设;
  3. Keyguard 提前隐藏后,Wallpaper 也会短暂Surface hide,进一步放大黑帧。

2.3 方案思路

采用“主路径闸门 + 就绪等待”双保险:

滑动 dismiss ≥ 0.3 ↓ 【闸门】surface-behind RemoteAnimation 是否已 running? ├─ 否 → 暂不 exit,等 notifyStartSurfaceBehindRemoteAnimation 再判定 └─ 是 → 进入 exitKeyguard ↓ 【等待】解锁到 Launcher 且 launcher_oncreated==0? ├─ 是 → 轮询(最多 800ms)直到首帧置 1 └─ 否 → 立即 hide Keyguard

设计取舍:

  • 优先保证无黑帧,允许 Keyguard 多停留几十毫秒;
  • 解锁到后台 App 时,不因 Launcher 标志为 0 而额外等待;
  • 不修改 WM 合成策略,只在 SystemUI / Launcher 侧对齐时序。

三、SystemUI 解锁动画闸门实现

3.1 文件与改动点

文件改动类型关键点作用
KeyguardUnlockAnimationController.kt修改finishKeyguardExitRemoteAnimationIfReachThreshold()dismiss 过阈值但动画未 running 时 defer exit
KeyguardViewMediator.java修改exitKeyguardAndFinishSurfaceBehindRemoteAnimation()修复并收紧 Launcher 就绪等待

上下游关系:

  • 滑动过程中onKeyguardDismissAmountChanged()会反复调用阈值判断;
  • showSurfaceBehindKeyguard()后,WM 回调notifyStartSurfaceBehindRemoteAnimation();
  • 该方法末尾在非 canned 动画路径会再次调用阈值判断,从而接住此前被 defer 的 exit。

3.2 关键代码:defer 过早 exit

路径:frameworks/base/packages/SystemUI/.../KeyguardUnlockAnimationController.kt

valdismissAmount=keyguardStateController.dismissAmountif(dismissAmount>=1f||(keyguardStateController.isDismissingFromSwipe&&!keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture&&dismissAmount>=DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)){// Swipe can cross EXIT threshold before WM starts the surface-behind remote// animation. Hiding keyguard then leaves a gap until Launcher/wallpaper draws.// Defer exit; notifyStartSurfaceBehindRemoteAnimation will call this again.if(!keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehind){Log.d(TAG,"finishKeyguardExitRemoteAnimationIfReachThreshold: "+"surface behind animation not running yet, defer exit",)return}setSurfaceBehindAppearAmount(1f)dismissAmountThresholdsReached=truekeyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false/* cancelled */)}

逻辑说明:

  1. 触发:滑动 dismiss 达到退出阈值(默认 0.3)或完全 dismiss;
  2. 新增判断:仅当isAnimatingBetweenKeyguardAndSurfaceBehind == true(即handleStartKeyguardExitAnimation已把mSurfaceBehindRemoteAnimationRunning置 true)才真正 exit;
  3. 为何不置dismissAmountThresholdsReached:defer 时保持 false,以便动画开始后或取消滑动时仍可继续走正常分支;
  4. 衔接:notifyStartSurfaceBehindRemoteAnimation()结束时会再次调用本方法,形成闭环。

3.3 关键代码:修复 Launcher 等待

路径:frameworks/base/packages/SystemUI/.../KeyguardViewMediator.java

原实现问题:

  • launcher_oncreated在 Runnable 外只读一次,轮询时值不会更新;
  • 只要初始值不是 0(常见是 1),会立刻 hide Keyguard。

优化后:

finallongstartTime=System.currentTimeMillis();finalRunnablecheckLauncherCodeRunnable=newRunnable(){@Overridepublicvoidrun(){// Re-read each pollintlauncherFinishKeycode=Settings.Global.getInt(mContext.getContentResolver(),LAUNCHER_ONCREATED,-1);Log.d(TAG,"launcher_finsih_keycode:"+launcherFinishKeycode);// Only wait when unlocking to Launcher and it has not drawn yet (0).booleanneedWaitLauncher=launcherFinishKeycode==0&&mKeyguardUnlockAnimationControllerLazy.get().isSupportedLauncherUnderneath();if(needWaitLauncher&&System.currentTimeMillis()-startTime<800){mHandler.postDelayed(this,50);}else{onKeyguardExitFinished("exitKeyguardAndFinishSurfaceBehindRemoteAnimation");if(mKeyguardStateController.isDismissingFromSwipe()||wasShowing){mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();}finishSurfaceBehindRemoteAnimation(showKeyguard);mUpdateMonitor.dispatchKeyguardDismissAnimationFinished();}}};mHandler.post(checkLauncherCodeRunnable);

逻辑说明:

  1. 每次轮询重读Settings.Global,避免捕获到过期值;
  2. 仅解锁到 Launcher且标志为 0 才等待,避免解锁回 App 被拖慢;
  3. 超时仍为800ms,Launcher 异常时不会永久卡在锁屏;
  4. 轮询间隔改为50ms,首帧就绪后更快放行。

四、Launcher 首帧就绪标志实现

4.1 文件与改动点

文件改动类型关键点作用
Launcher.java修改onStop/onStart/onCreate/onDestroy用“可见首帧”表达桌面就绪
Launcher.java新增markLauncherDrawnReadyAfterNextDraw()下一帧onDraw后写 Settings=1

launcher_oncreated语义调整为:

值含义
0未就绪(onCreate开始 /onStop)
1已产生可见帧(首帧onDraw)
-2ActivityonDestroy

4.2 关键代码

路径:packages/apps/Launcher3/.../Launcher.java

生命周期侧:

@OverrideprotectedvoidonStop(){super.onStop();// Clear ready flag so unlock waits for the next visible frame after restart.Settings.Global.putInt(getContentResolver(),LAUNCHER_ONCREATED,0);// ...}@OverrideprotectedvoidonStart(){// ...super.onStart();markLauncherDrawnReadyAfterNextDraw();// ...}

onCreate末尾不再直接写1,改为同样走首帧回调:

markLauncherDrawnReadyAfterNextDraw();

首帧置位实现(精简):

privatevoidmarkLauncherDrawnReadyAfterNextDraw(){finalViewroot=getRootView();if(root==null){return;}clearLauncherReadyDrawListener();mLauncherReadyDrawListener=newOnDrawListener(){@OverridepublicvoidonDraw(){Settings.Global.putInt(getContentResolver(),LAUNCHER_ONCREATED,1);finalOnDrawListenerlistener=this;root.post(()->{try{ViewTreeObserverobserver=root.getViewTreeObserver();if(observer.isAlive()){observer.removeOnDrawListener(listener);}}catch(IllegalStateExceptione){// ViewTreeObserver may already be dead during teardown.}if(mLauncherReadyDrawListener==listener){mLauncherReadyDrawListener=null;}});}};ViewTreeObserverobserver=root.getViewTreeObserver();if(observer.isAlive()){observer.addOnDrawListener(mLauncherReadyDrawListener);}else{mLauncherReadyDrawListener=null;Settings.Global.putInt(getContentResolver(),LAUNCHER_ONCREATED,1);}}

逻辑说明:

  1. 灭屏进入onStop后标志清零,下次解锁必须等新的可见帧;
  2. onStart/onCreate注册一次性OnDrawListener,首帧后写 1 并移除监听;
  3. onDestroy清理监听,避免泄漏;
  4. 与 SystemUI 等待逻辑通过Settings.Global("launcher_oncreated")衔接。

4.3 模块衔接

Launcher.onStop → Settings=0 ↓ 亮屏滑动解锁 SystemUI showSurfaceBehindKeyguard / keyguardGoingAway ↓ WM Transition ready,RemoteAnimation running ↓ SystemUI 允许 exitKeyguard ↓ 若 Settings==0 且底下是 Launcher → 轮询等待 ↓ Launcher.onStart → 首帧 onDraw → Settings=1 ↓ SystemUI hide Keyguard(此时桌面已有内容)

五、关键改动文件清单

层级文件改动类型作用
SystemUI.../keyguard/KeyguardUnlockAnimationController.kt修改动画未 running 时 defer exit
SystemUI.../keyguard/KeyguardViewMediator.java修改重读 Settings + 仅解锁到桌面时等待
Launcher3.../launcher3/Launcher.java修改onStop 清零、首帧置位就绪标志

可选说明:

曾考虑的改动最终未采用原因
直接关闭/拉长 unlock 动画 duration影响全局解锁手感,且不能解决“锁屏先藏”的根因
无条件等待launcher_oncreated==0会拖慢解锁回 App 的路径

六、验证建议

  1. 快滑解锁到桌面:灭屏 → 亮屏 → 快速上滑,反复 20+ 次,预期无黑闪;日志可看到surface behind animation not running yet, defer exit,以及launcher_finsih_keycode从 0 变为 1。
  2. 慢滑 / 正常滑:过渡自然,无额外卡顿感。
  3. 滑一半取消:上滑到半屏松手回弹,应回到锁屏,不卡死。
  4. 解锁回后台 App:打开 App → 灭屏 → 解锁,应无明显额外延迟。
  5. 指纹/人脸解锁到桌面:冒烟通过即可(本方案主要作用于滑动阈值路径)。

七、总结

本次优化的核心是:不要在 surface-behind RemoteAnimation 真正开始前隐藏 Keyguard,并用“Launcher 可见首帧”作为第二道就绪信号。通过 SystemUI 闸门 + Launcher Settings 标志联动,消除滑动解锁到桌面时的概率性黑帧。


八、参考:端到端时序

用户上滑 ↓ dismiss ≥ 0.15 → showSurfaceBehindKeyguard → keyguardGoingAway ↓ dismiss ≥ 0.3 ├─ animation 未 running → defer exit(关键) └─ animation 已 running → exitKeyguard ↓ 等待 Launcher 首帧(仅解锁到桌面) ↓ hide Keyguard / wallpaper 过渡 ↓ 桌面稳定显示

如有问题欢迎留言交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询