我们做一件事,并不因为它会带来利益,而是因为它是对的。------ER
在2025年末~2026年春节前的这段时间,我想要把业余的研究重点放在 Perfetto------这个 Google 官方提供的性能检测方案上。之所以这样计划,有三个原因:
- 第一,卡顿、内存、功耗等,是APP规模增长到一定程度后,必然会面临的问题 。 我们当下的业务,也正在经历这一个阶段。需要一套行之有效的
发现问题-分析原因-解决方案-长期监控框架 和 工具。 - 第二,Google 官方已经弃用 Systrace,全面投向 Perfetto。而目前中文互联网上,并没有很多资料和使用技巧介绍。对自己来说,这是一次极好的学习机会,同时也是一个不小的挑战。
- 第三,我也想试验,在面临需要快速学习掌握一门新技术的背景下,自己是否具备这样的搜集整理知识、学习能力和自控力。
直接开始,第一篇文章。

作为第一篇介绍性的博客,本文主要会回答以下3个问题:
- Perfetto 是啥
- Perfetto 有什么优势
- Perfetto 在哪些方面帮助我们分析应用性能
- 在文末,将用一个分析卡顿的真实案例来进行实践
1. Perfetto 是啥
Perfetto 是一系列开源的 SDK 、守护进程 、工具 的集合。
- SDK:低开销的tracing(追踪)SDK,应用开发者可以通过代码自由开启/关闭tracing,以分析代码中的特定流程。
- 守护进程:从Android 10开始,Android Framework中就已经内置了Perfetto守护进程,用以代替原来的Systrace。
- 工具:提供了包括chrome网页、本地分析工具、SQL结构化查询支持。
2. Perfetto 有什么优势
作为 APM 体系的搭建和使用者,选择性能追踪工具和分析框架时,我们应该考虑这些方向的问题:
- 容易上手:在项目角度------接入成本低,在人员角度------学习曲线平缓。
- 功能全面:不仅能分析Java调用,还可以深入到Native代码的调用链路;支持CPU、GPU、耗电、内存等多个维度的追踪和测量。
- 运行效率:Tracing 工具自身的运行,不应当影响到APP原有的性能指标,例如因为开启Tracing导致页面卡顿,这就属于本末倒置了。
- 长期支持:有官方作为背书,有专门的社区、论坛、github 仓库,便于搜索、反馈和解决问题。
3. Perfetto 在哪些方面帮助我们分析应用性能

这是Perfetto官方的功能模块架构示意图,可以看到,围绕着 Traces,它的主要功能分为三个部分。
3.1 功能一 Record traces:记录traces
支持 系统层级 、Web页面 、应用内部 三个维度。
3.1.1 System tracing
它是 覆盖面最大、最底层、跨进程的整机系统分析,是我们分析系统卡顿、CPU 分配、内存占用最主要的功能,用来观测整个 Android 系统底层任务调度。
针对系统的不同层次,Perfetto 基于 ftrace/atrace/Perfetto system service 进行全系统级别的 trace。通过 System tracing,可以观察到:
- 线程调度(sched)
- CPU、负载、频率、功耗
- Binder 调用、syscalls
- SurfaceFlinger / RenderThread / Input / ViewRoot 等 Android 框架层 atrace
- 其它系统服务(ActivityManager、PackageManager......)
System tracing 的典型适用场景
- RecyclerView 页面滑动掉帧、卡顿 → 看 是否 CPU 抢占 / GC / Binder 卡
- 冷启动、切 App 慢 → 看 进程创建、IO、主线程调度
- 电量高、发热 → 看 CPU 频点、wakeups、唤醒原因
3.1.2 Chrome tracing
它源自 Chrome内核的tracing系统 (chrome://traces),使用 JSON 格式记录 H5 页面的渲染和加载过程,可以观察到:
- 浏览器进程、Renderer 进程的 JS 执行、Layout、Paint
- 网络请求、定时器、事件循环
- 自己写的 TRACE_EVENT_xxx 业务打点
- 组件级、模块级的耗时切片
Chrome tracing 的典型适用场景
- 排查 Web 页面卡顿 / 慢渲染(H5、PWA)
- 分析 复杂 C++ 模块的内部执行流程(如果它们用了 Chrome tracing 宏)
- 与 H5 前端同事共享统一格式的 trace 文件,方便一起分析
3.1.3 In-app tracing
是 APP 开发人员在应用代码里主动打点的 Trace,用于分析业务逻辑代码中有哪些卡顿点。通过调用 SDK 提供的API,可以把自己 App 的业务流程集成到 Perfetto 的可视化分析工具里,看每一步产生多少耗时:
- 事件名字(比如 "FetchDialConfig", "DecodeBitmap", "DB.query.home_feed")
- 哪个线程 / 哪条"轨道"(track)上显示
- 开始结束时间(切片),或是瞬时事件(Instant event)
- 还可以记录 counter(比如缓存大小、当前在线人数)
In-app tracing 的典型适用场景
在第一步已经通过 System tracing 定位到卡顿来自于 App 某个线程 / 函数附近,接下来借助 In-app tracing 进一步分析其根本原因可能是:
- 网络慢?
- 解析 JSON 慢?
- 数据库慢?
- UI diff & bind 慢?
3.2 功能二 Analyze traces:分析traces
Perfetto 支持在 Android/Linux/MacOS/Win 多个平台上,对已经抓取到的 Trace 文件进行分析。
- 导入、导出结构化(Protobuf, JSON, systrace)的trace
- 使用SQL对trace内容进行查询和统计
通过以上两点,极大地扩展了 Perfetto 的使用场景,除了单点问题排查,还可以将其集成到监控系统、自动化工具中,实现标准化、流程化、规模化。
3.3 功能三 Visualize traces:可视化traces
这里主要指的是 Chrome 浏览器内置的 Trace 查看工具(https://ui.perfetto.dev/),它支持离线使用(需要首次加载成功后),可以通过 adb 识别到 usb 连接的设备,进行 Trace 抓取和可视化分析。
4. Tracing 和 Profiling
这两者都是在性能分析中很重要的概念,由于它们使用场景相同、功能相似,因此经常让人混淆。这里简单说下个人的理解。
Tracing 重点是"追踪、跟踪" ,它的出发点是记录 一段时间内 系统的行为,包括函数调用、CPU调度、内存分配过程等,可以观察到随着时间推进,系统内一步步依次做了哪些事。
Profiling 重点是"切面、快照" ,它是一个 瞬间 的系统切面,记录了系统此时的状态,是一个瞬时值而非连续值。
因此,在实践过程中,如果尚未明确问题的大致方向,则需要先通过 Trace 找出系统状态异常的大体位置,然后在对应时间点抓取详细的 Profile,分析具体原因。
如果已经确定了问题的最大嫌疑对象(内存、网络、CPU),则可以直接用 Profile 定位原因。
在线上监控中,往往提取 问题发生瞬间的 Profile 和往前一小段时间的 Trace,作为现场,提供给应用开发者进行分析。
5. 使用 Perfetto 分析 RecyclerView 滑动卡顿
作为第一个例子,我们使用 Perfetto 来定位一个 RecyclerView 滑动过程中的卡顿问题。我们知道,RecyclerView 内部提供了缓存和复用机制,在上下滑动时,对于移出屏幕的 ItemView,会将其放入缓存池以供复用。通过调用 Adapter.onBindViewHolder() 绑定新的 Item。
如果在调用上述函数时,发生耗时操作,就会导致绘制时间过长(>16ms),从而错过 UI 线程的绘制窗口,发生掉帧(卡顿)现象。
这是一个人为造成卡顿的的 Demo。
kotlin
// 单个元素 Adapter
class SlowAdapter(
private val items: List<String>
) : RecyclerView.Adapter<SlowAdapter.SlowViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SlowViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_slow_row, parent, false)
return SlowViewHolder(view)
}
override fun onBindViewHolder(holder: SlowViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount(): Int = items.size
class SlowViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val textView: TextView = itemView.findViewById(R.id.titleTextView)
fun bind(text: String) {
textView.text = text
// 刻意制造一段 CPU 密集型计算,阻塞主线程
simulateHeavyWork()
}
/**
* 模拟耗时操作
*/
private fun simulateHeavyWork() {
val start = System.nanoTime()
var acc = 0.0
// 忙等
while (System.nanoTime() - start < 45_000_000L) { // 45ms
acc += kotlin.math.sin(acc) // CPU 运算
}
// "acc变量是有用的!",防止编译器将上述运算优化掉
if (acc == 42.0) {
println("impossible")
}
}
}
}
// 列表页面 Activity,展示大量数据
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
// 准备一堆数据,方便长列表滑动
val items = List(300) { index -> "Item #$index" }
recyclerView.adapter = SlowAdapter(items)
}
}
以下是 Item 和 Activity 的布局文件。
xml
<!-- Item -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="56dp"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<TextView
android:id="@+id/titleTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Demo item"
android:textSize="16sp"
android:ellipsize="end"
android:singleLine="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Activity -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
5.1 Demo运行效果
为了对比明显,打开 开发者选项--HWUI呈现模式分析,绘制过程中的掉帧,会以长条形的形式展示在界面上。

5.2 配置 Perfetto 定位掉帧原因
用USB连接手机至PC,在浏览器中打开 ui.perfetto.dev/ ,页面左侧选择"Record new trace",随后就可以在当前页面与测试手机建立连接,如下图。

然后我们需要设置Tracing参数,由于我们关注的重点是掉帧,只开启最小化必要选项即可。

注意在最后一个选项目录"Advanced settings"中开启包名关联,如果不开的话,Tracing中就只有ProcessID,看不出来是哪一个具体进程。

在设置完图形化的设置选项后,会实时更新配置命令,可以在"Cmdline instructions"中看到它们,我将其粘贴在这里。
json
buffers {
size_kb: 65536
fill_policy: DISCARD
}
buffers {
size_kb: 4096
fill_policy: DISCARD
}
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "ftrace/print"
ftrace_events: "sched/sched_process_exit"
ftrace_events: "sched/sched_process_free"
ftrace_events: "task/task_newtask"
ftrace_events: "task/task_rename"
atrace_categories: "input"
atrace_categories: "ss"
atrace_categories: "view"
atrace_apps: "com.lei.perfettodemo"
}
}
}
data_sources {
config {
name: "android.surfaceflinger.frametimeline"
}
}
data_sources {
config {
name: "linux.process_stats"
target_buffer: 1
process_stats_config {
scan_all_processes_on_start: true
}
}
}
duration_ms: 10000
5.3 抓取并分析 Tracing
完成配置后,点击开始按钮进行 Tracing,默认时间是 10s。抓取完成后会自动打开。

Expected Timeline:系统留给APP的绘制窗口,在60fps的情况下,一帧应当在16ms内完成绘制。Actual Timeline,APP实际绘制所消耗的时间,绿色表示符合系统窗口时间,红色则表示超过了系统时间,发生卡顿(UI Jank)。
不同颜色块的释义如下表:

放大图表后,就可以看到,导致UI Jank红色方块的原因,在于 RV.OnBindView,因此,可以定位到我们写的SlowViewHolder.bind()函数。

6. 结语
以上就是 Perfetto 的介绍以及首次使用实践,我会继续研究 Perfetto 的实用技巧和方法,欢迎有兴趣的朋友一起交流讨论。