
1. 从一个掉帧现场说起HWC 到底在画面合成的哪一环如果你做过 Android 系统性能优化大概率遇到过这种场景App 本身逻辑不重GPU 占用也不高但滑动列表就是有规律地掉帧用dumpsys SurfaceFlinger一看合成路径里出现了大量 GPU Client 合成而 HWC 合成几乎没怎么干活。这时候问题往往不在应用层而在HWCHardware Composer硬件合成器这一层。先把概念摆清楚。Android 的显示合成链路大致是这样应用通过 Surface 提交图层LayerSurfaceFlinger 负责收集、排序、决定每个图层怎么合成最终把结果送到显示设备。合成这件事有两种做法一种是让 GPU 把所有图层画到一张 buffer 上这叫GPU Client 合成也叫 GLES 合成另一种是把图层直接交给显示控制器硬件去叠加这叫HWC 合成硬件合成、Overlay 合成。HWC 合成的好处很直接不占 GPU、不占带宽、功耗低、延迟小。GPU Client 合成则是兜底方案一旦 HWC 说这几个图层我合不了SurfaceFlinger 就退回 GPU 去画。所谓HWC 退化指的就是本该由 HWC 硬件合成的图层因为各种原因被迫回退到 GPU Client 合成。退化本身不是 bug它是合成框架的正常兜底机制但频繁退化就是性能问题了GPU 负载上升、内存带宽被吃掉、功耗变高、掉帧概率变大。所以防治的核心不是消灭退化而是让退化发生在该发生的时候别在滑动、视频播放、游戏这些关键场景里乱退化。这篇内容适合谁看做 Android 系统开发、Framework/显示子系统、性能优化的工程师以及被掉帧和功耗问题折磨的 ROM 开发者。我会从 HWC 的判定逻辑讲起把退化的常见成因、排查手段、防治策略一条条拆开尽量给到能直接上手复现的操作。需要说明的是不同 SoC 平台高通、联发科、展锐等的 HWC 实现差异很大下面涉及的参数和命令以通用 AOSP 逻辑为主具体平台需要对照厂商文档做适配这部分我会明确标注。2. HWC 的判定逻辑它凭什么拒绝一个图层要治退化先得知道 HWC 在什么情况下会拒收图层。HWC 不是随便就能合成任意图层的它有一堆硬性约束任何一个不满足这个图层就得走 GPU。理解这些约束是后面所有排查和优化的基础。2.1 图层数量与硬件叠加层上限最直观的一条显示控制器能同时叠加的图层数量是有限的。早期设备可能只有 2 到 4 个 overlay 通道现在主流平台能到 8 个甚至更多但这个上限始终存在。当一帧里的图层数超过硬件通道数多出来的图层就必须由 GPU 合成。这里有个容易被忽略的点图层数量不等于可见 Surface 数量。SurfaceFlinger 在送 HWC 之前会做一轮预处理比如把一些不透明图层做裁剪、把被完全遮挡的图层剔除。所以你在dumpsys SurfaceFlinger里看到的图层数和最终 HWC 实际处理的图层数可能不一样。排查时要以 HWC 那一侧的输入为准而不是应用提交了多少个 Surface。提示图层数超限导致的退化典型特征是图层一多就退化图层少就正常。如果你发现某个页面固定在某些操作后开始掉帧先数一数那一刻的图层数量。2.2 格式、缩放与旋转的硬约束HWC 对图层格式有要求。它通常支持 RGBA8888、RGBX8888 这类常见格式但对一些特殊格式比如某些 YUV 格式、带 alpha 的特殊排布支持有限。视频播放场景里如果解码输出的格式 HWC 不认就会退化成 GPU 合成这也是为什么有些视频播放器一播放就功耗飙升。缩放和旋转也是重灾区。很多 HWC 硬件对缩放比例有范围限制比如只支持 1/4 到 4 倍之间的缩放超出范围就拒绝。旋转同理硬件可能只支持 0/90/180/270 这几种正交旋转任意角度旋转就得 GPU 来。所以一个带任意角度旋转动画的 View在动画过程中很容易触发退化。2.3 遮挡关系与混合模式HWC 处理图层叠加时对遮挡和混合有要求。如果上层图层是半透明的HWC 需要做 alpha blending大部分硬件支持但如果混合模式比较特殊比如某些 blend mode、颜色矩阵变换硬件可能不支持。另外如果图层之间有复杂的遮挡关系硬件叠加的判定也会变复杂某些实现会选择直接退回 GPU 求稳。2.4 安全图层与受保护内容涉及 DRM 保护内容的图层比如某些流媒体视频通常要求走 secure overlay 通道。如果 secure 通道被占用或者配置不对这类图层也可能退化。这一块和具体平台强相关排查时需要看厂商的 HWC 日志。把上面这些约束整理成一张表排查时对照着看会快很多约束类型典型限制退化表现图层数量超过硬件 overlay 通道数图层多时固定退化像素格式不支持某些 YUV/特殊格式视频播放时退化缩放比例超出硬件支持范围缩放动画时退化旋转角度仅支持正交旋转任意角度旋转时退化混合模式不支持特殊 blend/颜色变换特定 UI 效果时退化安全内容secure 通道占用或配置错误受保护视频退化这张表不是让你背而是给你一个退化归因的检查清单。实际排查中大部分退化都能归到其中一两类。3. 退化是怎么被观测到的从 dumpsys 到 systrace 的完整链路光知道原理不够得能看见退化。这一节讲怎么把退化抓现行包括命令、日志字段和判断方法。这部分是实操重点建议边看边在设备上敲。3.1 dumpsys SurfaceFlinger 里的关键信息最常用的入口是dumpsys SurfaceFlinger。这个命令输出很长重点看合成相关的部分。较新版本的 Android 里输出中会有 HWC 的合成统计包括每个显示设备上 HWC 合成了多少图层、GPU 合成了多少图层。adb shell dumpsys SurfaceFlinger在输出里搜索HWC或者Composition相关的段落。你会看到类似Display 0 HWC layers: X, Client layers: Y这样的统计。Client layers 就是被 GPU 合成的图层数这个数字持续大于 0就说明存在退化。另一个有用的命令是带--display-id参数的版本可以只看某个显示设备adb shell dumpsys SurfaceFlinger --display-id注意不同 Android 版本和厂商定制dumpsys 的输出格式差异很大。如果找不到 HWC 统计字段可以试试dumpsys SurfaceFlinger --latency看帧延迟或者直接看厂商提供的调试节点。3.2 用 systrace/perfetto 看合成路径dumpsys 给的是统计快照要看时序就得用 systrace 或 perfetto。抓一段滑动操作的 trace在 SurfaceFlinger 的 track 里找onMessageInvalidate、composite这些 slice展开后能看到这一帧走了 HWC 还是 GPU。具体操作# 用 perfetto 抓取新版本推荐 adb shell perfetto -o /data/misc/perfetto-traces/trace.pftrace -t 10s \ sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory # 拉取到本地 adb pull /data/misc/perfetto-traces/trace.pftrace抓到的 trace 用 Perfetto UI 打开在 SurfaceFlinger 进程下找HWC相关的 slice。如果看到GPU composition或者Client composition的 slice 频繁出现就定位到退化了。3.3 一个真实的排查案例我之前遇到过一个列表滑动掉帧的问题。现象是静止时流畅一滑动就掉帧而且掉帧有规律大概每滑几屏就卡一下。第一步dumpsys SurfaceFlinger看统计发现 Client layers 在滑动时从 0 跳到 3 左右。说明滑动时确实有图层退化了。第二步抓 perfetto看退化发生在哪一帧。发现退化集中在列表快速滚动、新 item 进入屏幕的那几帧。第三步数图层。这个列表每个 item 有背景、图片、标题、副标题、分割线加上顶部悬浮的搜索栏、底部导航栏一屏内图层数轻松超过 8 个。而设备的 HWC overlay 通道只有 6 个。图层数超限就是根因。第四步验证。把列表 item 的多个 TextView 合并成一个自定义 View 绘制减少图层数退化明显减少掉帧也缓解了。这个案例说明图层数超限是最常见、也最容易通过 UI 结构调整解决的退化原因。4. 退化的常见成因分类与逐项排查上一节讲了怎么观测这一节把成因系统化。我把退化成因分成四大类图层数量类、格式与变换类、资源竞争类、配置与策略类。每一类给出排查方法和判断依据。4.1 图层数量类退化这是最普遍的一类。判断方法很简单看退化是否和图层数量正相关。你可以做一个实验在一个页面里逐步增加可见 View观察 Client layers 的变化。排查步骤用dumpsys SurfaceFlinger记录当前页面的图层数。用 Layout Inspector 或者dumpsys activity top看 View 树找出哪些 View 是独立图层。重点看那些本可以合并却独立成层的 View比如带alpha的 View、带elevation的 View、SurfaceView、TextureView。这里有个经验elevation和alpha是图层杀手。一个 View 只要设置了非零 elevation 或者非 1.0 的 alpha系统就可能给它单独开一个图层RenderNode。列表里如果每个 item 都有阴影图层数会迅速膨胀。4.2 格式与变换类退化这类退化的特征是特定内容才退化。比如只有播放视频时退化只有做旋转动画时退化。排查方法视频场景看解码器输出格式对比 HWC 支持的格式列表。可以用dumpsys media.codec看解码器状态。旋转场景把旋转角度改成 90 的整数倍看退化是否消失。如果消失说明是任意角度旋转导致的。缩放场景把缩放比例调整到 1:1 或者常见比例看退化是否改善。4.3 资源竞争类退化这类比较隐蔽。HWC 的 overlay 通道是有限资源多个显示设备、多个 secure 通道会互相抢占。比如外接显示器时主屏的 overlay 通道可能被分走导致主屏退化。还有一种情况是带宽竞争。当 GPU 和显示控制器同时抢内存带宽时HWC 可能主动放弃一些图层避免带宽打满导致更严重的卡顿。这类退化在低端设备上尤其常见。4.4 配置与策略类退化这类是人为造成的。比如 HWC 的某些优化开关被关掉了或者 SurfaceFlinger 的合成策略配置不当。常见的配置点debug.sf.hwc.min.duration控制 HWC 合成的最小间隔配置不当会导致 HWC 被跳过。debug.sf.enable_hwc_vds虚拟显示相关的 HWC 开关。厂商自定义的 HWC 策略节点通常在/sys或/proc下。提示改这些配置前一定要确认平台文档乱改可能导致显示异常甚至黑屏。建议在可恢复的环境里测试。把四类成因和排查手段整理一下成因类别判断依据首选排查手段图层数量类退化与图层数正相关dumpsys 数图层 Layout Inspector格式变换类特定内容才退化对比格式支持列表 改角度/比例验证资源竞争类多显示或高负载时退化看带宽统计 断开外接设备对比配置策略类改配置后行为变化查厂商文档 逐项开关验证5. 防治策略从 UI 结构到系统配置的层层设防知道了成因防治就有方向了。我把防治策略按改动成本从低到高排序优先做成本低、收益高的。5.1 应用层减少不必要的图层这是性价比最高的一招。核心思路是能合并的 View 就合并能不用独立图层的就不用。具体做法去掉不必要的elevation。阴影可以用 drawable 或者自定义绘制实现不一定非要 elevation。慎用alpha动画。如果只是淡入淡出考虑用ViewPropertyAnimator配合硬件层动画结束后及时清理 layer。列表 item 里的多个 TextView如果样式固定可以合并成一个自定义 View 用 Canvas 绘制。避免滥用SurfaceView和TextureView。视频播放优先用SurfaceView它对 HWC 更友好。我实测过一个列表页把 item 里的 5 个 TextView 合并成 1 个自定义 View 后图层数从 12 降到 6退化基本消失滑动帧率从 45 提升到 58 左右。这个收益非常可观。5.2 系统层合理配置 HWC 策略系统层的调整需要 Framework 或 ROM 权限。核心是让 HWC 在关键场景优先工作。一个常见做法是给关键场景比如视频播放、游戏设置 HWC 优先级让这些场景的图层优先走硬件合成。具体实现依赖平台通常通过 HWC 的setLayerCompositionType接口或者厂商的策略配置。另一个做法是调整 SurfaceFlinger 的合成决策。AOSP 里 SurfaceFlinger 会根据 HWC 的getChangedCompositionTypes返回值决定哪些图层走 GPU。如果发现 HWC 过于保守可以检查它的能力上报是否准确。5.3 平台层HWC 实现本身的优化这一层是 SoC 厂商的领域但作为系统开发者你可以推动厂商优化。常见的优化点包括扩大 overlay 通道数硬件限制改不了。优化格式支持列表增加对常见视频格式的硬件支持。改进缩放和旋转的硬件支持范围。优化带宽调度减少资源竞争导致的退化。如果你在做 ROM 定制可以和厂商 FAE 沟通这些点通常厂商会有对应的 patch 或配置。5.4 一个防治效果的验证方法改完之后怎么验证有效我一般用这套组合用dumpsys SurfaceFlinger对比改动前后的 Client layers 统计。用 perfetto 抓同样操作的 trace对比 GPU composition slice 的数量。用dumpsys gfxinfo看应用的帧耗时分布。有条件的话用功耗仪测同样操作的功耗变化。四个指标里Client layers 和 GPU composition slice 数量是最直接的。如果这两个明显下降说明防治生效了。6. 几个容易踩的坑和我的实操心得最后这部分是我踩过的坑和一些不太写在文档里的经验分享出来希望能帮你少走弯路。第一个坑以为退化一定是坏事。其实不是。有些场景下 GPU 合成反而更合适比如图层内容频繁变化、HWC 合成需要额外拷贝的情况。盲目追求零退化可能适得其反。判断标准应该是这个场景下哪种合成方式总代价更低而不是退化数越少越好。第二个坑只看 dumpsys 不看时序。dumpsys 是快照可能刚好抓到一个正常帧。一定要结合 perfetto 看时序确认退化是持续的还是偶发的。偶发退化往往不是大问题持续退化才需要处理。第三个坑忽略厂商定制。AOSP 的 HWC 逻辑和厂商实现可能差很远。我遇到过一个平台它的 HWC 对某个格式的支持在文档里写了支持实际却会退化最后是厂商确认了实现上的 bug。所以排查时不要完全信文档要以实测为准。第四个坑改配置不备份。HWC 相关配置改错了可能导致显示异常一定要在能恢复的环境里操作改之前记录原始值。一个实用技巧如果你怀疑是图层数超限可以临时把一些 View 的visibility设为GONE快速验证图层数对退化的影响。这个方法比改代码重新编译快得多适合快速定位。还有一个经验关注系统版本差异。Android 12 之后 SurfaceFlinger 的合成逻辑有较大调整HWC 的交互方式也变了。同一个问题在不同版本上的表现可能完全不同排查时先确认系统版本再对照对应版本的 AOSP 源码看合成逻辑。关于工具除了 dumpsys 和 perfettodumpsys SurfaceFlinger --latency对看帧延迟很有用dumpsys gfxinfo对看应用侧渲染耗时很有用。这几个工具配合起来基本能覆盖从应用到显示的整条链路。真正把 HWC 退化问题吃透靠的不是记住某个命令而是理解图层从应用到显示的完整流转过程知道每一步可能在哪里出问题。这个过程需要反复实践多抓几次 trace多对比几次改动前后的数据手感自然就来了。