Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 中的主要证据如下。

数据源记录内容能确认什么仍需其它证据的问题
WindowManagerWindowContainer 层级、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 transitionstransition id、类型、flags、目标 leash/window、起止 bounds、开始/结束 transaction 与时间戳启动、返回、旋转、分屏、PiP、Recents 的系统转场状态;leash 是转场期间承载动画的临时父 layerApp 内部动画每帧的绘制成本
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 hierarchyWindowContainer、DisplayContent、Task、ActivityRecord、WindowState逻辑 parent、display、bounds、visibility、focus、Insets
WM Perfetto traceWindowTracingPerfetto、WindowTracingDataSourceLOG_LEVEL_、LOG_FREQUENCY_ 与 WMS dump 序列化
Shell transitionTransitionController、system_server 与 WM Shell 各自的 PerfettoTransitionTracer、TransitionDataSourcesync/transition id、target leash layer id、start/finish transaction、handler(处理器)分派、merge(转场合并)、finish/abort(完成/中止)
Surface transactionSurfaceControl.Transaction、SurfaceComposerClientposition、matrix、crop、alpha、layer、reparent、buffer 与 transaction id
SF runtime stateRequestedLayerState、LayerLifecycleManager、LayerSnapshotBuildertransaction 状态怎样变成当前 snapshot,父层属性怎样影响计算结果
SF layer traceLayerDataSource、LayerTracingactive snapshot 与 generated snapshot 的来源
SF transaction traceTransactionDataSource、TransactionTracingcontinuous ring buffer、active 逐笔写入、PID/UID/layer/vsync id

固定 tag 的入口如下:

读 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.windowmanager
  • android.protolog
  • android.input.inputevent
  • android.surfaceflinger.layers
  • android.surfaceflinger.transactions
  • com.android.wm.shell.transition
  • android.inputmethod
  • android.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. 典型问题的排查顺序

白屏或黑屏

白屏或黑屏,我们按下面的顺序收集依据。

  1. 在录像中锁定异常帧与 Display。
  2. 在 WM 中确认目标窗口、starting window(应用内容就绪前的临时启动窗口)、splash 与上层系统窗口的状态。
  3. 按出图类型找到宿主与独立内容 layer。
  4. 检查 buffer、frame number、visible region、occlusion、parent、crop、alpha 和 z-order。
  5. 有 layer 且有 buffer 时,区分“上层遮挡”“提交黑色像素”“protected/secure 内容受安全策略限制而截不到图”。
  6. 几何/显隐错误沿 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可能一直得不到应用。

SurfaceView 几何与内容的同步条件

图:同时追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 的分工

工具主要证据适合的判断
WinscopeWM/SF/transition/transaction/input 状态序列对象是谁,窗口与 layer 状态何时偏离预期
Perfetto调度、slice(持续时间区间)、counter(数值序列)、Binder、FrameTimeline、ftrace(内核事件)、部分图形数据源哪个阶段迟到,CPU/锁/Binder/fence/I/O 等待在哪里
APA / AGI / RenderDocAPA System提供系统轨道;APA canary Frame基于GFXR重放测量;AGI/RenderDoc检查兼容的逐帧资源与调用固定工具版本、设备、API和重放条件;AGI的GLES帧路径需ANGLE转Vulkan,不能当作任意原生GL调用的通用证据
dumpsys当前时刻的文本/proto 状态快速复核静态现场或脚本化取证

简单说:“画面状态错误”优先用 Winscope 定位对象与状态;“状态正确但画面迟到”转到 Perfetto;“buffer 内像素或 GPU 命令错误”根据图形 API 和采集路径选 AGI 或对应 Producer 工具。复杂问题通常要在同一时间点组合多类证据。

12. 排查清单

  1. 异常发生在哪个 displayId 和时间点?
  2. 目标是一个 Window、一个 SF layer、一个 transition leash,还是宿主内部 UI 节点?
  3. 页面属于标准窗口、SurfaceView、TextureView、混合出图还是多窗口?
  4. WM 逻辑可见与 SF 计算可见是否分别确认?
  5. 目标 SF 对象是 container、buffer layer、background、leash 还是旧实例?
  6. requested 与 calculated geometry 差异来自哪个 parent?
  7. frame number、buffer、visible region、occlusion、crop、alpha 与 relative Z 是否一致?
  8. 修改状态的 transaction id、PID/UID、layer id 与 vsync_id 是什么?
  9. transition id、目标 window/layer、start/finish transaction 和 finish/abort 状态是否匹配?
  10. 现象属于状态错误、buffer 迟到、像素错误还是 present 迟到?
  11. trace 模式和 flags 是否足以观察该变化,又是否改变了被测性能?
  12. 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 变换。

排查位置问题按以下顺序记录:

  1. 目标节点和直接 parent 的 layout bounds;
  2. margin、padding、constraint 或 LayoutParams;
  3. translation、scale、rotation、pivot 与 elevation/Z;
  4. parent 的 scroll、clipChildren、clipToPadding 和 matrix;
  5. 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 运行时。

重组计数要围绕一个受控交互读取:

  1. 点击 Reset 清零计数;
  2. 只执行一次目标动作;
  3. 找计数上升的最小 composable 子树;
  4. 双击节点跳到源码,核对参数与状态读取;
  5. 用 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 录制与渲染提交

图:按状态写入、更新请求、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/layout 在目标帧持续过长扁平化高频列表 item,减少重复 measure
Compose 子树计数高composition/recomposition slice 与目标帧重合缩小 state 读取范围,检查参数稳定性与 key
bounds/属性正确,内容没更新traversal、display list、RenderThread、buffer区分状态没写入、没 invalidation、没录制与 buffer 没显示
应用树正确,仍有遮挡或焦点错WM/SF/input traceWinscope 检查系统窗口、layer、touchable region
SurfaceView 节点存在但画面黑独立 BLAST child、buffer、protected pathWinscope、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(系统构建)。排查顺序如下:

  1. 确认运行的是前台 debuggable 进程,Running Devices 选择了正确设备;
  2. 物理设备启用 mirroring,adb 已授权且 pidof 能返回目标进程的 PID;
  3. 允许 debug_view_attributes 触发一次 Activity 重启,再复现问题;
  4. Compose 节点缺失时检查版本 metadata(描述 Compose 版本的 APK 元数据) 是否被 packaging 规则删除;
  5. 属性缺失时检查全局 flag、构建类型、Activity 是否按新设置重启;
  6. 条件允许时,在 Pixel/AOSP 设备复现同一问题,帮助区分应用问题与厂商调试限制;
  7. 错误状态出现时导出 snapshot,并记录截图、操作脚本和代码提交;
  8. 按问题类型另抓 Perfetto 或 Winscope,避免 Inspector 附着改变性能结果。

一份可复查记录至少包含:设备与构建信息、复现步骤、snapshot、目标节点路径、异常属性、源码位置和后续 trace 链接。点击或遮挡问题还要记录 Window/displayId;SurfaceView/TextureView 问题还要记录内容 Producer 与对应 SF layer。

参考资料

Winscope

Layout Inspector