adb shell dumpsys window windows > window.txt
过滤 has_drawn

mDrawState是 Android 窗口管理服务(WMS)中,用来描述窗口绘制状态的关键变量,它有几个典型值:
NO_SURFACE:窗口还没有创建 SurfaceCOMMIT_DRAW_PENDING:窗口绘制请求已提交,但还没完成READY_TO_SHOW:绘制已提交,Surface 还没显示HAS_DRAWN:窗口首次绘制完成,且内容已经成功上屏显示,处于稳定可见状态
窗口还没画出来(HAS_DRAWN 状态为 false)
窗口已经画出来了(HAS_DRAWN 状态为 true)
怎么看哪个窗口在最顶端
| 字段 | 含义 | 作用 |
|---|---|---|
layer |
窗口的 Z 轴层级值 | 越大越靠上,同一屏幕上层级最高的窗口就是最顶端的 |
type |
窗口类型(Window Type) | 系统窗口(如悬浮窗、系统弹窗)默认层级高于普通应用窗口 |
isOnScreen / visible |
窗口是否可见且在屏幕上 | 只有同时为 true 的窗口,才会实际参与显示层级排序 |
二、结合你的日志,教你一步步定位最顶端窗口
我们用你截图里的日志举例,手把手拆解:
1. 先看 layer 值:谁的数字大,谁就在最上面
你日志里的这一行:
Surface: shown=true layer=0 alpha=1.0 rect=(0.0,0.0) transform=(1.0, 0.0, 0.0, 0.0, 1.0)
规则:
- 日志里所有窗口的
layer值,数值越大,Z 轴越靠上,显示优先级越高。 - 比如 A 窗口
layer=10,B 窗口layer=5,那么 A 窗口一定在 B 窗口的上面。
注意:部分日志会用负数表示层级(比如
layer=-1表示在底层),但核心逻辑不变:数值越大越靠上。
2. 再看窗口类型 type:特殊窗口天生优先级更高
你日志里的关键信息:
这里的 ty=APPLICATION_OVERLAY(即 TYPE_APPLICATION_OVERLAY)是悬浮窗 / 系统覆盖窗口类型 ,它的层级天生就高于普通的 APPLICATION 类型窗口,哪怕 layer 值看起来不大,也会盖在普通应用窗口上面。
常见窗口类型优先级(从高到低):
- 系统级窗口(如状态栏、系统弹窗、Toast)
APPLICATION_OVERLAY悬浮窗 / 系统警告窗口- 普通应用的 Activity 窗口
- 应用内部的 Dialog/Toast
3. 排除不可见窗口:只看 shown=true + isOnScreen=true 的
日志里这两个字段:
cpp
Surface: shown=true
isOnScreen=true
isVisible=true
三、给你一个通用的排查步骤
- 过滤有效窗口 :先筛出所有
shown=true、isOnScreen=true、isVisible=true的窗口,排除不可见的。 - 按
layer排序 :把这些窗口按layer值从大到小排序,第一个就是当前层级最高的窗口。 - 结合窗口类型二次确认 :如果有
APPLICATION_OVERLAY、系统窗口类型,优先级默认高于普通应用窗口,哪怕layer值相同,也会盖在上面。 - 看
zOrder字段(部分日志有) :部分 WMS 日志会直接输出zOrder,值越大越靠上,直接看最大值即可。
还可以看
adb shell dumpsys SurfaceFlinger > Surface
过滤 focesed

一、先搞懂 dumpsys SurfaceFlinger 输出的核心结构
这个命令输出的日志,核心是SurfaceFlinger(Android 的屏幕合成服务)当前的图层列表,决定了屏幕上每个窗口的绘制顺序和最终显示效果。
关键信息分为两部分:
- 图层列表(Layers):每个 Surface(窗口)的层级、状态、大小、透明度
- 合成状态:是否在刷新、是否合成成功,直接关联冻屏问题
二、结合你的日志,手把手分析
你的截图里就是典型的 SurfaceFlinger 图层列表,我们一行行拆解:
1. 每一行代表一个图层(Surface)
格式是固定的:rel 0 | [PID] | CLIENT | 0 | [视口/大小] | [位置/透明度] | [标记]
举你日志里的例子:
cpp
com.android.launcher3/...QuickstepLauncher#363
rel 0 | 1 | CLIENT | 0 | 0 1440 2960 | 0.0 0.0 1440.0 2960.0 | []
com.android.launcher3/...:图层所属的包名 / 窗口,这里是桌面 Launcher#363:图层的唯一 ID,后续可以用它关联 WMS 日志rel 0:相对层级(rel= relative z-order),数字越大越靠上1:进程 PID,Launcher 的进程号是 1(system_server 进程)0 1440 2960:图层的大小,这里是 1440×2960(全屏桌面)0.0 0.0 1440.0 2960.0:图层在屏幕上的位置(从 (0,0) 到 (1440,2960),全屏)[]:图层标记,空表示正常,[*]表示该图层是当前的 "脏区域"(需要刷新)
2. 关键判断点:谁在最顶端?
看两个核心字段:
(1)rel(相对层级)
rel 就是图层的 Z 轴顺序,数值越大,越靠上。你日志里的图层:
- Launcher:
rel 0 - 你的 App
Sys2038:com.example.mysystemdialog.MainActivity#361:rel 0 - 你的 App
Sys2038:com.example.mysystemdialog.MainActivity#372:rel 0 - 状态栏
StatusBar#80:rel 0 - 导航栏
NavigationBar0#73:rel 0
⚠️ 这里的 rel 0 不代表层级相同,因为 dumpsys SurfaceFlinger 输出的图层列表是从上到下排序的!
- 日志里越靠下的行,层级越高,越在屏幕上方。
- 你日志里的顺序:
Launcher → App#361 → App#372 → StatusBar → NavigationBar所以层级从低到高是:Launcher < App#361 < App#372 < StatusBar < NavigationBar也就是NavigationBar(导航栏)在最顶端,其次是状态栏,然后是你的两个 App 窗口。
(2)[*] 标记
你日志里 App#372 这一行:
这里的 [*] 表示:这个图层是当前需要刷新的脏区域 ,也就是 SurfaceFlinger 正在处理的图层。如果冻屏时,只有这个图层一直带 [*] 但没有后续刷新,说明:
- 你的 App 提交了刷新请求,但 SurfaceFlinger 没有合成成功,或者 App 没有继续提交新的帧。
3. 结合冻屏问题,重点看这几个状态
(1)图层是否存在且显示
- 如果你的 App 图层不在列表里,说明窗口已经被销毁,冻屏和它无关。
- 如果图层存在,但
alpha=0或visible=false(部分版本日志会显示),说明窗口被隐藏,也不会导致冻屏。
(2)是否有新的帧提交
正常情况下,SurfaceFlinger 每 16ms 会刷新一次图层列表。如果冻屏时,你的 App 图层的位置、大小、标记一直不变,说明:
- App 主线程阻塞,无法生成新的帧
- App 的 Surface 被意外销毁,无法提交新的 Buffer
- SurfaceFlinger 合成卡住,没有把新的帧刷到屏幕上
过滤掉 无法触摸页面
