Skip to content

跨层故障链:从用户现象回到机器证据 ​

先读系统栈总图。本页不假设问题一定在浏览器、Flutter 或 GPU。它的工作是把用户的一句话,拆回一串可以分别证伪的节点。

text
现象
  -> 输入是否到达?
  -> 应用线程是否被调度?
  -> 状态是否转换?
  -> 引擎是否生成了新的渲染工作?
  -> 驱动是否接受并提交?
  -> GPU 是否完成?
  -> 新 buffer 是否被合成和扫描?
  -> 用户看到的是否是共享事实?

不要把“没反应”当成一个问题 ​

用户描述可能断点最先要的证据容易犯的错
点了没反应输入路由、主线程、状态机、焦点输入事件、应用日志、主线程 trace先改 CSS 或重写组件
点了很久才变调度、长任务、锁竞争、缺页输入到 handler 的等待时间、CPU profile直接归咎 GPU
状态已变,画面未变脏标记、布局/绘制、frame 提交状态日志、DOM/RenderObject、frame timeline认为网络慢
动画卡,最终状态对每帧 CPU、栅格、GPU、vsync每帧时间线、GPU/合成轨道只看平均 FPS
滚动白块图块、图片解码、显存、合成器tile/raster 轨道、内存、资源解码以为数据没加载
画面显示已保存,刷新后没有本地投影与共享事实requestId、服务端读回、审计记录用截图证明保存成功

第一条原则:沿时间线找缺口 ​

一条交互应至少留下这些时间点。没有某个时间点,不要猜下游。

text
t0 用户输入被设备采样
t1 内核/窗口系统把事件交给目标进程
t2 应用 handler 开始运行
t3 本地状态完成转换
t4 引擎提交新一帧
t5 GPU/合成完成该帧
t6 显示器呈现该帧
t7 服务端确认或拒绝该命令
t8 应用把确认后的状态呈现出来

t0 -> t2 大,先看输入、调度和主线程;t2 -> t4 大,先看应用与引擎;t4 -> t6 大,先看驱动、GPU、合成与显示;t6 -> t8 大,先看网络、共享事实和结果合并。不要跨过中间点直接给结论。

四条可操作的故障链 ​

1. 点击延迟 ​

text
鼠标/触摸报告
  -> 内核输入队列
  -> 窗口命中测试
  -> 浏览器/应用主线程
  -> handler

检查顺序:

  1. 系统层是否已经出现输入事件;外接设备、蓝牙、触控驱动和窗口焦点都会在这里出问题。
  2. 目标进程是否在运行,还是被 CPU 饱和、锁、I/O 或系统调度延后。
  3. handler 是否立即返回;长同步 JSON、布局读取、图像处理和 GC/内存压力都会延后下一帧。
  4. 若 handler 已运行但 UI 没变,才进入状态和渲染树检查。

在浏览器中,DevTools Performance 的 input delay 与 Long Task 能覆盖第 2 到第 3 步;在 Flutter 中,用 DevTools Timeline 区分 UI thread、raster thread 与 Dart 代码。若需要证明 OS 层排队,应使用平台工具,例如 macOS Instruments、Linux perf/ftrace 或 Windows WPA,而不是只看应用控制台。

2. 一帧没有赶上刷新 ​

text
状态改变
  -> layout / paint / display list
  -> command buffer
  -> driver queue
  -> GPU execution
  -> compositor acquire buffer
  -> display scanout

这里的关键不是“60 FPS”,而是某次工作是否在下一次可用呈现窗口前完成。CPU 迟到时,GPU 可能空闲;GPU 迟到时,主线程可能空闲。两种情况的修复方向相反。

证据更像哪类瓶颈下一步
主线程长脚本、layout 或 paint 跨帧应用/引擎 CPU 侧缩小更新和布局范围,拆分长工作
栅格线程忙、进入视口的 tile 未就绪绘制/解码/显存压力降低单 tile 复杂度,控制图片与滤镜
提交已完成但 GPU work 跨帧GPU 或驱动队列检查 overdraw、纹理大小、shader、同步等待
GPU 完成但显示仍旧帧surface/compositor/vsync检查 buffer ownership、合成器和显示路径

3. 视觉与交互不一致 ​

画面上有一个按钮,不代表输入系统或辅助技术也认为它存在。

text
渲染树 / layer tree
      !=
命中测试区域
      !=
焦点顺序
      !=
无障碍语义树

覆盖层、裁剪、变换、透明元素、异步销毁与焦点恢复都可能让四份结构不同步。验证时至少做一次真实点击、Tab 键导航和屏幕阅读器/Accessibility tree 读取。截图只能证明像素。

4. 本地“成功”与服务端事实分离 ​

text
本地状态: submitting(requestId)
  -> 乐观画面: 已保存?  不能直接下结论
  -> 网络与服务端写入
  -> confirmed(version) 或 failed(reason)
  -> 最终画面

渲染链越快,越容易掩盖这个问题。所有可重试写入都需要稳定命令关联;超时不等于失败,也不等于成功。最终验收必须有服务端读回、版本号、审计记录或等价证据。

证据分层 ​

层典型工具能证明什么不能证明什么
应用状态日志、单元测试、请求关联状态机和业务分支GPU 或显示是否呈现
引擎DevTools、Flutter DevTools、渲染树检查layout、paint、frame 调度内核是否真的调度了设备
OSInstruments、perf、ETW/WPA、ftrace线程、CPU、I/O、上下文切换应用业务语义
驱动/GPUGPU trace、厂商 profiler、frame debugger队列、shader、纹理、GPU 时间服务端确认是否正确
显示与用户高刷录屏、present trace、真实交互是否真正翻帧、是否可操作为什么上游生成了错误画面

工具名随平台变,但证据职责不变。不要把某一个 profiler 的一条轨道当成全机真相。

一个完整排障记录应有的内容 ​

text
现象:120Hz 设备滚动列表时偶发白块
条件:设备、OS、GPU、应用版本、刷新率、缓存状态、复现动作
假设 A:主线程阻塞滚动
证据:主线程 2ms,compositor 可滚动;否定 A
假设 B:新图块栅格/图片解码未赶上
证据:raster queue 堆积,白块出现时缺少 ready tile;支持 B
改动:降低首屏图片解码压力并预留尺寸
复测:同条件下白块消失;仍需覆盖低显存设备

这比“优化了滚动性能”有用,因为它说明了问题在哪一段、为什么修复可能有效、在哪些条件下还不确定。

与其他页面的关系 ​

学习规划与研究资料库