不同的人对流畅性(卡顿掉帧)有不同的理解,对卡顿阈值也有不同的感知,所以有必要在开始这个系列文章之前,先把涉及到的内容说清楚,防止出现不同的理解,也方便大家带着问题去看这几篇问题,下面是一些基本的说明
- 对手机用户来说,卡顿包含了很多场景,比如在 滑动列表的时候掉帧、应用启动白屏过长、点击电源键亮屏慢、界面操作没有反应然后闪退、点击图标没有响应、窗口动画不连贯、滑动不跟手、重启手机进入桌面卡顿 等场景,这些场景跟我们开发人员所理解的卡顿还有点不一样,开发人员会更加细分去分析这些问题,这是开发人员和用户之间的一个认知差异,这一点在处理用户(或者测试人员)的问题反馈的时候尤其需要注意
- 对开发人员来说,可以先按 流畅度(画面节奏不连贯)、响应速度(操作后等待过长)和 稳定性(崩溃、ANR 等)整理反馈。这些现象可能重叠;点击没有响应不一定是稳定性故障,需要结合输入、应用状态和日志进一步分类。
- 技术上,这三类问题并不是同一个 Message 超时机制的不同阈值。主线程长任务会同时影响输入和绘制,但 GPU、SurfaceFlinger、显示链路、线程调度或远端服务也可能造成延迟。ANR 由具体输入/组件等超时规则触发,崩溃则另有异常或信号等原因;不能仅凭一次长 Message 判定它们。
- 流畅性这个系列主要是分析流畅度相关的问题,响应速度和稳定性会有专门的文章介绍,在理解了流畅性相关的内容之后,再去分析响应速度和稳定性问题会事半功倍
- 流畅性这个系列主要是讲如何使用 Systrace (Perfetto) 工具去分析,之所以 Systrace 为切入点,是因为影响流畅度的因素很多,有 App 自身的原因、也有系统的原因。而 Systrace(Perfetto) 工具可以从一个整机运行的角度来展示问题发生的过程,方便我们去初步定位问题
版本说明(2026-10-14 核查):本文发表于 2021 年,保留旧 Systrace 截图和附件。下文框架代码按 AOSP android-10.0.0_r1 核对,不代表各厂商截图对应的完整实现。60 Hz / 16.67 ms 是示例,具体帧预算和 Buffer 路径需按系统版本与采集数据判断。新采集优先使用 Perfetto,Android 12(API 31)及以上可结合 FrameTimeline;没有相应数据时不能倒推现代帧指标。
本文目录
Systrace 系列文章如下
- Systrace 简介
- Systrace 基础知识 - Systrace 预备知识
- Systrace 基础知识 - Why 60 fps ?
- Systrace 基础知识 - SystemServer 解读
- Systrace 基础知识 - SurfaceFlinger 解读
- Systrace 基础知识 - Input 解读
- Systrace 基础知识 - Vsync 解读
- Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解
- Systrace 基础知识 - MainThread 和 RenderThread 解读
- Systrace 基础知识 - Binder 和锁竞争解读
- Systrace 基础知识 - Triple Buffer 解读
- Systrace 基础知识 - CPU Info 解读
- Systrace 流畅性实战 1 :了解卡顿原理
- Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析
- Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问
- Systrace 响应速度实战 1 :了解响应速度原理
- Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例
- Systrace 响应速度实战 3 :响应速度延伸知识
- Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇
- Systrace 线程 CPU 运行状态分析技巧 - Running 篇
- Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇
Systrace (Perfetto) 工具的基本使用如果还不是很熟悉,那么需要优先去补一下上面列出的 Systrace 基础知识系列,本文假设你已经熟悉 Systrace(Perfetto)的使用了
了解卡顿原理
卡顿现象及影响
本文主要分析连续动画和滚动中的画面节奏。fps(frames per second)是一定时间内的帧数,必须说明统计的是应用生成、提交还是屏幕呈现的帧。假设目标确实是每秒呈现 120 个新画面,一秒只呈现 110 个,说明没有维持目标吞吐量;但仅凭平均 fps 不能确定哪一帧晚了、停顿分布如何或用户是否察觉。稳定的 60 fps 和平均 60 fps、间隔忽长忽短,体验并不相同。
同时掉帧的原因非常多,有 APP 本身的问题,有系统原因导致卡顿的,也有硬件层的、整机卡的,这个可以参考下面四篇文章
- Android 中的卡顿丢帧原因概述 - 方法论
- Android 中的卡顿丢帧原因概述 - 系统篇
- Android 中的卡顿丢帧原因概述 - 应用篇
- Android 中的卡顿丢帧原因概述 - 低内存篇
用户在使用手机的过程中,卡顿是最容易被感受到的
- 偶尔出现的小卡顿会降低用户的使用体验,比如刷微博的时候卡了一下,比如返回桌面动画卡顿这种
- 整机出现卡顿的则会让手机无法使用
- 现在的高帧率时代,如果用户习惯了 120 fps ,在用户比较容易感知的场景下突然切换到 60 fps,用户也会有明显的感知,并觉得出现了卡顿
所以不管是应用还是系统,都应该尽量避免出现卡顿,发现的卡顿问题最好优先进行解决
卡顿定义
应用一帧渲染的整体流程
为了知道卡顿是如何发生的,我们需要知道应用主线程的一帧是如何工作的
从执行顺序的角度来看
下面以 View/HWUI 硬件加速路径为例,从 Choreographer 的帧回调,经 UI Thread、RenderThread 和 GPU,到 SurfaceFlinger/HWC 再到屏幕呈现。CPU 提交完成、GPU 完成、合成完成和实际呈现是不同的时刻;绘制与合成可以跨帧流水重叠,不能把全过程限制在图里的一个 16.6 ms 格子。

图中 RenderThread 框里的“GPU”标注不准确:RenderThread 是运行在 CPU 上的线程,负责准备/提交图形命令,GPU 异步执行。issue 应理解为提交命令,不是“命令问题”;swap buffer 也不等于 GPU 已完成或屏幕已显示。图中的 measure/layout 只在需要时发生。
从 Systrace 的角度来看
上面的流程图从 Systrace (Perfetto)的角度来看会更加直观

这张旧图展示 OPPO Launcher 的一次流水过程,不能只按红箭头的先后认定同一帧的因果链。需要核对窗口/Layer、buffer 身份和 fence:队列计数上升不是 GPU 完成证据,HWC 调用结束也不等于已扫描到屏幕。长耗时应沿实际依赖检查应用主线程、RenderThread、GPU、SystemServer、SurfaceFlinger 和调度状态。
卡顿定义
本文把卡顿理解为:在预期连续更新的场景中,画面没有按预期节奏呈现,出现迟到、停顿或不均匀推进。原文“没有绘制”只是可能的中间现象,不能代替最终呈现判断。静态页面、主动限帧或低帧率内容不需要每个屏幕刷新周期都产生新 Buffer。
下图标出的 UI Thread 空档是一个待查点。先确认当时确实应该更新,再追踪对应窗口的提交和呈现;已有排队帧、RenderThread 独立动画、SurfaceFlinger 的 Layer 属性动画,都可能使“主线程没画”与“屏幕没动”不同。原图的 Jank 箭头不能单独作为判定依据。

可以从三个层面收集证据,但它们不是三个等价的判定公式:
- 画面:预期每次刷新都有新内容时,重复画面是停顿线索;30 fps 视频在 60 Hz 屏幕上重复一帧则可能完全正常。平均 fps、刷新次数和内容变化次数要分开。
- SurfaceFlinger/显示链路:检查目标 Layer 是否及时有可用内容、acquire fence 是否满足、合成及呈现是否错过期限。没有新 Buffer 时仍可使用上一张 Buffer 合成,或只改变 Layer 属性;SurfaceFlinger 也可能因没有任何更新而休眠。
- 应用:检查帧开始、UI 工作、RenderThread 提交和 GPU 完成是否符合本帧的计划。一个 Vsync 周期内缺少
queueBuffer不是充分证据;及时 queue 也不能保证 GPU、合成或呈现及时。
主线程长任务是常见排查入口,但不能预设所有问题都在主线程。即使 CPU 片段较短,也可能存在 Runnable 等待、锁/Binder 等待、GPU 或显示链路延迟。
原文所说的 逻辑卡顿,是指本该推进的动画/内容没有推进,即使新的 Buffer 仍持续提交。这是对内容异常的描述,不是 FrameTimeline 的统一分类:先排除静态内容、视频节奏和有意重复,再检查应用状态及时间步进;仅凭截图相同不能直接锁定代码根因。
Android 12 及以上:结合 FrameTimeline
在采集到 android.surfaceflinger.frametimeline 的 Perfetto trace 中,按窗口/Layer 和 token 关联 Expected/Actual Timeline,检查预期与实际呈现、jank_type 和 on_time_finish。应用 Actual 的结束涉及 GPU 完成和提交,不只是 doFrame 的 CPU 片段。Buffer Stuffing 还可能表现为画面连续但输入延迟增加。支持范围和字段随系统/采集条件变化;缺轨道不是“零卡顿”。参见 Perfetto FrameTimeline 文档。
若只有旧 Systrace,就把线程、队列和可获得的 fence 证据串起来,并明确哪些呈现时刻未知。ANR 应另按 官方诊断指南 核对触发类型;例如异步广播还可能在主线程空闲时等待 PendingResult.finish()。
系统运行机制简介
由于卡顿的原因比较多,如果要分析卡顿问题,首先得对 Android 系统运行的机制有一定的了解,下面简单介绍一下分析卡顿问题需要了解的系统运行机制:
- App 主线程运行原理
- Message、Handler、MessageQueue、Looper 机制
- 屏幕刷新机制和 Vsync
- Choreographer 机制
- Buffer 流程和 TripleBuffer
- Input 流程
系统机制 - App 主线程运行原理
普通应用进程从 Zygote 派生后,经过运行时初始化进入 ActivityThread.main。以下是 AOSP android-10.0.0_r1 的相关节选,省略号注释代表未展示的系统代码,不是可复制到应用中运行的独立示例。prepareMainLooper() 创建 Looper/MessageQueue;ActivityThread 的实例字段初始化会创建其 Handler,不是 prepare 一次创建三者。
1 | // frameworks/base/core/java/android/app/ActivityThread.java |
主线程完成初始化后,Looper 从队列取可执行 Message,交给目标 Handler 串行分发。Application、Activity、Service 和通常的 BroadcastReceiver 回调主要在主线程,但不能据此说组件的所有方法都在主线程:ContentProvider 的 onCreate() 在主线程,远程数据访问通常由 Binder 线程处理,需支持并发;广播也可指定调度 Handler。下面只节选 H 的部分常量和 BIND_APPLICATION 分支。
1 | // frameworks/base/core/java/android/app/ActivityThread.java |
这部分可以看 Android Systrace 基础知识 - MainThread 和 RenderThread 解读 这篇文章
系统机制 - Message 机制
Looper.loop() 会取出当前可执行的 Message;没有可执行项时才进入等待,不保证刚进入 loop 就一定休眠。延迟消息可能尚未到期,同步屏障也可能挡住同步消息。输入和显示事件还会经 native Looper 的文件描述符回调进入,并非所有唤醒都对应一条普通 Java Message。
从下图可以看到 ,Android Message 机制的核心就是四个:Handler、Looper、MessageQueue、Message

网上有很多关于 Message 机制代码细节的分析,所以这里只是简单介绍 Message 机制的四个核心组件的作用
- Handler:把 Message/Runnable 交给指定 Looper 的线程执行;创建 Handler 本身不创建线程。隐式选择当前 Looper 的旧构造方法已弃用,没有当前 Looper 时还会抛异常。应用代码应明确使用如
new Handler(Looper.getMainLooper())的目标。 - Looper:取出可执行 Message,调用其 target 的
dispatchMessage();Runnable、Handler.Callback 与handleMessage()有各自分发路径。setMessageLogging()可观察分发前后,传统 APM 会利用它,但测到的墙钟时长可包含等待,不覆盖队列等待、GPU 和呈现全过程,日志本身也有开销。 - MessageQueue:在这里的 Android 10 实现中按执行时间管理消息,并处理屏障/异步消息。
nativePollOnce通向 native Looper 等待,可能由事件、唤醒或到期时间结束;不是只在队列完全为空时等待,也不是任何入队都立即执行。异步 Message 仍在同一 Looper 线程执行,不是新后台线程。 - Message:携带
what、arg1、arg2、obj或 Runnable callback 等信息,实际处理由目标 Handler 决定。
具体合同见 Handler API 和 ContentProvider 线程说明。框架内部结构需按系统版本查源码。
系统机制 - 屏幕刷新机制 和 Vsync
首先我们需要知道什么是屏幕刷新率,简单来说,屏幕刷新率是一个硬件的概念,是说屏幕这个硬件刷新画面的频率:举例来说,60Hz 刷新率意思是:这个屏幕在 1 秒内,会刷新显示内容 60 次;那么对应的,90Hz 是说在 1 秒内刷新显示内容 90 次
与刷新率对应,FPS 描述每秒帧数,统计口径可在应用生成、提交或屏幕呈现等阶段。60 Hz 屏幕可以承载 30 fps 内容;应用也可能生成过多帧,其中一些没有呈现。Android 11 引入的 多刷新率支持 还允许系统结合显示模式、策略和内容需求选择刷新率,不能认为硬件频率永远固定或与应用 fps 一一对应。
Vsync(Vertical Synchronization)为显示更新及图形流水提供时序参考,帮助协调提交与呈现。它不会保证应用生成足够的帧,也不能靠一个开关消除所有卡顿。
| 目标节奏 | 相邻目标帧间隔 |
|---|---|
| 60 fps | 约 16.67 ms |
| 90 fps | 约 11.11 ms |
| 120 fps | 约 8.33 ms |
这些是周期,不是所有线程通用的超时阈值。实际可用预算取决于帧开始、调度预测、流水深度、GPU 与合成期限;即使每段 CPU 工作都小于 16.67 ms,也不能据此证明没有迟到呈现。
延伸阅读:
系统机制 - Choreographer
Choreographer 是应用帧回调的协调器。需要动画或遍历时,它请求帧时机,再按阶段执行到期回调;静态界面无需每次物理刷新都绘制。60 Hz 只是例子,不应在应用中硬编码 16.6 ms。回调的帧时间用于动画计算,也不是回调开始执行或最终呈现的保证时刻。
Choreographer 承接上下游:
- 处理回调:Android 10 的顺序包括 Input、Animation、Insets Animation、Traversal、Commit。图只画了主要阶段;不是所有输入都等待 Input 阶段,也不是每帧都需 measure/layout。迟执行的日志是线索,不是屏幕最终掉帧统计。
- 请求显示时序:通过 FrameDisplayEventReceiver 请求/接收 Vsync,再向所属 Looper 安排异步消息执行
doFrame。这里的“异步”能越过同步屏障,但不能抢占正在执行的主线程任务。应用公开入口可用 Choreographer.postFrameCallback(API 16 起),该回调执行一次,持续动画需按生命周期继续请求。
下图就是 Vsync 信号到来的时候,Choreographer 借助 Message 机制开始一帧的绘制工作流程图
这部分详细的流程可以看 Android 基于 Choreographer 的渲染机制详解 这篇文章

系统机制 - Buffer 流程和 TripleBuffer
BufferQueue 连接图形 Buffer 的生产者和消费者,交换的是 Buffer 的使用权、元数据与同步信息,不是每次 queue 都复制整张图片。生产者和消费者可以在不同进程,也可以位于同一进程。下面先用旧式应用 Surface → SurfaceFlinger 的队列模型解释。

旧式窗口 Surface 路径中,应用是生产者,SurfaceFlinger 消费其提交的图形内容:
dequeueBuffer()取得可写槽位/Buffer;不是“释放 Buffer”。需要遵守返回的同步 fence,才能安全复用内存。- 应用通过 CPU 或 GPU 生成内容,
queueBuffer()提交 Buffer 和就绪 fence。GPU 工作可能仍在执行;queue 完成不等于像素已经全部可读。 - 消费者通过
acquireBuffer()取得内容及其 fence,并在正确的同步条件下使用。SurfaceFlinger 会结合计划时刻选择适合的内容,并不是每个 Vsync 都必然 acquire 一张新图。 - 消费方不再需要旧 Buffer 后,通过 release 及相应 fence 交还复用条件。CPU 上 release 调用返回,不保证显示/GPU 对这块内存的访问已结束。
图形内存和同步合同见 BufferQueue 与 同步框架。一个应用可以有多个 Surface/队列,容量与实际分配量由具体路径和两端配置决定,不是“每个应用固定三个 Buffer”。
现代路径补充:AOSP android-16.0.0_r1 的 BLASTBufferQueue 在客户端 acquire Buffer,再通过 SurfaceControl Transaction 的 setBuffer 把 Buffer 和 fence 交给 SurfaceFlinger。因此,旧图“队列消费者就在 SurfaceFlinger”不能直接套到所有现代窗口。
下图是双 Buffer 和 三 Buffer 的一个对比图

三 Buffer 可以增加生产和消费之间的重叠空间:
- 减少等待空闲 Buffer:当一张还在消费、另一张正在生成时,多一个可用 Buffer 可能让生产者继续工作。dequeue 等待常见于 RenderThread/生产线程,也可能经同步传到主线程。
- 缓冲短时波动:已经排队且及时就绪的内容,可能维持某个显示周期的更新。图中的“两次重复减少到一次”只对应所画的时序,不是通用收益;它仍画出了第一处重复帧。
- 无法消除持续瓶颈:更多 Buffer 不会缩短 GPU 执行,也不能修复持续超过预算的生产/合成。App 与 SurfaceFlinger 的 Vsync 可以有不同相位,不能假定同时唤醒。GPU 命令由应用图形栈提交,BufferQueue 不是等待 SurfaceFlinger 来启动 GPU 的任务队列。
代价除内存外还有潜在的排队延迟。队列越深,用户输入对应的画面可能越晚呈现;应同时看流畅度与响应时间,不是 Buffer 越多越好。
这部分详细的流程可以看 Android Systrace 基础知识 - Triple Buffer 解读 这篇文章
系统机制 - Input 流程
点击、移动等输入是常见的事件来源。旧图中的 InputReader 和 InputDispatcher 是 system_server 内的 native 线程;线程位置和 trace 标签仍需按具体系统核对。长按则通常由手势逻辑根据按下时刻与后续事件判定,不是 EventHub 直接读到一个“长按”硬件事件。
- InputReader 从 EventHub 读取设备事件,完成解释/转换后送往分发链路。
- InputDispatcher 根据焦点、触摸目标和策略等选取目标,发到相应窗口/monitor 的 InputChannel;不是向所有应用广播。
- OutboundQueue 是某个 Connection 尚待发布的事件队列,不能当作整个应用的全部输入。
- WaitQueue 记录已发布但尚待完成确认的事件;完成确认可以是
handled=false,不是只有“处理成功”才出队。队列长度也不是耗时。 - PendingInputEventQueue 是应用 ViewRootImpl 的待投递队列。Android 10 在调用
deliverInputEvent前就减计数,归零不表示处理或屏幕反馈已完成。 - deliverInputEvent 是事件投递到应用输入阶段的异步 trace 区间,结束于 finishInputEvent;不是线程唤醒时刻,区间也未必都是 CPU 执行。
- 旧查看器的 Input Response / Rendering Response 是交互分组,不能直接当作物理触摸到屏幕发光的完整延迟。
- 应用处理输入后可能请求更新。手指释放后是否继续 fling 取决于速度、边界和手势逻辑;不能由 UP 一律推断 fling。输入完成 ACK 也不等于新画面已呈现。
上面流程对应的 Systrace 如下

这张图来自魅族 Launcher,前文另有 OPPO Launcher 与 Matrix 示例,不能拼成同一设备、同一次采集的因果链。图中的绿色 F 是旧查看器的帧分析标记,不独立证明 GPU 完成或最终显示正常。
这部分详细的流程可以看 Android Systrace 基础知识 - Input 解读 这篇文章
系列文章
附件
附件已经上传到了 Github 上,可以自行下载:https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action
- xiaomi_launcher.zip : 桌面滑动卡顿的 Systrace 文件,这次案例主要是分析这个 Systrace 文件
- xiaomi_launcher_scroll_all_the_time.zip : 桌面一直按着滑动的 Systrace 文件
- oppo_launcher_scroll.zip :对比文件
2026-10-14 核查时,上述三个 ZIP 均可下载,CRC 校验通过,内含旧 Systrace HTML 和 ftrace 数据。它们是历史采集,不是当前设备复现;未重跑操作,也未验证内嵌旧查看器在当前浏览器中的兼容性。分析时应使用支持该格式的工具,不能给旧数据补算不存在的 FrameTimeline 信息。
源码参考:ActivityThread、Looper、MessageQueue、Choreographer、ViewRootImpl,均为 android-10.0.0_r1。
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。
一个人可以走的更快 , 一群人可以走的更远
