卡顿、黑屏与无效刷新如何定位并优化
卡顿、黑屏与无效刷新如何定位并优化
显示卡顿不能从 SurfaceFlinger 进程名直接定责。一帧可能先在应用生产、GPU 渲染、 BufferQueue 交接、SurfaceFlinger 合成或 HWC present 的任意阶段错过时限。先找到第一处超时, 再针对同一场景用同一指标回归,才算完成一次有效优化。
不要从屏幕结果反推唯一责任方
| 常见误解 | 正确理解 |
|---|---|
| 屏幕卡就一定是 SurfaceFlinger 慢 | App、GPU、BufferQueue、SurfaceFlinger 和 HWC 都可能先超时 |
| Layer 在列表里,黑屏就不是图形问题 | Layer 还可能没有 Buffer,或被 hidden、crop、父层状态影响 |
改一个 debug.sf.* 属性变顺就是优化成功 | 改属性改变实验条件,可能隐藏根因且不具备跨设备结论 |
| 平均帧率提高就代表偶发卡顿消失 | 平均值会掩盖长尾帧,应比较相同场景的 deadline miss 与帧分布 |
只盯最终到达屏幕的 SurfaceFlinger,容易把上游没有按时交帧误判成合成服务变慢。
先把一帧拆成五段责任
先记住结论:同一个“掉帧”现象,可以由五段不同工作触发。
| 责任段 | 正常职责 | 常见异常信号 |
|---|---|---|
| App 主线程 | 输入、业务、View measure/layout/draw | 长任务、重复布局、错过 App deadline |
| RenderThread / GPU | 录制、提交和执行渲染工作 | GPU completion 晚、大纹理上传、离屏效果过重 |
| BufferQueue / BLAST | 生产、queue、acquire、释放与事务同步 | dequeue 等待、BufferQueue 背压、尺寸格式频繁改变 |
| SurfaceFlinger | commit Layer 状态、规划并执行合成 | SF deadline miss、Layer/transaction 抖动、client composition 压力 |
| HWC / 显示 | 校验设备合成、present、fence 与扫描输出 | present/fence 等待、硬件平面或厂商显示栈限制 |
FrameTimeline 是 Android 图形时序模型。它把一帧的期望时间与 App、SurfaceFlinger 的实际 工作关联起来,帮助判断是 App 没按时生产,还是合成侧没按时呈现。它不是“某个函数耗时” 列表,也不能替代线程调度、GPU 和 fence 细节。
一句话小结:优化前先确定最早异常的责任段,后续层变慢可能只是等待上游的结果。
用证据而不是感觉判断瓶颈
诊断流程必须固定场景、刷新率和采样窗口。否则优化前后样本不是同一个实验。
- ① 固定复现:记录设备、版本、刷新率、温度/功耗状态、操作起止点和目标包。
- ② 先取帧证据:用
framestats看 App 帧分布,用 FrameTimeline 同时看 App 与 SurfaceFlinger,再展开 sched、GPU、BufferQueue 或 fence 轨道。 - ③ 找第一处异常:上游已晚交的帧,不应首先归因到等待它的下游组件。
- ④ 同指标回归:保持输入和采样方式不变,比较 deadline miss、长尾帧和首帧时刻。
| 现象 | 第一份证据 | 优先责任层 | 回归指标 |
|---|---|---|---|
| 偶发卡顿 | FrameTimeline 中异常帧 | 第一处 deadline miss 所在段 | 相同操作的异常帧数与长尾 |
| 持续低帧率 | 帧分布、CPU/GPU 与刷新率轨道 | 稳定超预算的 App/GPU/SF | 相同窗口内帧间隔分布 |
| 窗口黑屏 | WMS + Layer + Buffer/可见状态 | 创建、生产或合成状态缺失处 | 首个有效 Buffer 到可见帧 |
| 首帧慢 | Activity、ViewRootImpl、BLAST、FrameTimeline | 最晚完成的首帧前置阶段 | 同一起点到首帧的时间 |
| 无效刷新/耗电 | invalidate、RenderThread、SF 唤醒轨道 | 重复生产或重复事务发起方 | 静止场景帧数与唤醒次数 |
| Layer 频繁创建 | Layer/transaction trace | App 特殊 Surface 或 Window/Shell | 同场景 create/destroy 次数 |
验证与排障
环境:主机连接目标设备;包名替换为实际应用。普通 adb shell 可读取的字段受 build 类型和 厂商策略限制。
TARGET_PACKAGE="com.example.app"
adb shell dumpsys gfxinfo "$TARGET_PACKAGE" framestats
adb shell dumpsys SurfaceFlinger --list
adb shell dumpsys SurfaceFlingerframestats 提供 App 侧帧阶段时间,适合筛选异常帧,但不能等同于物理屏幕最终扫描时间。 SurfaceFlinger dump 是状态快照;Layer 名称、Buffer、合成类型和 fence 字段可能随版本变化。
需要连续因果链时,使用 Android Studio System Trace 或 Perfetto 录制同一复现场景,优先查看 FrameTimeline、主线程、RenderThread、GPU、SurfaceFlinger、sched 和 binder 轨道。本文未在 目标设备执行录制,因此不声称某个厂商字段必然存在。
卡顿与掉帧怎样优化
App 主线程证据异常: 先移出输入响应和绘制时段中的 I/O、锁等待和长计算;减少重复 measure/layout、无变化的 invalidate() 和一帧内反复更新同一状态。回归时比较相同操作的 App deadline miss 与长尾帧,而不是只看平均 FPS。
RenderThread/GPU 证据异常: 减少过度绘制、大纹理临时上传、昂贵模糊/阴影、超大裁剪 和不必要离屏层。分辨率或效果降级只在画质要求允许时使用,并以 GPU 完成时间和实际呈现 节奏共同回归。
出现 BufferQueue 背压: 生产者长期快于消费者时,dequeue 可能等待可用 slot。先检查 消费者为什么没释放,再检查是否反复改变尺寸/格式或重建 Surface。不能通过无限增加 Buffer 数量解决持续失衡;这只会增加内存和延迟上限。
一句话小结:减少工作量之前先证明是哪类工作超预算,避免优化一段本来就在等待的线程。
黑屏与首帧慢怎样优化
按创建篇的四种状态逐层证明:
- 窗口/Layer 是否存在:WMS 是否创建 SurfaceControl,SurfaceFlinger 列表是否有目标层。
- 是否产生有效 Buffer:ViewRootImpl/BLAST 是否建立,RenderThread 是否完成首帧提交。
- Layer 是否可见:检查 hidden、父层、alpha、crop、z-order,以及安全窗口是否只在截图中黑屏。
- 是否按时 present:用 FrameTimeline 和 fence 等待判断是迟交、迟合成还是显示侧延迟。
优化首帧时,把“Activity 启动”“窗口 Layer 创建”“首个 Buffer 完成”“show transaction”和 “按时 present”分成里程碑。复用可安全复用的对象、缩短首帧关键路径有价值,但不能为了 更快显示而长期展示错误尺寸、旧 Buffer 或未完成内容。
局部闪烁与旧帧怎样优化
局部闪烁通常不是“整个显示系统突然变慢”,而是相关 Layer 的 Buffer 与几何状态没有按预期原子生效。 先在 trace 中对齐目标 Layer 的 Buffer、position、crop、alpha、reparent 与 transaction apply 时刻, 确认是 App 迟交内容、Window/Shell 拆散视觉状态,还是显示侧延迟呈现。
| 证据 | 优化动作 | 边界与回归 |
|---|---|---|
| 新 Buffer 与新尺寸分两帧生效 | 把同一视觉终态放入同一同步 transaction | 不跨越必须等待的 Buffer;回归闪烁帧数 |
| reparent 后短暂露出旧父层内容 | 核对 leash、父层可见性与操作顺序 | 不用提前隐藏掩盖失败;回归逐帧 Layer 状态 |
| 只有截图或录屏出现黑块 | 检查安全 Layer 与捕获策略 | 不以关闭安全标记为优化;分别回归屏幕和捕获结果 |
一句话小结:闪烁优化要恢复 Buffer 与视觉状态的正确原子边界,而不是简单延长动画或遮住旧帧。
无效刷新与 Layer 抖动怎样优化
应用侧: 静止内容不应持续 invalidate;动画停止后取消回调;尺寸与格式稳定时避免重建 Surface。SurfaceView、视频或自定义 native 渲染要分别核对自己的生产节奏。
Window/Shell 侧: 同一视觉状态的 position、crop、alpha、layer 等操作写入同一 SurfaceControl.Transaction,避免逐帧 create/reparent/destroy Layer。合并事务不等于无限 延迟 apply;边界仍是同一帧必须原子生效的一组状态。
SurfaceFlinger/HWC 侧: 先检查 client composition、device composition、fence 和 present 时序,再判断是否为 GPU 带宽、硬件平面能力或厂商显示栈问题。不能从一台设备的 overlay 选择推广出所有 Android 14 设备的优化规则。
优化后必须用同一指标回归
优化前后保持同一设备、系统版本、刷新率、场景脚本、采样长度和热状态。至少记录三类结果:
- 正确性:没有新增黑屏、闪烁、旧帧、裁剪错误或安全内容泄露。
- 时序:目标阶段 deadline miss 和长尾是否下降,首帧里程碑是否前移。
- 资源:CPU/GPU 时间、内存、功耗或 Layer 数量是否把成本转移到别处。
禁止把强制 GPU、强制 HWC、永久修改 debug.sf.*、关闭同步或盲目提高刷新率当成完成方案。 它们只能作为可回滚的对照实验,并且必须恢复原环境再验证真正修复。
源码入口
源码基准:android-14.0.0_r75。
| 文件 | 关键位置 | 作用 |
|---|---|---|
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp | 2507 行 commit() | 记录帧状态并刷新 Layer 快照 |
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp | 2659 行 composite() | 构造显示输出的合成参数 |
frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp | 433 行 Output::present() | 规划、准备、合成并 present |
frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp | 1125 行 chooseCompositionStrategy() | 与 HWC 协商合成策略 |
frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.cpp | 570 行 presentAndGetReleaseFences() | 提交显示并取得 fence |
frameworks/native/libs/gui/BLASTBufferQueue.cpp | 714 行 onFrameAvailable() | 处理可用 Buffer 与事务同步 |