Winscope、Layout Inspector 与 UI 状态调试
排查窗口显示、转场、输入这类问题时,我们常常需要把系统各服务的状态放到同一条时间轴上对照。Winscope 就是 AOSP 提供的系统状态录制、回放和分析工具,它把 WindowManager(WMS,窗口管理服务)、SurfaceFlinger(SF,系统合成器)、Shell transition、SurfaceControl.Transaction、Input、IME、ProtoLog 和 ViewCapture 等数据放到同一时间轴。这几个名字后文会反复出现:Shell transition 是系统窗口转场状态机,SurfaceControl.Transaction 把一组 layer 属性修改放进同一次提交,ProtoLog 承担 Android 系统服务的结构化日志,ViewCapture 用来记录受支持系统窗口的 View 属性。
两者分工明确:Winscope 观察 WindowManager、SurfaceFlinger 和输入状态随时间变化,适合窗口与合成时序问题;Layout Inspector 观察应用当前的 View 或 Compose 结构,适合布局层级和属性问题。本文前半部分讲 Winscope 记录的系统状态时间线,后半部分讲应用内部的 View 与 Compose 结构检查。
具体到排障,Winscope 适合回答这些问题:目标窗口是否进入可见状态,目标 layer 是否带 buffer,哪一层遮住了它,哪笔 transaction 修改了位置或透明度,转场参与者是否被合并或中止,触摸区域与焦点是否匹配。
本文沿用 trace 与源码里的英文名:layer 是 SurfaceFlinger 管理的合成节点,buffer 是 Producer 提交的一帧像素数据,transaction 是一批状态修改,fence 是跨 CPU、GPU 和显示硬件协调读写完成时间的同步对象,present 在这里指显示栈的呈现动作与反馈;present fence 是系统时间基准,光学上像素点亮的时刻要另行测量。
源码基线:AOSP
android-17.0.0_r1;涉及 fence 或显示驱动等待时,kernel 参考android17-6.18-2026-06_r6。
Winscope 提供的是系统状态证据;至于 CPU 调度、Binder(Android 跨进程调用机制)、RenderThread(HWUI 渲染线程)、GPU 工作、fence 等待和 present 时延,则交给 Perfetto(系统 trace 采集与分析工具)、AGI(Android GPU Inspector)与设备侧图形轨迹去解释。
窗口、Layer 与输入状态时间线
1. 先明确 Winscope 记录了什么
WindowManager 和 SurfaceFlinger 描述的是同一块屏幕的不同层次,Winscope 把这两层状态放到同一条时间轴上,供我们逐帧对照。先看 WindowManager 一侧:它维护 Display(逻辑显示设备)、DisplayArea(显示区域)、Task、Activity、WindowToken(窗口身份与归属的令牌)和 WindowState 等容器,窗口生命周期、焦点、Insets(状态栏、导航栏等占用或避让区域)、方向、转场与几何都由它们控制。
应用、解码器或相机等 Producer(画面生产者)通过 Surface 提交 buffer;layer 的位置、裁剪、层级和显隐,则由 WindowManager 和 WM Shell(负责系统窗口行为和动画的进程组件) 用 SurfaceControl.Transaction 修改,同一笔有效 transaction 的状态修改按原子事务提交。这个原子性只覆盖状态应用本身:相关 buffer 的写入 fence 完成与否、多个 Display 是否在同一光学时刻更新,都要另行确认。SurfaceFlinger 再把这些状态整理成当前时刻的 layer snapshot(图层状态快照),交给 CompositionEngine 选择合成策略,最后经 HWC(Hardware Composer,硬件合成接口) 送到显示硬件。

图:WM/Shell提交几何状态,Producer提交buffer,两条输入在SF状态模型中汇合。 原子状态事务只管状态应用,写入fence完成与否、多Display是否同一光学时刻更新要另行核对。
Winscope 中的主要证据如下。
| 数据源 | 记录内容 | 能确认什么 | 仍需其它证据的问题 |
|---|---|---|---|
| WindowManager | WindowContainer 层级、bounds(矩形边界)、visibility、focus、orientation、Insets 等 | 窗口与 Task 是否处于预期逻辑状态 | 应用是否及时提交新像素,某段代码为何耗时 |
| SurfaceFlinger layers | 每个 snapshot 的 layer 层级、buffer、requested/calculated geometry(请求值/计算值)、可见区域、输入区域等 | layer 在该状态下能否参与显示,父层与遮挡关系是否正确 | buffer 内的像素是否正确,present 是否按目标时限完成 |
| SurfaceFlinger transactions | 初始状态与提交到 SF 的原子状态变化 | 哪个 PID/UID(进程/用户标识)、transaction id、layer id 改了属性 | 发起方为何晚提交,某次 GPU 工作何时结束 |
| Shell transitions | transition id、类型、flags、目标 leash/window、起止 bounds、开始/结束 transaction 与时间戳 | 启动、返回、旋转、分屏、PiP、Recents 的系统转场状态;leash 是转场期间承载动画的临时父 layer | App 内部动画每帧的绘制成本 |
| Input / IME | 输入事件链与输入法(Input Method Editor)状态 | 事件是否进入系统、在哪一段停住,IME 状态是否一致 | 应用回调内部为何阻塞 |
| ViewCapture | 支持该能力的系统窗口 View 属性 | SystemUI、Launcher 等受支持窗口的 View 位移、alpha(透明度)、可见性 | 任意第三方应用的完整 View 树 |
| Screen recording / screenshot | 用户能看到的画面 | 把状态时间点与视觉现象对齐 | 窗口树、layer 树或 transaction 的系统状态证据 |
这些状态写进 Perfetto 时各有注册路径:WMS 一侧由 WindowTracingPerfetto 注册 android.windowmanager,按配置把 WindowManagerService 状态序列化到 Perfetto;SurfaceFlinger 一侧,LayerDataSource 注册 android.surfaceflinger.layers,TransactionDataSource 注册 android.surfaceflinger.transactions。
LayerTracing 有两种模式,区别在于 snapshot 从哪来:active 模式在采集期间直接写 snapshot,记录运行时状态序列;generated 模式根据 transaction ring buffer 的记录重建 snapshot。ring buffer 是容量固定的循环缓冲区,写满后会覆盖最旧记录。所以排查本地一两帧错位时,我们优先采短时 active trace;要在bugreport中保留故障前历史时,可评估generated bugreport模式:平时只保留transaction ring buffer,生成snapshot时仍要消耗CPU和内存,采集期开销低,重建阶段的成本照样存在。
2. 读 layer tree 前先判断出图拓扑
读 layer tree 之前,我们要先判断“出图拓扑”:像素从哪里产生、经过哪些 Surface、最终对应哪些 SF layer。同一张画面,在 SurfaceFlinger 里可能只有一个 App Window buffer layer,也可能包含宿主窗口加多个独立内容 layer。跳过这一步,很容易把“没找到视频 layer”误判成“视频没有提交”。
按内容最终进入哪个 Surface,出图可以分为四类。
标准 View / Compose 窗口
普通 View 与纯 Compose 内容由宿主 ViewRoot/HWUI 生成 App Window buffer;HWUI 是 Android 的硬件加速 UI 渲染器,UI线程在traversal/draw中更新View的RenderNode display list,RenderThread再处理渲染和GPU提交;具体耗时仍按trace中的线程和阶段区分。因此 SurfaceFlinger 通常只看到宿主窗口一个 layer,某个按钮、Compose node 或 TextureView 子区域都不会各自成为可见的 SF layer。我们用 Winscope 验证的是窗口与宿主 layer 的 bounds(布局边界)、buffer 和遮挡关系;宿主内部每个 UI 节点长什么样,要靠后文的 Layout Inspector。
SurfaceView 与独立 Surface
SurfaceView 的主体内容走独立的 buffer stream(连续提交的图像缓冲序列)。它牵出的常见对象关系包括 SurfaceView container(承载几何和层级的容器层)、BLAST buffer child(接收内容 buffer 的子层)和按条件显示的背景 color layer;BLAST 是 Buffer Layer Asynchronous Surface Transaction 的缩写,它让 buffer 更新与 SurfaceControl.Transaction 协同提交。几何、crop(裁剪区域)、显隐和相对 Z 主要落在 container(承载几何的容器层) 上,frame number 与内容 buffer 落在 BLAST child 上。
container 的状态进入 SF snapshot,说明的只是几何或层级已经生效;BLAST child 的新 buffer 有没有被同一个 display frame 采用,要选中 child 才知道。只看 container 就宣布“有 buffer”,结论会下错。
宿主 App Window 与 SurfaceView 内容各有自己的 buffer 更新和归还节奏:acquire fence(buffer 可被消费者读取的同步信号) 表示消费者何时可以读取 buffer,release fence 表示生产者何时可以安全复用它。黑屏或一帧错位时,我们要沿下面几项分别检查,hole-punch(在宿主窗口中留出透明区域,让下方 SurfaceView 内容露出)就排在第一项。
- 宿主是否正确生成 hole-punch 或覆盖 UI;
- container 的 requested/calculated geometry 是否一致;
- BLAST child 是否收到新 buffer,frame number 是否推进;
- parent、relative Z(相对另一 layer 的层级)、crop、alpha 与 visible region 是否匹配;
- 宿主 geometry transaction 与内容 buffer 是否在预期 display frame 生效。
TextureView
TextureView 的外部 buffer 先交给应用进程内的 SurfaceTexture queue(纹理缓冲队列),由 HWUI 采样进宿主 App Window buffer,所以 SurfaceFlinger 通常看不到独立的 TextureView 可见 layer。TextureView 黑屏时,Winscope 最多能证明宿主窗口有没有提交和显示;外部 SurfaceTexture queue(缓冲队列)、宿主 RenderThread 采样和纹理内容,要换成应用、Perfetto或对应图形API采集工具侧的线索去查;AGI的GLES帧捕获需要明确的ANGLE转译路径,能覆盖哪些原生GLES调用以这条转译路径为准。
多窗口、PiP、分屏与多 Display
分析顺序是 Display → Window/Task → SF layer/leash → 内容 Producer。屏幕上两个 pane(并排区域)可能只是同一 Window 内的普通 View,也可能是两个 Task 或两个顶层 Window;layer 偏多时,还要想到壁纸、系统栏、IME、dim layer(压暗背景的图层)或 transition leash。
多 Display 场景要分开记录 displayId(逻辑显示设备编号)、目标窗口、可见 layer 集、刷新率、color mode 与 present 时间。一个进程可以在多个 Display 上有窗口,而每个 Display 都有自己的帧与 present 时间线,两个 Display 在相近时刻的 present 要分开解释。
3. Winscope 面板与源码对象对照
Winscope 报告里的对象,可以回到下面这些源码入口对照。
| 面板或字段 | Android 17 源码入口 | 阅读重点 |
|---|---|---|
| WM hierarchy | Window、Display、Task、Activity、Window | 逻辑 parent、display、bounds、visibility、focus、Insets |
| WM Perfetto trace | Window、Window | LOG_、LOG_ 与 WMS dump 序列化 |
| Shell transition | Transition、system_server 与 WM Shell 各自的 Perfetto、Transition | sync/transition id、target leash layer id、start/finish transaction、handler(处理器)分派、merge(转场合并)、finish/abort(完成/中止) |
| Surface transaction | Surface、Surface | position、matrix、crop、alpha、layer、reparent、buffer 与 transaction id |
| SF runtime state | Requested、Layer、Layer | transaction 状态怎样变成当前 snapshot,父层属性怎样影响计算结果 |
| SF layer trace | Layer、Layer | active snapshot 与 generated snapshot 的来源 |
| SF transaction trace | Transaction、Transaction | continuous ring buffer、active 逐笔写入、PID/UID/layer/vsync id |
固定 tag 的入口如下:
- WMS
WindowTracingDataSource.java - WMS
PerfettoTransitionTracer.java - WM Shell
PerfettoTransitionTracer.java - Framework
TransitionDataSource.java - Framework
SurfaceControl.java - SurfaceFlinger FrontEnd
- SurfaceFlinger tracing
读 Winscope 报告卡在某个字段上时,这些源码能解释字段来源与状态流转。如果还不熟悉SF状态模型,可先读 SurfaceFlinger FrontEnd 与 RequestedLayerState。
还有一条现实限制:HWC plane(显示控制器可直接扫描输出的硬件图层) 分配、DPU(Display Processing Unit,显示处理器)限制、GPU driver 和 protected display path(受保护内容的安全显示链路),通常位于 vendor(设备厂商或芯片厂商) 实现中,AOSP trace 只能提供公共框架侧线索。
4. 版本与采集入口
Android 15—17
Android 15 起,各类 Winscope trace 接入 Perfetto:每种 trace 都是一个独立的 data source(Perfetto 可单独启停的数据采集模块),可以在同一个 tracing session 里组合:
android.windowmanagerandroid.protologandroid.input.inputeventandroid.surfaceflinger.layersandroid.surfaceflinger.transactionscom.android.wm.shell.transitionandroid.inputmethodandroid.viewcapture
这套 data source 在 Android 17 上继续沿用。平台 tag 更新到新版本,viewer 字段在厂商构建上也未必完整:产品裁剪、权限和 trace flag(开关值)都会影响数据。
Android 14 及更早版本
旧版本使用各自的命令和 /data/misc/wmtrace 文件,例如 wm tracing start/stop。这些 trace 可以继续用 Winscope 加载,但配置名、字段和采集方式要按目标系统版本解释。RequestedLayerState 是 SF FrontEnd 接收的 layer 请求状态,LayerSnapshot(快照) 是计算父子继承、裁剪和可见性后的快照;Android 17 的这套对象模型与旧实现的内部对象并不一一对应。
网页端与命令行
Winscope Web UI 支持 Winscope Proxy 和 Web Device Proxy。前者运行本机Python代理并通过adb连接设备;后者也要求本机安装并运行Web Device Proxy daemon,浏览器在获得origin和device授权之后才能连接设备。当前官方指南(更新于2026-10-09)要求Winscope Proxy使用Python3.10+与adb,仍注明Web Device Proxy不支持macOS。命令行采集面向userdebug/eng构建,需确认adbd已实际取得root;普通量产user build上这套入口是否可用,要单独验证。
5. 一份短时、可复现的采集配置
下面的配置面向 15 秒本地窗口转场复现,一次采齐 WM frame snapshot、SF active layer snapshot、SF active transaction 和 Shell transition。注意 TRACE_FLAG_BUFFERS 是 SF layer trace 的采集开关,负责捕获没有几何变化时的 buffer 更新;少了它,画面内容更新可能不会产生新的 layer snapshot。
#!/usr/bin/env bash
set -euo pipefail
adb root
adb wait-for-device
winscope_uid=$(adb shell id -u)
winscope_uid=${winscope_uid//$'\r'/}
[[ "$winscope_uid" == 0 ]] || { printf '%s\n' 'adbd未取得root,停止采集' >&2; exit 1; }
adb shell perfetto \
-c - --txt \
-o /data/misc/perfetto-traces/winscope_window_debug.perfetto-trace <<'EOF'
duration_ms: 15000
unique_session_name: "winscope_window_sf_debug"
buffers: {
size_kb: 65536
fill_policy: RING_BUFFER
}
data_sources: {
config {
name: "android.windowmanager"
windowmanager_config: {
log_level: LOG_LEVEL_DEBUG
log_frequency: LOG_FREQUENCY_FRAME
}
}
}
data_sources: {
config {
name: "android.surfaceflinger.layers"
surfaceflinger_layers_config: {
mode: MODE_ACTIVE
trace_flags: TRACE_FLAG_INPUT
trace_flags: TRACE_FLAG_COMPOSITION
trace_flags: TRACE_FLAG_BUFFERS
}
}
}
data_sources: {
config {
name: "android.surfaceflinger.transactions"
surfaceflinger_transactions_config: {
mode: MODE_ACTIVE
}
}
}
data_sources: {
config {
name: "com.android.wm.shell.transition"
}
}
EOF
adb pull /data/misc/perfetto-traces/winscope_window_debug.perfetto-trace .
配置启动后复现问题,15 秒结束时 trace 落到设备文件,最后一条命令把它拉回当前目录。MODE_ACTIVE 会写入采集期间的初始状态和后续变化,适合短时稳定复现;SF layer active 模式计算开销较高,我们不用它做长时间的性能测量。
问题只涉及当前静态状态时改用 dump(单个状态快照);要在 bugreport 中保留故障前历史时,可评估 MODE_GENERATED_BUGREPORT_ONLY 和 transaction continuous ring buffer。continuous 模式持续维护循环缓冲区,到 flush(请求输出当前缓冲内容) 或 bugreport 时输出其中记录。 固定A17的Perfetto单快照实现还要留意实际触发时刻:SF layer的MODE_DUMP和WMS的LOG_FREQUENCY_SINGLE_DUMP都在停止回调阶段安排快照,实际时刻以这里的实现为准,proto注释容易让人读成启动瞬间;本文后面的dumpsys静态命令是另一条独立入口。
默认配置没有启用 TRACE_FLAG_EXTRA、TRACE_FLAG_HWC、WindowManager verbose、ProtoLog stacktrace(调用栈)和全量 input event。TRACE_FLAG_EXTRA 增加额外 layer 元数据,TRACE_FLAG_HWC 增加非结构化的 HWC 信息,这些选项会明显增加内存、运行开销或隐私风险,所以只有当前问题需要对应字段时,才在短时本地 trace 中启用。
官方把 LOG_FREQUENCY_FRAME 描述为每帧状态,把 LOG_FREQUENCY_TRANSACTION 描述为每次WMS transaction commit状态。固定A17实现中,WindowTracing.logState() 在transaction频率下立即调用 log(where);frame频率则合并多次请求,只安排一次Choreographer frame callback后采样,这个采样点和每个显示刷新的自动dump、SF的commit或present时刻都是两回事。未指定配置时,A17实现映射到DEBUG对应的TRIM级别和FRAME频率;显式保存配置便于复查。
6. 从录像到窗口、layer 与 transaction
一轮分析,我们按七个检查点走。
6.1 锁定 Display 与时间点
先记录异常发生的 displayId 和时间戳。Screen recording 可与 trace 同采;screenrecord 版本达到 1.4 才支持多 Display,旧版本只记录单 Display。视频帧帮我们定位现象,state snapshot 描述当时的窗口与 layer 状态,两者要配合起来用。
6.2 在 WindowManager 找逻辑对象
接下来在 WindowManager 一侧找逻辑对象:用 title、token、parent token、Task、Activity、WindowState、display、bounds、is_visible 与 focused window 锁定目标。token 是对象身份标识,parent token 用于确认父子归属;relayout 是应用与 WMS 重新协商窗口几何和 Surface 状态的过程。WM 中还不可见时,继续检查 Activity/Task 状态、transition、Insets、relayout 和焦点分配;WM 中可见,也只说明窗口管理状态满足条件,SF 那边有没有可显示 buffer 还要另看。
6.3 按出图类型找 SF 对象
然后按出图类型找 SF 对象。标准 View/Compose 或 TextureView 页面从宿主 App Window layer 开始;SurfaceView、视频、相机、游戏或自研 EGL/Vulkan 输出,要继续找独立的 content/BLAST layer,EGL 是连接 OpenGL ES 等渲染 API 与窗口 Surface 的接口。PiP(Picture-in-Picture,画中画)、分屏和桌面窗口还要记录 task leash、transition leash、caption(窗口标题栏)、dim、wallpaper、IME 和 SystemUI layer。
layer 名称只是检索入口,确认对象要结合 owner PID/UID、layer id、parent、buffer、frame number(该 buffer 流中的帧序号)、bounds 和 transaction,避免把同名旧 layer 或 container 当成当前内容 layer。
6.4 解释 SurfaceFlinger 可见性
Winscope 的 rects view(矩形视图) 用 layer 的 bounds、z-order(前后层级)、opacity(是否不透明)、relative Z 和圆角画出屏幕占位。SF viewer的 V chip表示该layer被判定为可见;从chip到最终屏幕可见,中间还隔着Occluded/Covered与真实像素两道检查。Android 15 起旧的 HWC/GPU hierarchy chips 已弃用;当前这层由 HWC 还是 GPU 合成,以新字段为准,别再拿旧 chip 判断。
可见性检查包含:
HIDDEN、父层隐藏、没有 buffer、visible region 为空等 invisibility reason(不可见原因);Occluded:上方 opaque layer 完全遮挡;Partially Occluded:上方 opaque layer 遮挡一部分;Covered:上方被标识为非opaque的layer覆盖,底层可能透出;若其实际像素全不透明,底层也可能完全看不见;- buffer 是否存在、frame number 是否更新、destination frame(buffer 映射到的目标矩形)是否正确;
- input touchable region(可接收触摸的区域)和 focus 属性是否匹配。
SF 中 layer visible 也不保证 buffer 像素正确:应用可能提交一张纯黑帧,decoder(解码器) 或 shader(GPU 着色程序) 也可能写出错误内容。Winscope 能证明该 buffer layer 参与了当前状态;像素内容对不对,还要 Producer 或 GPU 侧的线索。
6.5 对比 requested 与 calculated
Requested geometry/effects 是该 layer 提交的几何与效果请求,Calculated 是叠加父层继承、坐标变换和裁剪后用于当前合成的值。所以 requested 正确而 calculated 错误时,我们去查 parent、leash、crop、relative Z、transform 与 display 变换;requested 本身就错时,沿 transaction 找提交者。
6.6 回到 transaction 与 transition
SurfaceFlinger transaction trace 提供 transaction id、PID、UID、layer id 与状态变化。vsync_id 位于一次 SF commit 形成的 trace entry 上,同一 entry 内的 transactions 共用它;每笔客户端 transaction 各自的提交时间,要另找依据。
看到 bounds、alpha、crop 或 reparent(更换父 layer)异常后,搜索对应 transaction,确认它来自 App、system_server(承载 WMS 等系统服务的进程)、SystemUI/WM Shell 还是其它进程。
这里有个容易踩的点:源码里有两份同名的 PerfettoTransitionTracer。system_server 中的 com.android.server.wm.PerfettoTransitionTracer 记录 transition id、create/send/finish/abort 时间、start/finish transaction id、目标 leash layer id、window id、起止 display/rotation/bounds;WM Shell 中的 com.android.wm.shell.transition.tracing.PerfettoTransitionTracer 记录 handler 分派、merge request(合并请求)、merged(从子节点合并) 和 aborted。两者都通过 TransitionDataSource 注册 com.android.wm.shell.transition。
拿同一个 transition id,我们可以把 Shell transition 与 SF transaction、WM container、目标 layer 对齐。transition 已 finish 只说明系统状态机走完,目标 layer 显示是否正确还要再看。

图:用transition、start/finish transaction、目标window/layer等身份关联两侧状态。 system_server记录创建/发送/完成等,Shell记录分派/合并/中止;finish不是显示正确证明。
6.7 时序问题转到 Perfetto
Winscope 能指出“哪一个状态从哪一帧开始错误”。状态序列都对但画面卡顿时,我们转入 Perfetto,检查 UI thread、RenderThread、Binder、SF、FrameTimeline、HWC/DisplayHAL、GPU 与 I/O。FrameTimeline 把应用帧和 SurfaceFlinger 帧的预期时间、实际时间关联起来;DisplayHAL 是系统访问显示硬件的接口层。
若 buffer 到达、latch(SF 选取 buffer 进入本次合成)或 present 受 fence 限制,再核对指定 kernel tag 的 sync_file.c 和 dma-fence.c 语义,并结合设备 vendor 驱动轨迹;AOSP kernel 文件解释的是公共同步语义,具体等待来源取决于设备驱动。
7. 典型问题的排查顺序
白屏或黑屏
白屏或黑屏,我们按下面的顺序收集依据。
- 在录像中锁定异常帧与 Display。
- 在 WM 中确认目标窗口、starting window(应用内容就绪前的临时启动窗口)、splash 与上层系统窗口的状态。
- 按出图类型找到宿主与独立内容 layer。
- 检查 buffer、frame number、visible region、occlusion、parent、crop、alpha 和 z-order。
- 有 layer 且有 buffer 时,区分“上层遮挡”“提交黑色像素”“protected/secure 内容受安全策略限制而截不到图”。
- 几何/显隐错误沿 transaction 追提交者;buffer 迟到转到 BLAST、BufferQueue(连接 Producer 与 Consumer 的缓冲队列) 和 Perfetto;像素内容错误转到 HWUI、MediaCodec、Camera、GL/Vulkan 或 AGI。
官方 Winscope flicker 样例就是这样:Activity layer 被设为 visible/opaque,但 visible region 为空,当前真正可见的是上方 opaque 的 NotificationShade。它演示的正是 WM visible 与 SF 最终可见结果要分开验证。
首帧与 starting window
首帧问题,我们把 Activity/Window、starting window、splash layer、App Window buffer 和 transition 放到同一时间轴上:splash 是 starting window 中显示的启动画面,而 WM Activity visible、App 第一块 buffer 进入 SF、splash 移除 transaction、用户看到首帧这四个时刻可能各不相同。App layer 没有 buffer 时走启动/BLAST 路径;buffer 已存在但 splash 过早移除时,检查 transition 与 starting-window policy(启动窗口的显示和移除策略)。
SurfaceView 一帧错位
SurfaceView 一帧错位时,同时选中宿主 App Window、SurfaceView container 和 BLAST child,对比 container geometry transaction、宿主 draw transaction、BLAST child frame number、buffer transaction 与目标 display snapshot。
container 已移动而 child 沿用旧 buffer,未必是异常:几何和内容本来就来自不同状态源。应检查是否通过同步组、同一有效 SurfaceControl.Transaction,或按frame number合并了几何与buffer更新;这些机制负责协调状态,入口用上之后,还要逐项验证实际匹配对象、提交/合并时机与fence条件,才算确认目标帧同步成功。
若 crop、relative Z 或内容/几何持续错位,继续查 mergeWithNextTransaction()、applyTransactionOnDraw()、applyTransactionToFrame() 等同步入口和 Producer 的提交节奏;applyTransactionToFrame() 只有调用前暂停SurfaceView渲染、之后再产出下一帧时,精确绑定的帧才有定义;持续出帧时对应哪一帧未定义,后续没有新帧时,这笔transaction可能一直得不到应用。

图:同时追container几何和BLAST child内容,验证所需的同步关系及帧绑定条件。 applyTransactionToFrame精确绑定要求先暂停再产下一帧;持续出帧未定义对应帧,无新帧时可能一直得不到应用。 下方为合成状态示意,条长非实测时间。
TextureView 黑屏
SF 中找不到独立 TextureView layer 属于正常拓扑。我们先确认宿主 App Window 有没有新 buffer 并可见,再检查 SurfaceTexture 的 frame-available 通知、HWUI texture acquire(取得待采样纹理)、宿主 RenderThread 和外部 Producer;只在 Winscope 里反复搜“TextureView layer”,是搜不出有效线索的。
转场、旋转、分屏与 PiP
从 transition id 进入目标转场,记录参与的 WindowContainer、leash layer、起止 bounds/display/rotation、start/finish transaction 和 played/merged/aborted(已播放/已合并/已中止)状态,再逐帧检查 child buffer 与 leash geometry。
位置跳变的候选来源不少:WCT(WindowContainerTransaction,批量修改窗口容器的事务)、WM sync(协调多项窗口更新同步提交)、Shell leash 动画、应用 resize buffer 迟到、SF parent 变换;区分它们要靠 transition 与 transaction 记录。
点击无响应
SF TRACE_FLAG_INPUT 提供 layer 输入窗口属性、touchable region 和 focus 线索,但完整的事件派发它不记录;需要事件链时增加 android.input.inputevent。固定A17的 android.input.inputevent 源限定userdebug/eng构建。TRACE_MODE_TRACE_ALL 会绕过调试构建的隐私过滤,只用于本地或测试;规则模式另用 TRACE_MODE_USE_RULES,且默认就是规则模式。配置了规则,普通量产user build也还是拿不到这个数据源。
先按事件类别分流。指针触摸从Display和坐标出发,检查touchable region、遮挡/触摸策略、手势当前target与InputDispatcher派发,再追应用回调;按键和非指针motion则重点检查focused window,IME还需自己的焦点/输入连接。触摸target未必是focused window,点击命中也未必经过focus。可见layer与实际输入目标也可能不同。

图:指针按坐标、区域和手势目标分派,按键及非指针motion主要走焦点目标。 可见最上层、focused window和触摸target可能不同;还需追应用回调和窗口输入策略。
8. Search viewer 与 Perfetto SQL
前面都是逐帧翻看;要按条件批量筛状态时,我们转到 Search viewer。Winscope在Search viewer首次初始化时创建helper views;这些视图在独立Trace Processor中未必预先存在。它借助Perfetto Trace Processor提供 sf_layer_search、transactions_search、transitions_search、viewcapture_search、wm_search 等 helper view(预先整理好的 SQL 视图)。property 保留 repeated field(proto 中可重复出现、类似数组的字段) 的下标,flat_property 忽略下标;value 与 previous_value 用字符串表示,布尔值写成 0/1。
比如我们想定位候选时间点,下面的查询列出目标 App layer 被计算为可见的状态:
SELECT DISTINCT ts, layer_id, layer_name
FROM sf_layer_search
WHERE layer_name LIKE '%com.example%'
AND is_visible = 1
ORDER BY ts;
结果中的 ts 是 trace 时间戳,可直接映射到 Winscope 时间轴。可见记录很多时,再加 layer_id 过滤当前实例,避免混入旧 layer 或同包其它窗口。
下面的查询寻找窗口容器的可见采样状态。WM与SF按不同触发条件写快照,两个查询并没有自动做区间连接;要判断同一时间区间内的关系,还需按稳定对象身份、相邻快照有效区间及丢失情况对齐:
SELECT DISTINCT ts, title, token, parent_token
FROM wm_search
WHERE title LIKE '%DetailActivity%'
AND is_visible = 1
ORDER BY ts;
WM可见采样早于SF可见采样时,可继续查App buffer、starting window和transition;采样频率不同或缺失也可能造成先后差,认定真实延迟前,先把这两类原因分开。
下面的查询用官方 helper view 字段,寻找把 layer x 改为 -54.0 的 transaction:
SELECT ts, transaction_id, value
FROM transactions_search
WHERE flat_property = 'transactions.layer_changes.x'
AND value = '-54.0'
ORDER BY ts;
拿到 transaction id 后,结合底层表 android_surfaceflinger_transaction 的 PID/UID/layer id 和 ProtoLog 定位提交方;查询里要做数值比较时,先把字符串显式转换成数值类型,避免按字典序比较。
9. Dump、bugreport 与 trace
静态问题,比如“当前点不到”或“当前谁盖住谁”,可以用 WM/SF proto dump:proto dump 把服务当前状态序列化为 Protocol Buffers 二进制快照。下面两条命令分别保存 WindowManager 与 SurfaceFlinger 快照,供 Winscope 加载。
adb exec-out dumpsys window --proto > window_dump.winscope
adb exec-out dumpsys SurfaceFlinger --proto > surfaceflinger_dump.winscope
当前目录会得到两个候选 .winscope 文件;检查命令退出状态,并在Winscope中成功解析确认内容。重定向可能先创建空文件,服务端错误也可能被写入文件,有效的proto dump以Winscope成功解析为准。dump 记录的只是单个时刻,闪屏、转场顺序或一帧错位这类时序问题,要靠连续 trace 才能看到,而连续 trace 又受开销和 ring buffer 容量影响;bugreport 是 Android 的综合故障报告,适合保留故障前的 ring-buffer 历史,代价是包含的系统信息更多、隐私等级更高。三种产物按问题选用。
10. 采集成本、隐私与保真度
采集配置怎么选,要看下面这些成本、隐私和保真度的影响。
- SF layer
MODE_ACTIVE会在 layer 状态变化时直接采集 snapshot,计算开销高;长时间性能测试应使用开销更低的模式。 TRACE_FLAG_BUFFERS记录每次 buffer 变化;缺少这个 flag 时,默认只在几何变化时生成新 SF 状态,可能看不到静止几何下的 frame 推进。TRACE_FLAG_EXTRA与TRACE_FLAG_HWC数据量大。HWC flag 提供未结构化厂商元数据,跨设备字段一致性没有保证。- WindowManager transaction 频率能捕获一帧内中间状态,开销高于 frame 频率。
- ProtoLog stacktrace 会为日志附加调用栈,采集速度慢,适合短时本地复现。
- Input、IME、screen recording、ViewCapture、ProtoLog 和 bugreport 可能包含操作轨迹、文本、窗口标题、应用标识与画面。采集、传输、存储和分享都要按敏感诊断数据管理。
- ring buffer 太小会覆盖故障前状态;过大又增加内存压力。短时确定性复现优先于十几分钟全量采集。
- protected/secure 内容使用受保护的 buffer 与显示路径,可能进不去 screen recording 或截图。黑色视频区域别单凭录像判断 Producer 没出帧。
11. Winscope、Perfetto、AGI 与 dumpsys 的分工
| 工具 | 主要证据 | 适合的判断 |
|---|---|---|
| Winscope | WM/SF/transition/transaction/input 状态序列 | 对象是谁,窗口与 layer 状态何时偏离预期 |
| Perfetto | 调度、slice(持续时间区间)、counter(数值序列)、Binder、FrameTimeline、ftrace(内核事件)、部分图形数据源 | 哪个阶段迟到,CPU/锁/Binder/fence/I/O 等待在哪里 |
| APA / AGI / RenderDoc | APA System提供系统轨道;APA canary Frame基于GFXR重放测量;AGI/RenderDoc检查兼容的逐帧资源与调用 | 固定工具版本、设备、API和重放条件;AGI的GLES帧路径需ANGLE转Vulkan,不能当作任意原生GL调用的通用证据 |
dumpsys | 当前时刻的文本/proto 状态 | 快速复核静态现场或脚本化取证 |
简单说:“画面状态错误”优先用 Winscope 定位对象与状态;“状态正确但画面迟到”转到 Perfetto;“buffer 内像素或 GPU 命令错误”根据图形 API 和采集路径选 AGI 或对应 Producer 工具。复杂问题通常要在同一时间点组合多类证据。
12. 排查清单
- 异常发生在哪个
displayId和时间点? - 目标是一个 Window、一个 SF layer、一个 transition leash,还是宿主内部 UI 节点?
- 页面属于标准窗口、SurfaceView、TextureView、混合出图还是多窗口?
- WM 逻辑可见与 SF 计算可见是否分别确认?
- 目标 SF 对象是 container、buffer layer、background、leash 还是旧实例?
- requested 与 calculated geometry 差异来自哪个 parent?
- frame number、buffer、visible region、occlusion、crop、alpha 与 relative Z 是否一致?
- 修改状态的 transaction id、PID/UID、layer id 与
vsync_id是什么? - transition id、目标 window/layer、start/finish transaction 和 finish/abort 状态是否匹配?
- 现象属于状态错误、buffer 迟到、像素错误还是 present 迟到?
- trace 模式和 flags 是否足以观察该变化,又是否改变了被测性能?
- Input、录像、bugreport 与 ProtoLog 是否按敏感数据处理?
View、Compose 结构与属性检查
系统窗口状态确认后,我们把视角收进应用内部,用 Layout Inspector 或 ViewDebug 查布局问题。这两类工具的时间维度和权限范围并不相同。
Layout Inspector 是 Android Studio 中检查运行时 UI 的工具,看的是应用进程内的 View、Compose 或混合 UI:节点是否存在、父子关系如何、bounds 与属性是什么、Compose 节点重组或跳过了多少次。它给我们的材料是组件树和当前属性;一帧为何变慢、画面最终有没有进屏幕,要靠下面这组分工。
帧耗时交给 Perfetto,窗口与 SurfaceFlinger layer 状态回到前文的 Winscope 时间线,GPU 命令和 buffer 像素问题根据图形 API 转到 AGI 或对应 Producer 工具。
以下界面步骤按本轮取回的官方嵌入式Layout Inspector指南叙述,平台机制按Android17/API37源码核对。Quail3 | 2026.1.3修复单用于历史版本对照,以下UI操作以实际安装版本为准。3D、独立窗口入口与连接方式仍按实际IDE版本确认。
1. 当前 Layout Inspector 的入口与界面
当前官方指南默认使用嵌入Running Devices的Layout Inspector:运行 debuggable 应用后,我们在 Running Devices 窗口点击 Toggle Layout Inspector,IDE 会自动连接当前设备前台的 debuggable 进程。debuggable 表示该构建允许调试器和检查工具附着,通常对应开发用的 debug 变体;物理设备则要先启用 device mirroring(把设备画面和输入映射到 IDE)。
当前界面有三个主要区域:
- Component Tree:View、composable(由
@Composable函数产生的 Compose 节点)与混合节点的层级; - Layout Display:当前渲染结果、bounds、标签、放大视图与参考图 overlay(叠加图);
- Attributes:选中节点的运行时属性、Compose 参数和 semantics(供无障碍服务与测试理解 UI 含义的语义信息)。
Deep Inspect(深度选取)开启时,点击镜像画面会选中组件,重叠区域右键还能列出多个候选节点;要继续操作应用,记得关闭 Deep Inspect。树太深时,用 Show Only Subtree 或 Show Only Parents 只保留目标子树或父链。semantics 的概念后文会反复用到。
Layout Inspector 能直接确认什么、什么要转其它工具,见下表。
| 问题 | 能直接确认的证据 | 需要转到其它工具的部分 |
|---|---|---|
| 节点没有出现 | Component Tree 中是否存在、父节点是否符合预期 | 状态更新或分支为何没有执行:日志、调试器、Compose tracing |
| 尺寸或位置错误 | layout bounds、margin/padding、constraint(约束关系)、translation(绘制平移)、父层裁剪 | 一帧内为何晚更新:Perfetto |
| 点击无响应 | clickable/enabled、节点层级、可见 bounds、semantics | 父层拦截、TouchDelegate(代理触摸区域)、InputDispatcher(系统输入分发器)、窗口遮挡:事件 trace/Winscope |
| Compose 更新异常 | recomposition/skipped count(重组/跳过次数)、参数、semantics、源码跳转 | 重组成本和帧影响:Compose tracing/Perfetto |
| View 层级过深 | 节点数量、嵌套与重复容器 | 是否造成性能问题:measure/layout/draw 时间 |
| 黑屏或遮挡 | 宿主 UI 树是否存在,SurfaceView/TextureView 节点属性 | SF layer、独立 buffer、系统窗口、protected(受安全策略保护的)内容:Winscope/Perfetto |
一个节点在 Component Tree 中存在,说明应用进程里有这个 UI 对象;节点最终有没有可见像素、输入事件有没有送到它,还要分别到渲染和输入链路上确认。
2. Live 检查、snapshot 与参考图
嵌入模式下,要让 UI 变化实时反映到 Inspector,需要启用 Live Edit;独立 Layout Inspector 仍可在设置中开启,用工具栏的 Live Updates 开关。官方建议优先用默认嵌入模式,性能更好。
Snapshot 会保存详细渲染结果、View/Compose/hybrid(混合) 组件树和节点属性,可用于离线复盘与团队协作,导出和导入都走 Snapshot Export/Import 入口。排查偶发错位时,我们要在错误状态还在时就抓 snapshot;等界面恢复了,snapshot 就还原不出此前的树了。
参考图 overlay 适合核对设计稿:Inspector 把 bitmap(位图) 缩放到布局显示区域,Overlay Alpha 控制叠加图透明度,可以暴露间距、尺寸和文字基线的差异。像素级截图对比还是要单独做——缩放、设备镜像、字体栅格化(字形转成像素)、动态颜色与系统栏都会影响视觉结果。
Android Studio Panda 2 已将 Layout Inspector 3D Mode 标为 deprecated(已弃用)。当前文档以标准 2D Layout Display 与 Component Tree 为主,团队文档里不必再把 3D 当成必需步骤。
3. View 属性检查会重启前台 Activity
Views 属性检查依赖全局设置 debug_view_attributes:Layout Inspector 启动时会自动开启它,系统随之重启当前前台 Activity;只要这个 flag 没被手动关闭,后续连接就不会再因启用该设置重启。这次重启会改变冷启动路径、页面状态和只消费一次的事件,抓现场前要把它写进复现步骤。
下面的命令用于手工核对或恢复该全局开关。
#!/usr/bin/env bash
set -euo pipefail
inspector_previous=$(adb shell settings get global debug_view_attributes)
inspector_previous=${inspector_previous//$'\r'/}
case "$inspector_previous" in
null|0|1) ;;
*) printf '%s\n' '原值不是null/0/1,保留现场并停止' >&2; exit 1 ;;
esac
inspector_restore() {
if [[ "$inspector_previous" == null ]]; then
adb shell settings delete global debug_view_attributes
else
adb shell settings put global debug_view_attributes "$inspector_previous"
fi
}
trap inspector_restore EXIT
adb shell settings put global debug_view_attributes 1
printf '%s\n' '完成属性检查后按Enter,脚本将恢复原值'
read -r inspector_done
put 1会为设备上所有进程启用额外属性信息;脚本恢复原值,原先未设置时才delete。它是手工检查示例,日常可由IDE管理。切换设置可能触发Activity重启,恢复命令也可能失败;性能测量前断开Inspector,复核实际flag与页面状态。
View.mAttributes 以“属性名、属性值”相邻成对的数组保存这类调试信息,debug_view_attributes 对应的开发者选项负责启用 View 属性检查。属性缺失时,依次检查构建是否 debuggable、Activity 是否已按新设置重启、目标是否为前台进程,以及厂商系统是否限制调试通道。
4. 读懂坐标、变换与层级
View 的 left/top/right/bottom 是相对父 View 的布局边界,translationX/Y 只改绘制位置、不重定义这组 layout bounds,View 的 x/y 则包含 translation。把 Inspector 数值与整屏截图比较时,还要算上父层滚动、matrix(缩放/旋转等坐标变换矩阵)、窗口在屏幕上的偏移、Insets、letterbox(为适配宽高比留下的边带)和 Display 变换。
排查位置问题按以下顺序记录:
- 目标节点和直接 parent 的 layout bounds;
- margin、padding、constraint 或 LayoutParams;
- translation、scale、rotation、pivot 与 elevation/Z;
- parent 的 scroll、clipChildren、clipToPadding 和 matrix;
- App Window 相对 Display 的位置、Insets 与多窗口 bounds。
visibility=VISIBLE 加 alpha>0(并非全透明) 也不足以证明最终可见:节点可能被父层裁剪、被 sibling(同一父节点下的兄弟节点)覆盖、位于窗口外,或者宿主窗口在 SurfaceFlinger 中被系统 layer 遮住。应用内父子层级用 Inspector,跨窗口与 SF layer 遮挡用 Winscope。
点击区域要再加一组检查:enabled、clickable、focusable、TouchDelegate、父 View 的 onInterceptTouchEvent()(是否拦截子节点触摸)、Compose pointer input(指针输入处理)、semantics merge(把子节点语义合并到父节点)和窗口级 input target(系统选中的输入目标)。视觉上位于最上方的节点不一定收到事件;clickable=true 也不证明事件已经抵达。
5. Compose 层级、重组计数与 semantics
Layout Inspector 可显示 composable 层级、参数、recomposition count(composition/recomposition 计数)、skipped count(Compose 运行时判定本轮无需执行的次数) 和 semantics。查看计数要求设备 API 29+ 且 Compose 1.2.0+。若 Component Tree 没有 Compose 节点,先确认 APK 没有移除 META-INF/androidx.compose.*.version 文件——这些版本元数据供 Inspector 识别 Compose 运行时。
重组计数要围绕一个受控交互读取:
- 点击 Reset 清零计数;
- 只执行一次目标动作;
- 找计数上升的最小 composable 子树;
- 双击节点跳到源码,核对参数与状态读取;
- 用 Compose tracing 或 Perfetto 验证这部分工作是否占用目标帧。
读这两个计数要小心:高 recomposition count 只说明函数频繁重新执行,单次执行可能很轻;skipped count 只说明本轮被跳过。状态读取范围过大、参数不稳定、在 composition(Compose 生成并维护 UI 节点的过程)中反复创建不稳定对象、列表 key 不稳定,都可能扩大重组范围;要下性能结论,仍以源码与 trace 为准。
选中 composable 后,Attributes 可以显示参数,inline function(编译器把函数体展开到调用点的内联函数) 的参数可能展示不出来。Semantics 信息要区分 declared(节点直接声明) 与 merged:前者由节点直接声明,后者从子节点合并。视觉树、Compose composition tree、semantics tree 和测试节点树用途不同,节点合并或清除 semantics 后,自动化工具看到的层级可能与 Component Tree 不同。
6. Layout Inspector、ViewDebug 与 typed inspector 的职责
平台同时保留了几类调试属性机制:Layout Inspector 是 IDE 工具,ViewDebug 包含基于注解和反射的旧式导出能力,android.view.inspector 定义强类型属性读取接口。三者会在工具链里配合,但调用方式和数据来源不同。
debug_view_attributes 与 View.mAttributes
开发者选项让 View 保存 XML 属性名和值,Layout Inspector 可显示这部分属性来源信息。
android.view.inspector
API 29 起的 typed inspector(强类型检查接口)使用 InspectionCompanion、PropertyMapper 和 PropertyReader:companion 先把属性名和类型映射为整数 ID,再按 boolean、int、color、resource ID、object 等具体类型读取值。StaticInspectionCompanionProvider 按“被检查类名 + $InspectionCompanion”查找内部类或生成类。
整数 ID 与类型专用的读写方法能减少重复字符串比较和 primitive boxing(把 int、boolean 等基本类型包装成对象),也让枚举、flags(按位组合的开关值)与资源 ID 更清晰。这些 framework 接口和隐藏 inspection 注解主要供平台与工具链使用,应用别把隐藏注解当成公开 API;自定义组件优先提供稳定 getter,并为无障碍和测试补全 semantics。
ViewDebug.ExportedProperty
ViewDebug 仍提供 @ExportedProperty 与 @CapturedViewProperty:View 本身就大量用 @ExportedProperty 标记 measurement、layout、drawing、focus、accessibility 等字段或 getter。ViewDebug 按 Class 缓存反射得到的属性描述,再按 category(分类)、resource ID、enum/flag mapping(枚举/位标志映射)等规则格式化。
dumpCapturedView() 会把 @CapturedViewProperty 标记的值格式化后写入 log。它适合在受控环境中临时取证,不适合作为线上高频监控:反射、字符串构造与日志都会增加运行成本,输出也可能包含业务数据。
7. invalidate()、requestLayout() 与 RenderNode
布局错误和绘制更新错误要分开处理。requestLayout() 沿父链请求新的 measure/layout(测量尺寸/确定位置);invalidate() 只标记需要重绘的区域,尺寸维持原样。requestLayout() 之后框架也可能复用满足条件的测量结果,未必要每个节点重新 measure。RenderNode 是硬件加速渲染中的节点,保存绘制属性和 display list。
下面的调用链是一次 View 重绘请求的概念路径,用于判断更新停在应用属性、父链 invalidation、下一次 traversal,还是 display list 录制。
View.invalidate()
→ View.invalidateInternal()
→ ViewParent.invalidateChild(...)
→ ViewRootImpl.scheduleTraversals()
→ Choreographer.CALLBACK_TRAVERSAL
→ ViewRootImpl.performTraversals()
→ ThreadedRenderer / RenderNode display list
invalidateInternal() 设置 PFLAG_DIRTY,并在需要时设置 PFLAG_INVALIDATED——这两个是 View 内部的 dirty/invalidated 标志位;随后它把 damage(需要重绘的区域)传给 parent。
ViewRootImpl.scheduleTraversals() 放置同步屏障,使屏障之后的普通同步消息等待traversal,显式异步消息可以继续调度。doTraversal() 会先移除屏障,再执行 performTraversals();排队与屏障只约束相应消息顺序。它通过 Choreographer.postVsyncCallback(CALLBACK_TRAVERSAL, ...) 安排下一次 traversal。Choreographer 负责把 UI 工作对齐到显示垂直同步,traversal 则包含 measure、layout、draw 等树遍历工作。这些状态只说明工作已进入后续帧调度,尚未证明目标帧按时绘制或显示。
硬件加速路径下,ThreadedRenderer.updateViewTreeDisplayList() 根据 PFLAG_INVALIDATED 设置 mRecreateDisplayList,清除 flag 后调用 updateDisplayListIfDirty()。display list 就是交给 RenderNode/HWUI 的绘制命令记录。节点属性正确但屏幕内容没更新时,我们按四个阶段检查:
- 状态是否已写入 View 或 RenderNode;
- 是否调用
requestLayout()、invalidate()或对应 Compose state update; - traversal/display-list 录制是否发生;
- RenderThread、App Window buffer、SurfaceFlinger 与 present 是否继续推进。
前三项属于应用 UI 和渲染提交,第四项已经离开 Layout Inspector 能看到的范围;这条调用链本身也证明不了“像素已经出现在屏幕上”。

图:按状态写入、更新请求、UI traversal/display list和渲染提交分段检查。 有效invalidation才进入相应调度;同步屏障允许异步消息,display list更新不证明已present。
8. SurfaceView、TextureView 与宿主窗口
Layout Inspector 展示 View/Compose 层级,SurfaceFlinger 展示参与系统合成的 layer;两棵树描述的层次不同,节点之间没有一一对应关系。
- 普通 View 与纯 Compose 通常绘制进宿主 App Window buffer。Inspector 能选到内部节点,Winscope 主要看到宿主窗口 layer。
- SurfaceView 在 View 树中有宿主节点,同时维护 container、BLAST buffer child(接收独立内容 buffer 的子层) 和按条件显示的 background layer,三者含义见前文。外部 Producer 的内容 buffer 不在宿主 View display list 中,Inspector 看不到 BLAST child 有没有收到新 buffer。
- TextureView 也有 View 节点,但外部 buffer 先由 SurfaceTexture/HWUI 采样进宿主 App Window。SurfaceFlinger 通常没有独立可见的 TextureView layer;外部 queue、acquire fence 和纹理像素是否正确,Inspector 同样看不到。
黑屏排查应先判断像素进入了哪个 Surface:SurfaceView 转到 Winscope 检查 container/BLAST child、buffer、crop 与 Z 层级;TextureView 检查 SurfaceTexture、宿主 RenderThread 与 App Window buffer;标准 View/Compose 则检查宿主 display list 和窗口 buffer。
9. uiautomator dump、Accessibility 与 Inspector
Layout Inspector 读取可调试应用的内部 View/Compose 结构;UI Automator 和 Accessibility 面向的是 AccessibilityNodeInfo(提供给无障碍服务和自动化工具的节点)。uiautomator dump 是旧式 shell 入口,现代测试代码可调用 UiDevice.dumpWindowHierarchy() 输出 XML。两类入口都受可访问性节点暴露规则约束,结果可能与内部组件树不同。
| 入口 | 适合的场景 | 主要限制 |
|---|---|---|
| Layout Inspector | 自家 debug 构建、源码定位、运行时属性、Compose 重组 | 依赖 debuggable 进程和 IDE 调试通道 |
uiautomator dump | 黑盒 release App、脚本化获取已暴露的文本/bounds/clickable | 节点可能合并或缺失,不能代表完整 View 树;旧式 shell 命令的可用性还取决于系统版本 |
| Accessibility service | 无源码辅助功能与自动化检查 | 受权限、隐私、semantics 和窗口策略限制 |
Accessibility bounds 可用于复核自动化工具看到的点击区域;View 私有字段、RenderNode 绘制命令或 Compose 重组记录这些内部状态,要回到 Inspector 或 trace 里看。
10. px、dp 与坐标系
密度换算使用 density = densityDpi / 160,因此 dp = px / density。dp(density-independent pixel,密度无关像素)用于跨密度描述界面尺寸,px 是实际像素单位。截图像素、设备镜像像素、View layout 坐标和设计稿 dp 可能属于不同坐标系,换算前我们要记录物理/override(命令覆盖)尺寸、density、窗口 bounds 与 Insets。
下面的命令用于读取显示尺寸、density 和当前窗口/Display 信息。
adb shell wm size
adb shell wm density
adb shell dumpsys window displays
wm size 与 wm density 可能同时报告 physical(物理默认值)和 override 值。多窗口、桌面窗口、letterbox、cutout(屏幕开孔区域)、状态栏、导航栏与显示缩放都会改变 App Window 的原点或可用区域。记录 UI 差异时写明“截图 px → density → dp → 设计值”,并附上坐标系。
11. 与 Perfetto、Winscope 联合诊断
Inspector 用来确认结构与属性;性能基准我们放在断开连接后做:View 属性检查会重启 Activity,Live 更新、镜像和重组计数也会改变被测环境。性能复现时先关闭 Inspector,再用相同操作单独抓 Perfetto,避免工具附着成为额外变量。
| Inspector 现场 | 时间或系统证据 | 排查方向 |
|---|---|---|
| 重复容器多、层级深 | measure/ 在目标帧持续过长 | 扁平化高频列表 item,减少重复 measure |
| Compose 子树计数高 | composition/recomposition slice 与目标帧重合 | 缩小 state 读取范围,检查参数稳定性与 key |
| bounds/属性正确,内容没更新 | traversal、display list、RenderThread、buffer | 区分状态没写入、没 invalidation、没录制与 buffer 没显示 |
| 应用树正确,仍有遮挡或焦点错 | WM/SF/input trace | Winscope 检查系统窗口、layer、touchable region |
| SurfaceView 节点存在但画面黑 | 独立 BLAST child、buffer、protected path | Winscope、Producer trace、Media/Camera/GPU 工具 |
FrameTimeline 把 App 帧和 SurfaceFlinger 帧的 expected/actual 时间关联起来,可判断它们是否错过目标 present(交付显示的时限);这条帧时间线要回到 Perfetto 里看。measure/layout slice(trace 中的一段持续时间) 较长时,还要结合调用次数和节点规模,单凭“树很深”就宣布根因为时过早。
12. 连接失败与一轮可复查流程
连接失败时,我们先记录现场:Android Studio 版本、设备 API、目标进程、构建是 debug 还是 release、Compose 版本、Android 用户/profile(工作资料等独立用户空间)、adb 状态和厂商 ROM(系统构建)。排查顺序如下:
- 确认运行的是前台 debuggable 进程,Running Devices 选择了正确设备;
- 物理设备启用 mirroring,adb 已授权且
pidof能返回目标进程的 PID; - 允许
debug_view_attributes触发一次 Activity 重启,再复现问题; - Compose 节点缺失时检查版本 metadata(描述 Compose 版本的 APK 元数据) 是否被 packaging 规则删除;
- 属性缺失时检查全局 flag、构建类型、Activity 是否按新设置重启;
- 条件允许时,在 Pixel/AOSP 设备复现同一问题,帮助区分应用问题与厂商调试限制;
- 错误状态出现时导出 snapshot,并记录截图、操作脚本和代码提交;
- 按问题类型另抓 Perfetto 或 Winscope,避免 Inspector 附着改变性能结果。
一份可复查记录至少包含:设备与构建信息、复现步骤、snapshot、目标节点路径、异常属性、源码位置和后续 trace 链接。点击或遮挡问题还要记录 Window/displayId;SurfaceView/TextureView 问题还要记录内容 Producer 与对应 SF layer。
参考资料
Winscope
- Winscope overview
- Capture traces with Winscope
- Capture Winscope traces with adb
- Winscope SurfaceFlinger viewer
- Winscope trace search
- SurfaceFlinger and WindowManager
- AOSP WMS tracing,
android-17.0.0_r1 - AOSP transition tracing,
android-17.0.0_r1 - AOSP WM Shell transition tracing,
android-17.0.0_r1 - AOSP
SurfaceView.java,android-17.0.0_r1 - AOSP
TextureView.java,android-17.0.0_r1 - AOSP SurfaceFlinger tracing,
android-17.0.0_r1 - AOSP SurfaceFlinger FrontEnd,
android-17.0.0_r1 - Kernel sync file,
android17-6.18-2026-06_r6 - AGI Frame Profiler
Layout Inspector
- Android Studio: Debug your layout with Layout Inspector
- Layout Inspector for Views
- Compose UI debugging
- Android Studio Quail 3 closed issues
- Android Studio Panda 2 release notes
- Android developer options: view attribute inspection
- Optimize layout hierarchies
- UI Automator legacy API guide
- UiDevice API reference
- AOSP
View.java,android-17.0.0_r1 - AOSP
ViewRootImpl.java,android-17.0.0_r1 - AOSP
ThreadedRenderer.java,android-17.0.0_r1 - AOSP
ViewDebug.java,android-17.0.0_r1 - AOSP view inspector package,
android-17.0.0_r1