EN

Android 中的卡顿丢帧原因概述 - 系统篇

字数统计: 5k阅读时长: 18 min
2019/09/05
loading

技术复核 · 2026-10-13。 本文案例与截图来自 2019 年。此次按 Android 8.1/9/16 的相关源码、Linux v6.6 文档及现行 Android 文档核查,保留原始图片和作者经验;修正将长 slice、核号、内存回收或输入节奏直接等同根因的结论。没有重新运行原设备或回放完整 Trace,缺少的现场条件不作补造。

在应用篇中,我们列举了应用侧的卡顿问题。这一篇沿系统服务、显示、调度和内存路径整理案例。各设备的硬件、内核与厂商实现不同,定位时应保留系统版本和具体配置。

手机厂商和 App 开发者通常会通过优化组、Matrix 或自有监控方案收集线索。系统权限有助于获取更多数据,但采集范围仍受构建类型、内核配置和数据源开关影响。

流畅度不能仅等同于平均 FPS。60 Hz 下的一秒平均 55 FPS,无法说明延迟集中在哪几帧,也不能直接代表所有人的感知;应检查目标节奏、实际呈现间隔和输入响应。静止界面不需要每个刷新周期提交新内容。本系列四篇如下:

Android 平台性能导致的性能案例

下面这些是我在 2019 年整理的实际案例。有些在开发阶段就暴露了,发给用户前还能解决;有些要长时间使用才出现,这一类往往更难查;还有一些只在特殊场景或硬件上出现。

部分能从图中确认等待、合成或回收活动;设备私有实现和最终根因,则需要原始 Trace、日志与源码共同验证。截图没有提供完整系统版本,不把案例归因推广为所有 Android 设备的固定行为。

这些图使用旧 Systrace,可结合 Systrace 系列 阅读。新设备优先采集 Perfetto;Android 12(API 31)起,在采到相应数据时结合 FrameTimeline 检查 expected/actual frame、deadline 与呈现关系。旧图颜色、长 slice、缺少 slice 都只是分析入口。

1.SurfaceFlinger 主线程耗时

SurfaceFlinger 协调图层与合成。如果关键工作未赶上对应帧的 deadline,可能延后呈现;不是某个主线程调用超过固定时长就必然丢一帧。HWC、GPU 和显示驱动可以异步工作,还要看 fence 与实际呈现关系。

dequeueBuffer 可能等待空闲 buffer、分配或同步;queueBuffer 是生产者提交队列的接口,不能把二者一律称为直接阻塞在 SurfaceFlinger 上的 Binder 调用。旧 BufferQueue 可能跨进程,Android 16 的 BLASTBufferQueue 则能建立本地队列并通过 transaction 提交 buffer。具体阻塞位置要看调用链。

前两图展示合成和队列节奏;第三图选区约 28.02 ms,除较长 dequeueBuffer 外还有 allocateBuffers。第四图显示等待后产帧节奏推迟,但不能仅靠这些截图确认是哪一层造成,也不能把整个选区当作 GPU 耗时。

2.屏下光感截图导致 SurfaceFlinger 渲染不及时

原文描述的是某款设备屏下环境光传感器方案,通过获取屏幕内容辅助估计环境光;原文把“屏下光感”写成“屏下指纹”的地方应更正。不能推广为所有屏下光感都频繁截图,也不能与指纹识别流程混为一谈。

如果这类厂商功能同步占用合成线程、锁或 GPU 资源,可能干扰正常帧。下图 211.867 ms 是多帧观察区间,没有显示明确的截图调用或传感器请求;要确认该归因,还需关联请求频率、capture 路径及开启/关闭该功能的受控对照。

3.HWC Service 执行耗时

这里应为 HWC(Hardware Composer)。图中是历史 android.hardware.graphics.composer@2.2-service 的 HIDL 路径,可见 PresentDisplay、DRM Commit/AtomicCommit 等区间。长调用可能包含驱动或 fence 等待,不等于 HWC 一直消耗 CPU。

合成、提交和扫描输出相互依赖,延迟可能向应用队列传播,但不能写成“HWC 一慢,SurfaceFlinger 下一帧必定不合成”。第三图的 CRTC 工作与 HWC 调用重叠,重叠本身也不能证明“CRTC 在等 HWC”;需查看实际等待点和完成信号。第四图是另一组进程记录,不当作同一次测量。

4.CRTC 执行耗时

图中的 crtc_commit 是显示驱动提交相关的内核线程;它的时间片不是面板扫描输出耗时的直接测量。图里同时出现较长 dequeueBuffer、setTransactionState 和 CRTC 活动,可以沿 Binder、锁、fence、驱动完成事件追踪依赖。

显示提交延迟可能让上游等待,但仅凭并行轨道不能确认所有阻塞都是 CRTC 导致,更不能保证“下一帧一定不会合成”。具体调用链随厂商驱动和 Android 版本变化。

5.CPU 调度问题

调度分析要同时看线程状态、CPU 拓扑、允许运行的 CPU、调度策略/优先级、频率与帧 deadline。核号不是核型,小核、低频或迁核本身都不是错误;能否及时完成关键工作才是判断依据。

关键任务的 CPU 容量与频率

当关键任务获得的算力不足时,确实可能错过 deadline。但下图 22.558 ms 是选中的 RenderingResponse 区间,截图未给出完整拓扑和线程状态,不能直接读作“小核上的单帧 CPU 耗时”。

第二图原注释标出 RenderThread 约 1.66 ms / 2.4 GHz 与 8.6 ms / 0.7 GHz。它提示检查频率和调度,却不是控制了相同工作量、缓存、核型与等待时间的单因素实验。可结合 Linux v6.6 EAS 文档 理解容量与能耗取舍;设备是否使用该策略仍需查内核。

优先级低未能及时获取 cpu 时间片导致卡顿

用户关键路径上的线程不一定具有较高调度优先级。若它长时间 Runnable,应检查允许 CPU 上的竞争、策略/优先级、cpuset/affinity 和资源限制;也要检查它是否在等另一个更低优先级的线程。不能只看到某个 nice 值或线程名称就确认优先级反转。

被 RT 线程抢占

这里 RT 指实时调度线程,例如 SCHED_FIFO/SCHED_RR,不是 RenderThread 的缩写,也不是按整个进程一概分类。实时线程占用同一可用 CPU 时,可能延迟普通线程;要用实际策略、抢占与运行区间确认。

Android 9 ActivityManagerService 中确有默认关闭的 sys.use_fifo_ui 路径,为 TOP_APP 的 UI/RenderThread 调整调度。但源码不能支持“因为启动更慢,所以所有版本一直不开”的结论,也不构成把应用线程统一改为实时调度的建议。

大小核调度导致

不合适的 affinity/cpuset 或任务放置可能限制可用算力。在这次 CTS 问题里,我当时的判断是:线程受限于 CPU 7,竞争导致测试超时。但截图没有显示 affinity mask、完整调度策略或 CTS 报告,不能单独验证该根因。

“只有一个核,所以 RenderThread 不做完 UI 线程就只能等”也过于绝对:同核普通线程仍可被抢占、分时运行。应检查实际可用 CPU、谁在运行、等待多久,以及测试期限。

6.触发 Thermal 导致限频

热管理可能限制 CPU/GPU 频率、功耗或持续性能,从而影响复杂场景;不是任何发热或降频都必然造成卡顿。要把 thermal 状态、频率上限和对应帧时间关联起来,不能只凭手感温度或一张频率图判断。

Android 10(API 29)提供 thermal status 接口,Android 11(API 30)增加 getThermalHeadroom。Headroom 是估计值而非温度计,设备支持与有效性需要确认;工程优化应减少持续负载,并在相近温度条件下复测。单纯清后台或重启不能证明问题已修复。

7.后台活动进程太多导致系统繁忙

后台工作可能与前台竞争 CPU、I/O、内存或共享锁,但进程数量本身不是负载指标。应检查实际活跃工作及其与前台关键路径的关系,不能把所有后台进程都当成异常。

CPU 繁忙

dumpsys cpuinfo 是一段时间窗内的统计,不是单帧调度图。下图多个进程超过 100% 可来自多核累计,不能直接当整机占比;还需知道窗口和分母。图中 lowmemorykiller 日志写有 no selected tsk,不能把它当作该次扫描已经杀进程的证据。

线程调度等待:Runnable 状态

Runnable 表示线程可运行但尚未获得 CPU,关键路径上的长等待可能错过 deadline。原因还可能是允许 CPU 范围、优先级或配额,而非所有 CPU 都忙。下图显示的是 UnityGfxDeviceW,不是已确认的 App 主线程;需要选中状态详情与 sched 记录进一步看。

无关进程活跃耗时

判断“无关”要看当前操作的依赖关系,不能按进程名或是否后台直接划分。图中表头为 Wall Duration、Average Wall Duration、Occurrences,汇总了选中区间的墙钟时长与次数,不是 CPU 占用比例;UnityMain、RenderThread 也可能就是前台工作。应回到时间线看这些任务是否在关键线程可用的 CPU 上竞争。

cpu 被占用

低内存时可能增加回收工作。kswapd 是内核后台内存回收线程,HeapTaskDaemon 执行 ART 的堆维护任务;它们不是“疯狂向系统申请内存”的线程。仅凭名称也不能断言每个 HeapTaskDaemon 片段都在 GC,需看具体 GC/trim 埋点。

图中能看到这些线程活跃,不能据此说它们占满了所有 CPU。结合回收事件、PSI、页错误、线程运行时长与前台等待,才能判断资源竞争。

System 锁

图中约 93.633 ms 的选区里有多条 monitor contention,涉及 AMS 的 activityIdle、RecentsAnimation.finishAnimation 等,能支持“存在锁竞争”这一观察。要继续跟踪 owner 线程及其嵌套等待,区分持锁计算、被抢占和等待下游。

不是所有 Binder 请求都携带同一把 AMS/WMS 锁,也不能把整段选区等同某一次持锁时长。只有请求依赖被阻塞的临界区,才会沿该路径传播延迟;现代锁结构与动画实现也需按版本核对。

8.Layer过多导致 SurfaceFlinger Layer Compute 耗时

较多需要遍历、更新或参与可见区域计算的 layer 可能增加合成准备工作,但不能只按后台 App 数量判断。图里可见 rebuildLayerStacks 和可见区域计算,没有给出 layer 数量或一组可控对照。

原文“Android P 才改到 SurfaceFlinger 主线程”的说法不准确:Android 8.1 SurfaceFlinger 的 handleMessageRefresh 已调用 rebuildLayerStacks;Android 9 仍有此路径。Android 16 实现 使用更新后的 layer/CompositionEngine 体系,不能把旧函数名当跨版本接口。应查 layer 生命周期、泄漏和事务更新,不能以“多清后台”代替修复。

9.Input 报点不均匀

触摸事件异常间隔可能影响跟手性,但没有输入不等于主线程不能绘制:动画、滚动惯性、定时任务或 invalidate 都可继续触发更新。InputReader/Dispatcher 的 slice 也不是触屏原始报点的逐条副本,批处理、合并、过滤或采集缺失都要区分。

下图的缩略轨道可用来找时间区间,不能独立证明触屏漏报。应关联原始输入时间戳、Reader/Dispatcher/过滤链、应用收到事件与对应呈现帧,区分设备没产生事件、系统晚分发和 App 晚响应。

10.LMK 频繁工作抢占 cpu

旧文中的 LMK 要与现代用户态 lmkd 区分。AOSP lmkd 文档 说明 Android 10 支持基于 PSI 的压力检测、Android 11 更新了结合 thrashing 等信息的策略,仍依赖内核支持与设备配置。看到回收线程活跃不等于 lmkd 本身占满 CPU。

  1. CPU:反复杀进程与重启可能造成额外开销,但缓存进程被杀后不会保证立即由“主进程”拉起。需用实际 kill、启动记录及 PID/时间匹配确认循环。
  2. Memory:ART GC、内核回收和 lmkd 杀进程是不同机制;系统低内存不能直接推导所有进程同时 GC。
  3. I/O:文件页回收后再访问可能读盘;匿名页可能进入 swap。若使用 zram,压缩页主要存放 RAM,不能把所有 swap 都称为磁盘 I/O,配置了 writeback 时另看底层存储活动。

第一图是聚合列表,不能读作 kill/restart 次数。第二图与第 7 节 HeapTaskDaemon/kswapd 图片相同,作为同一历史片段复用,不是独立复现实验。

11.低内存导致 IO 耗时

内存压力可能通过 direct reclaim、文件页重新载入、swap 或回写增加等待,但需要具体证据,不能把所有启动、GC、JIT 或 D 状态都归为“低内存导致磁盘慢”。Linux v6.6 vmscan 描述回收路径;zram 文档 区分内存压缩与可选 writeback。

下面第一图能看到 NativeAlloc concurrent copying GC、资源加载和较长 traversal,不能仅凭它确认磁盘 I/O。后续几个子场景要分别判断。

主线程 IO 导致卡顿

这张图的 SystemUI 主线程状态明确为 Uninterruptible Sleep - Block I/O,阻塞点为 io_schedule,可以支持该片段存在 I/O 相关等待。具体文件、块设备、回收或锁路径仍需调用栈与 I/O 事件;单独一个 D 状态并不总是磁盘 I/O,更不能直接证明低内存。

主线程 IO 导致应用启动速度慢

第一图包含录屏进程 bindApplication、OpenDexFilesFromOat/Open oat 等启动加载;第二图还有 Application.onCreate、VerifyClass 与 JIT。加载可能包含读盘、页错误、验证、CPU 工作或等待,不能把整个启动区间当作纯 I/O。还要区分进程初始化、首帧与用户可交互时间。

滑动列表时候 IO 导致卡顿

图中 Launcher 主线程选中区间约 57.994 ms,状态为 Uninterruptible Sleep | WakeKill - Block I/O,等待点为 wait_on_page_bit_killable。这支持存在页相关等待,但不能单独确定哪个文件、是否低内存触发,或底层存储是否故障。需结合页错误、回收与块 I/O 时间线。

12.GPU 合成导致 SurfaceFlinger 耗时

HWC 可把部分 layer 指定为 client composition,由 SurfaceFlinger 通过 GPU 合成。这是正常路径之一;是否影响帧取决于像素工作量、GPU 负载、buffer/fence 和显示 deadline,不能说有 GPU 合成就必然慢。参见 SurfaceFlinger 与 HWC。

图中 hasClientComposition 与 FrameMissed 共现,第二图选区约 22.291 ms,还标注了 HWC 调用问题。这不能单独证明 GPU 是根因,也不能把 SurfaceFlinger 的 CPU 侧 slice 或整个选区当作 GPU 执行时长。

13.kswapd 的 CPU 占用

图中 CPU 7 上有较多 kswapd0 活动。CPU 7 是否属于大核要查该 SoC 拓扑,截图也没有温度或限频证据;不能直接推出“跑大核导致发热限频”。应量化回收线程在关键 CPU 上的运行时间、前台 Runnable 延迟和持续内存压力,再判断是否存在竞争。

14.SurfaceFlinger Vsync 不均匀

旧 Trace 的 VSYNC-sf 是调度信号轨道,不等于面板每次实际呈现时间。图中间隔变化值得检查,但刷新率/显示模式切换、调度相位调整、漏采和实现问题都要区分。不能仅看色块宽度就归因某次 SurfaceFlinger 或 HWC 修改。

现代系统支持 多刷新率切换。应同步看模式、目标周期、应用产帧和实际呈现;在支持时结合 FrameTimeline,判断变化是预期策略还是错过了 deadline。

15.三方应用使用 Accessibility 服务导致系统卡顿

部分无障碍功能会启用输入过滤或变换链,路径可能经过 system_server 的处理再转发。Android 9 AccessibilityManagerService 根据放大、触摸探索、按键过滤、手势注入等功能决定是否启用 InputFilter;不是任意 Accessibility 服务或仅监听可访问性事件就必然走同样路径。Android 16 的 AccessibilityInputFilter 也按功能处理事件。

下图提示 Reader 到 Dispatcher 的节奏变化,但不能据此一概判定“没有立即唤醒就是系统错误”。应关联事件时间、过滤/转发和 Handler/Binder 等等待,并对具体功能做受控对照。

总结

下面是我在 2019 年做这些工作时的一些感受。原文写作时的 Android Q 是 Android 10;Android 原生系统持续演进 , 每个版本都会解决非常多的性能问题 , 同时也会引进一些问题 ; 到了手机厂商这里 , 由于硬件差异和软件定制 , 会在系统中加入大量的自己的代码 , 这无疑也会影响系统的性能 .

上面列出的这些影响流畅性的案例 , 只是 Android 系统开发中遇到的性能问题的冰山一角 , 任何一个问题都会对用户的使用产生影响 , 这也是为什么手机厂商越来越重视系统优化 . 手机厂商非常重视开发过程中和用户使用过程中遇到的性能问题 , 并开发和提出各项优化措施 , 从硬件到软件 , 从用户行为优化到系统策略动态学习 . 这也是为什么现在的手机厂商的系统越做越好 , 质量越来越高的一个原因 , 那些不重视质量只重视设计和产品的手机厂商 , 都渐渐地被消费者淘汰了.

大家可以看看这个问题 : 当手机厂商说安卓手机性能优化的时候,他们到底在做什么

这也是流畅性的一个系列文章中的一篇 , 可以点击下面的链接查看本系列的其他文章.

本文知乎地址

由于博客留言交流不方便,点赞或者交流,可以移步本文的知乎界面
知乎 - Android 中的卡顿丢帧原因概述 - 系统篇

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. Android 平台性能导致的性能案例
    1. 1.1. 1.SurfaceFlinger 主线程耗时
    2. 1.2. 2.屏下光感截图导致 SurfaceFlinger 渲染不及时
    3. 1.3. 3.HWC Service 执行耗时
    4. 1.4. 4.CRTC 执行耗时
    5. 1.5. 5.CPU 调度问题
      1. 1.5.1. 关键任务的 CPU 容量与频率
      2. 1.5.2. 优先级低未能及时获取 cpu 时间片导致卡顿
      3. 1.5.3. 被 RT 线程抢占
      4. 1.5.4. 大小核调度导致
    6. 1.6. 6.触发 Thermal 导致限频
    7. 1.7. 7.后台活动进程太多导致系统繁忙
      1. 1.7.1. CPU 繁忙
      2. 1.7.2. 线程调度等待:Runnable 状态
      3. 1.7.3. 无关进程活跃耗时
      4. 1.7.4. cpu 被占用
      5. 1.7.5. System 锁
    8. 1.8. 8.Layer过多导致 SurfaceFlinger Layer Compute 耗时
    9. 1.9. 9.Input 报点不均匀
    10. 1.10. 10.LMK 频繁工作抢占 cpu
    11. 1.11. 11.低内存导致 IO 耗时
      1. 1.11.1. 主线程 IO 导致卡顿
      2. 1.11.2. 主线程 IO 导致应用启动速度慢
      3. 1.11.3. 滑动列表时候 IO 导致卡顿
    12. 1.12. 12.GPU 合成导致 SurfaceFlinger 耗时
    13. 1.13. 13.kswapd 的 CPU 占用
    14. 1.14. 14.SurfaceFlinger Vsync 不均匀
    15. 1.15. 15.三方应用使用 Accessibility 服务导致系统卡顿
  2. 2. 总结
  3. 3. 本文知乎地址
  4. 4. 关于我 && 博客