1. System Trace 是什么?
System Trace(系统跟踪) 是 Android 平台最强大的性能分析工具之一。它如同一台 "时间机器" 或 "系统级录屏",能够记录短时间内整个设备上所有软件和硬件活动的详细日志。
它的核心原理是在系统内核、Framework 层和应用层的关键位置插入探针(TracePoint)。当代码执行到这些点时,会留下一个时间戳和上下文信息。最终,所有这些点连点成线,形成一条可视化的时间轴,让我们能清晰地看到:
- CPU:每个核心在做什么?哪个线程在运行?
- 图形系统 :应用是如何绘制界面的?(
UI Thread
,RenderThread
) - 显示系统 :系统是如何合成并显示画面的?(
surfaceflinger
,VSYNC
) - 应用活动 :
Activity
启动、Service
绑定、Input
事件分发。 - 磁盘与网络 I/O:文件读写、网络请求阻塞在了哪里?
Systrace有网页版本和profiler自带的版本
Systrace 与 Perfetto:
- Systrace 是传统工具的名称,基于 Python 脚本。
- Perfetto 是其现代化继任者,从 Android 10 成为官方首选,功能更强大。如今我们常说的 "抓 Systrace",通常指的是使用 Perfetto 的录制功能 和分析界面。
2. 如何录制 .trace 文件?
录制 Trace 文件主要有三种方法,推荐使用第一种。
方法一:命令行录制(最强大灵活)
这是开发者最常用的方式,可以精确控制要抓取的内容。
使用 Perfetto (推荐)
-
准备配置文件 :创建一个
config.pbtx
文本文件。protobufduration_ms: 10000 # 抓取时长:10秒 buffers: { size_kb: 63488 fill_policy: DISCARD } data_sources: { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" ftrace_events: "sched/sched_wakeup" ftrace_events: "gpu_scheduler/gpu_scheduler_run_job" atrace_categories: "gfx" # 图形相关,必须 atrace_categories: "input" # 输入事件 atrace_categories: "view" # View 系统 atrace_categories: "wm" # 窗口管理 atrace_categories: "am" # 活动管理 atrace_categories: "app" # 应用层 } } }
-
执行命令 :在终端执行以下命令,并立即在手机上滑动列表复现卡顿。
bashadb shell perfetto --config config.pbtx --out /data/misc/perfetto-traces/trace.pftrace
-
拉取文件 :抓取完成后,将文件拉到电脑上。
bashadb pull /data/misc/perfetto-traces/trace.pftrace .
方法二:Android Studio Profiler(最简单,最新版本的Studio改进了)
- 打开 Android Studio → View → Tool Windows → Profiler。
- 连接设备并启动你的应用。
- 在 Profiler 窗口中点击 CPU 区域。
- 点击 "System Trace" 按钮 → 点击录制 → 操作手机 → 停止录制。
- Android Studio 会自动打开并显示 trace 文件。
方法三:设备自带的开发者选项(适合无电脑场景)
- 在手机的
开发者选项
→系统跟踪
中开启跟踪。 - 选择跟踪配置(可选),然后开始跟踪。
- 操作手机复现问题。
- 停止跟踪,分享生成的
.perfetto-trace
文件。
3. System Trace 界面的基本操作
3.1 网页的版本
使用 Chrome 浏览器打开 chrome://tracing/
,然后加载你的 trace 文件。
- 放大/缩小 :
W
/S
键 或 鼠标滚轮。- 双击 :放大到所选区域;双击空白处:重置视图。
- 移动视图 :按住
鼠标右键
拖拽 或A
/D
键。 - 选择与查看详情 :单击任何一个色块,下方会显示其详细信息(函数名、耗时、所属进程等)。
- 快速定位到选中区域 :按
M
键,可以将当前选中的片段高亮并居中显示。这是跟踪线程间唤醒关系的神键! - 搜索 :按
Ctrl + F
可以搜索关键字(如进程名、函数名)。 - 时间选择器:顶部有全局时间轴,可以快速定位到特定时间段。
3.2 最新Studio中的Profiler(推荐)
操作和网页版本一样,但是更加的简单
4. System Trace 如何具体的分析卡顿,具体的操作步骤
4.1 分析卡顿是一个有章可循的过程。请遵循以下 "四步分析法":
第一步:定位问题帧(找目标)
- 找到你应用进程的 Frames 轨道。
- 寻找黄色或红色的帧圆圈,这明确指示了卡顿发生的位置。
- 放大这个坏帧所在的时间区域。
第二步:沿流水线溯源(定范围) Android 渲染一帧遵循一个固定的流水线,我们的分析就沿着它展开: VSYNC-app
-> App UI Thread
-> App RenderThread
-> SurfaceFlinger
-> VSYNC-sf
-> Screen
-
检查 VSYNC :找到
vsync-app
信号,它是绘制的起点。 -
检查 UI Thread:
- 找到你的应用进程,展开并找到
UI Thread
(或main
)。 - 查看在两个
vsync-app
信号之间,UI Thread
是否在执行Choreographer#doFrame
。 - 如果
doFrame
的色块非常长,超过了 16.6ms,问题就在这里! - 点击该
doFrame
块,查看其子项目。是measure
/layout
慢,还是draw
慢?或者是ListView.bin
/RV.onBind
?
- 找到你的应用进程,展开并找到
-
检查 RenderThread:
- 如果
UI Thread
很快,问题可能出在渲染。 - 找到你应用的
RenderThread
。 - 查看它的
drawFrame
是否耗时过长。常见原因是复杂的阴影、圆角、Canvas
操作等。
- 如果
第三步:检查系统资源(排外因)
- 看 CPU :查看 CPU 轨道,确认当时 CPU 频率是否足够。查看 UI Thread 的状态,如果它长时间处于蓝色 (Runnable),表示它ready了但抢不到CPU,可能是其他线程占满了CPU。
第四步:使用 Alerts 面板(抓提示)
- 在 Perfetto 界面中,有一个
Alerts
面板。它会自动分析整个 Trace 文件并列出所有潜在问题,如Janky frame
,Expensive measure/layout pass
。直接点击 Alert,它会带你定位到问题发生的位置! 这是最高效的起点。
4.2 总结: 从应用的角度分析: 核心分析流程:从"卡顿"到"代码行"
看帧的颜色(红色),主线程,火焰图, 系统的卡顿再看渲染线程
如需检测卡顿情况,请按以下步骤操作:
-
在 Android Studio 中,依次选择 View > Tool Windows > Profiler ,或点击工具栏中的 Profile 图标
。
如果 Select Deployment Target 对话框显示提示,请选择要将您的应用部署到哪个设备以进行性能分析。如果您已通过 USB 连接设备但系统未列出该设备,请确保您已启用 USB 调试。
-
点击 CPU 时间轴上的任意位置以打开 CPU 性能分析器。
-
从 CPU 性能分析器的配置菜单中选择 System Trace ,然后点击 Record 。完成与应用的交互后,点击 Stop。
-
您应该会在 Display 下方看到 Janky frames 轨道。默认情况下,性能分析器只会将卡顿帧显示为有待调查的候选对象。在每个卡顿帧中,红色部分突出显示了相应帧超出其渲染截止时间的时长。
5.发现卡顿帧后,点击该帧;可根据需要按 M 键调整缩放程度以聚焦到所选帧。相关事件会在以下线程中突出显示:主线程、RenderThread 和 GPU completion。

6.通过选中或取消选中 All Frames 和 Lifecycle 复选框,您可以根据需要查看所有帧或呈现时间的细分数据

第一步:找到"案发现场"------ 识别卡顿帧 (Jank Frame)
这是整个分析的起点。你必须先确定哪个时间点发生了卡顿。
-
查看
Display
时间轴:- 位于分析界面的最上方。
- 每个竖直的条代表一个帧 (Frame) 。
- 理想情况:每帧渲染时间 < 16.6ms (60Hz),且能对齐 VSync 信号。
- 卡顿表现 :你会看到一个明显的 红色警告图标 (⚠️) 或文字标注 "Missed VSync" 或 "Jank" 。这就是"案发现场"!
- 操作 :用鼠标点击 这个被标记为 Jank 的帧。系统通常会自动将时间轴的视图居中到这个帧的时间点。
第二步:锁定"嫌疑人"------ 审查主线程 (main Thread) 活动
卡顿通常是因为主线程在关键的 16.6ms 内没有完成工作。现在,我们检查 main
线程在"案发时"在做什么。
-
定位
main
线程:- 在
Threads
时间轴区域,找到你的应用包名(如com.yourapp
)。 - 展开它,找到名为
main
的线程。
- 在
-
时间对齐:
- 确保时间轴的视图已经对准了你选中的那个 Jank 帧。
-
观察线程状态:
- 绿色 (Running) :正在 CPU 上执行代码。如果在 Jank 帧期间,
main
线程有一段很长的绿色块,这几乎可以肯定问题就出在这段代码里。 - 黄色 (Uninterruptible Sleep) :等待 I/O(磁盘、网络)。这也很可能是问题根源(同步 I/O 阻塞了 UI)。
- 灰色 (Sleeping) :空闲。这说明主线程没在做事,卡顿可能由其他原因引起(如 GPU 渲染慢、系统调度问题)。
- 绿色 (Running) :正在 CPU 上执行代码。如果在 Jank 帧期间,
-
初步判断:
- 如果
main
线程在 Jank 帧期间是长时间绿色 → 问题:CPU 密集型任务阻塞主线程。 - 如果
main
线程在 Jank 帧期间是长时间黄色 → 问题:同步 I/O 操作阻塞主线程。 - 如果
main
线程大部分时间是灰色 → 问题可能在RenderThread
、GPU
或系统层面。
- 如果
第三步:提取"指纹"------ 使用 Analysis
窗格定位具体方法
这是最关键的一步,也是最强大的功能。它能告诉你绿色/黄色块里到底在执行什么代码。
-
选择"作案时间" :
- 用鼠标在时间轴上拖拽 ,精确选择
main
线程上那个导致 Jank 的长绿色或黄色块。选得越精确,分析结果越准确。
- 用鼠标在时间轴上拖拽 ,精确选择
-
查看
Analysis
窗格:- 选择时间范围后,下方的
Analysis
窗格会实时更新,显示该时间段内的所有 CPU 活动。
- 选择时间范围后,下方的
-
切换到
Flame Chart
(火焰图) - 推荐首选:-
原理 :火焰图将所有具有相同调用栈的采样点合并成一个"火焰"形状。火焰的宽度代表该方法/函数占用的 CPU 时间。越宽,耗时越长。
-
如何阅读:
- 从上往下读:最顶层是当前正在执行的函数。它的"父母"是调用它的函数,它的"孩子"是它调用的函数。
- 找最宽的块 :在
main
线程的火焰图中,从顶部开始,寻找最宽的"火焰块"。这些就是耗时最长的"热点"函数。 - 示例 :你可能会看到一个非常宽的块,名字是
com.yourapp.ui.MainActivity.loadDataFromNetwork()
。这说明这个方法是主要的 CPU 消耗者。
-
-
切换到
Top Down
(自上而下) - 辅助分析:-
原理 :树形结构,根节点是
main
线程。 -
如何阅读:
- 展开节点,查看方法调用层级。
- 关注
Total CPU Time
列。这个时间包括了该方法自身及其所有子调用的总时间。Thread CPU Time
是方法自身执行的时间。 - 找到
Total CPU Time
占比很高的节点,这就是性能瓶颈所在。
-
-
交叉验证:
- 在
Flame Chart
中找到可疑方法后,可以在Top Down
视图中找到它,查看其完整的调用路径,确认它是在什么场景下被调用的。
- 在
第四步:直击"犯罪现场"------ 跳转到源代码
当你在 Flame Chart
或 Top Down
视图中锁定了一个可疑的方法后,就可以直接跳到代码了。
- 右键点击方法名 :在
Analysis
窗格中,找到你怀疑的那个方法(如loadDataFromNetwork
)。 - 选择
Jump to Source
。 - 结果 :Android Studio 会自动打开对应的 Java/Kotlin 源文件 ,并将光标定位到该方法的定义处,甚至可能高亮具体的行号。
至此,你已经完成了从"用户感知卡顿"到"定位具体代码行"的完整闭环!
因为系统卡顿也是存在的,如果分析系统卡顿,就要看cpu,渲染线程,surfaceFling.
5. System Trace实战:分析 RecyclerView 卡顿
场景:滑动 RecyclerView 时明显卡顿, 主线程做了耗时的计算。
java
public class PerformanceAdapter extends RecyclerView.Adapter<PerformanceAdapter.ViewHolder> {
private final List<String> items;
private final BindTimeListener bindTimeListener;
private long totalBindTime = 0;
private int bindCount = 0;
public interface BindTimeListener {
void onBindTime(long bindTime);
}
public PerformanceAdapter(List<String> items, BindTimeListener listener) {
this.items = items;
this.bindTimeListener = listener;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public final TextView titleText;
public final TextView subtitleText;
public ViewHolder(View view) {
super(view);
titleText = view.findViewById(R.id.titleText);
subtitleText = view.findViewById(R.id.subtitleText);
}
}
@Override
public ViewHolder onCreateViewHolder( ViewGroup parent, int viewType) {
// 模拟耗时操作
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_layout, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder( ViewHolder holder, int position) {
long startTime = System.currentTimeMillis();
// 模拟复杂数据绑定
holder.titleText.setText("Item " + items.get(position));
holder.subtitleText.setText("This is a long subtitle for item " + items.get(position) +
" that will cause text measurement to take more time");
// 故意制造性能问题 - 在主线程进行耗时操作
if (position % 5 == 0) {
simulateHeavyWork();
}
// 随机改变颜色增加布局复杂度
if (position % 3 == 0) {
holder.titleText.setTextColor(Color.RED);
} else {
holder.titleText.setTextColor(Color.BLACK);
}
// 记录绑定耗时
long bindTime = System.currentTimeMillis() - startTime;
totalBindTime += bindTime;
bindCount++;
// 回调报告绑定时间
if (bindTimeListener != null) {
bindTimeListener.onBindTime(bindTime);
}
}
@Override
public int getItemCount() {
return items.size();
}
public double getAverageBindTime() {
return bindCount > 0 ? (double) totalBindTime / bindCount : 0;
}
private void simulateHeavyWork() {
// 1. 主线程耗时计算
Random random = new Random();
long sum = 0;
for (int i = 0; i <= 10000000; i++) {
sum += random.nextLong();
}
}
}
耗时出问题的地方:
ini
private void simulateHeavyWork() {
// 1. 主线程耗时计算
Random random = new Random();
long sum = 0;
for (int i = 0; i <= 10000000; i++) {
sum += random.nextLong();
}
}
5.1 网页版本: 分析思路与操作步骤:
- 抓取 Trace:使用方法一(Perfetto)录制,在录制过程中疯狂滑动 RecyclerView。
- 打开并定位 :
- 用 Chrome 打开 trace 文件。
- 直接查看 Alerts 面板 ,很可能会有一条
Expensive measure/layout pass
的警告。点击它,视图会自动定位到问题帧。
- 聚焦 UI Thread :
- 放大后,观察
UI Thread
的doFrame
。 - 发现
doFrame
耗时极长(比如 40ms)。 - 点击展开
doFrame
,你会发现耗时主要集中在一个名为RecyclerView.onBindViewHolder
或RecyclerView.onLayout
的方法上。
- 放大后,观察
- 深入分析 :
onBindViewHolder
耗时 :这意味着你在onBindViewHolder
中进行了繁重操作,如解析数据、解码图片、设置复杂监听器等。onLayout
/measure
耗时 :这可能是因为你的Item
布局层级太深,或者使用了性能较差的布局容器(如RelativeLayout
嵌套过多)。
- 验证 :
- 在代码中优化你的
onBindViewHolder
方法(避免主线程IO、简化逻辑)和 Item 布局(使用ConstraintLayout
压平层级)。 - 再次抓取 Trace 进行对比,你会发现
doFrame
的耗时显著缩短,帧率恢复绿色。
- 在代码中优化你的
所以网页版本可以参考这个系列博客: zhuanlan.zhihu.com/p/713438834
5.2 Studio中的Profiler版本: 分析思路与操作步骤:
比起网页版本,简单容易懂多了,没有那么复杂! 也是2024年集成到Studio中的
google官网的地址:
developer.android.google.cn/studio/prof...
1.录制,选择 "Capture System Activities"
2. 找到 Frame Timeline 区域,查看红色高亮的"Janky"帧(即掉帧)。得到哪个类
有Expected duration和Actual duration时间
3 点击某一帧,查看其详细耗时分布(如 input、measure/layout、draw、sync、GPU)。得到哪个方法
frameChart
(帧时间线图)

这个版本是网页版本的阉割版本,比较精简,看不到主线程的,只能看到自己的应用
6. System Trace 总结
System Trace存在2个版本,网页版本和studio中的profiler版本
- 它是什么:System Trace 是 Android 性能分析的"终极武器",提供了系统级的、带时间线的详细视图。
- 核心价值 :它最大的优势在于能揭示线程之间的协作与等待关系,让你看清跨进程、跨线程的调用链,从而定位性能瓶颈的根因,而非只是表面现象。
- 学习曲线 :初次接触会感到复杂,但一旦掌握了 "四步分析法"(定位坏帧 -> 沿流水线溯源 -> 检查资源 -> 利用Alerts),就能快速上手。
- 最佳实践 :不要孤立地使用它。通常的流程是:Systrace/Perfetto 定位大致方向 -> Android Studio Profiler 的 Method Trace 定位到具体代码行 -> 代码优化 -> 再次抓 Trace 验证效果。
掌握 System Trace,意味着你拥有了洞察系统内部运作的能力,任何性能问题在你面前都将无所遁形。
RecyclerView卡顿案例地址: github.com/pengcaihua1...