性能优化的第一步,不是猜测问题在哪里,而是用数据说话。Trace工具就是那个让性能瓶颈无所遁形的"显微镜"。
引言:从3秒到1秒的优化历程
那是去年Q2的一个周一早晨,产品经理神色凝重地找到我:"用户反馈我们的App启动太慢了,平均3秒才能看到首页,竞品只要1秒。老板说如果这个季度优化不到1.5秒以内,项目可能要黄。"
我打开App试了试,确实慢得让人抓狂。但问题在哪里呢?Application初始化?Activity加载?网络请求?还是图片渲染?
凭感觉优化?那只是在盲人摸象。
于是我打开Systrace,抓取了一份启动Trace。当Chrome打开那个HTML文件的瞬间,整个启动流程一览无遗:
Application.onCreate(): 450ms
├─ SDK初始化: 280ms ← 元凶!
├─ 数据库迁移: 120ms
└─ 其他初始化: 50ms
Activity.onCreate(): 380ms
├─ setContentView(): 80ms
├─ 网络请求: 200ms ← 在主线程!
└─ RecyclerView首屏渲染: 100ms
问题一目了然:SDK初始化和网络请求占据了启动时间的一半。
经过两周的优化(SDK延迟初始化、网络请求移到后台、预加载优化),启动时间降到了0.9秒。产品经理再也没提过启动慢的问题,项目也顺利上线了。
这次经历让我明白:性能优化不是靠经验和直觉,而是靠数据。而Trace工具,就是获取这些数据的最强武器。
本文将带你全面掌握Systrace和Perfetto两大性能分析工具。读完本文,你将能够:
- 理解Systrace和Perfetto的工作原理和区别
- 掌握Trace抓取的完整流程和常用配置
- 学会分析Trace文件,快速定位性能瓶颈
- 使用代码插桩技术精确追踪关键路径
- 运用SQL查询进行高级性能分析
一、Trace工具概述
1.1 什么是Trace
Trace(跟踪) 是一种记录系统运行时事件序列的技术。简单来说,就是给系统"装个监控摄像头",记录下指定时间段内发生的所有关键事件。
Trace能记录什么?
| 类型 | 内容 | 用途 |
|---|---|---|
| 函数调用 | 方法进入/退出时间 | 分析哪个函数耗时长 |
| 线程调度 | 线程运行/等待/睡眠状态 | 定位线程阻塞问题 |
| CPU调度 | 哪个线程在哪个核心上运行 | CPU占用分析 |
| 渲染事件 | VSYNC、SurfaceFlinger绘制 | 卡顿和掉帧分析 |
| I/O操作 | 文件读写、网络请求 | I/O性能分析 |
| GC事件 | 垃圾回收的时间和频率 | 内存抖动分析 |
| Binder调用 | 跨进程通信 | IPC性能分析 |
Trace能解决什么问题?
✅ 启动慢 - 精确定位启动过程中的耗时操作
✅ 滑动卡顿 - 找出导致掉帧的函数调用
✅ 动画不流畅 - 分析渲染Pipeline的瓶颈
✅ CPU占用高 - 识别CPU密集型操作
✅ 功耗高 - 定位频繁唤醒和长时间运行的任务
1.2 Systrace简介
Systrace是Google早期推出的系统级性能分析工具,从Android 4.1开始引入。
核心特点:
- 基于Linux ftrace机制
- 轻量级,对系统性能影响小
- 以HTML文件呈现,使用Chrome Trace Viewer查看
- 支持Android 4.1到Android 12的所有版本
工作原理:

优势:
- ✅ 简单易用,一行命令即可抓取
- ✅ 兼容性好,支持老版本Android
- ✅ 无需额外安装工具
局限性:
- ❌ UI体验一般(基于Chrome插件)
- ❌ 不支持SQL查询
- ❌ 大文件加载慢
- ❌ 功能相对基础
1.3 Perfetto简介
Perfetto 是Google从Android 10开始推出的新一代性能分析平台,旨在取代Systrace。
核心特点:
- 现代化的Web UI(https://ui.perfetto.dev/)
- 强大的SQL查询引擎
- 支持Protobuf二进制格式,文件更小
- 更丰富的数据源(内存、电量、网络等)
- 向后兼容,可以打开Systrace文件
工作原理 :

优势:
- ✅ 现代化UI,交互体验极佳
- ✅ SQL查询引擎,支持复杂分析
- ✅ 更丰富的数据源
- ✅ 文件更小,加载更快
- ✅ 持续更新,功能不断增强
局限性:
- ⚠️ 主要支持Android 10+
- ⚠️ 学习曲线稍陡(SQL查询)
1.4 工具对比
| 特性 | Systrace | Perfetto |
|---|---|---|
| 推出时间 | Android 4.1 (2012) | Android 10 (2019) |
| UI体验 | Chrome Trace Viewer | 现代Web UI ⭐⭐⭐⭐⭐ |
| 功能丰富度 | 基础 | 强大 ⭐⭐⭐⭐⭐ |
| 数据格式 | HTML | Protobuf (更小) |
| 查询能力 | 无 | SQL查询 ⭐⭐⭐⭐⭐ |
| 数据源 | ftrace | ftrace + 内存 + 电量 + 网络 |
| 兼容性 | Android 4.1 - 12 | Android 9+ (最好10+) |
| 文件大小 | 较大 | 更小 |
| 学习曲线 | 简单 | 中等 |
| 推荐使用 | Android 9及以下 | Android 10+ ⭐推荐 |
选择建议:
- 如果你的项目主要支持Android 10+,强烈推荐Perfetto
- 如果需要兼容Android 9及以下,使用Systrace
- 两者可以共存,根据场景灵活选择
二、Systrace使用指南
2.1 环境准备
前置条件:
- ✅ Python 2.7+ 或 Python 3.x
- ✅ Android SDK Platform-Tools (adb命令)
- ✅ Chrome浏览器
Systrace位置:
bash
# Android SDK中的位置
$ANDROID_HOME/platform-tools/systrace/systrace.py
# 验证Python环境
python --version # 或 python3 --version
# 验证adb连接
adb devices
设备权限设置:
bash
# 1. 开启开发者选项
# 设置 → 关于手机 → 连续点击"版本号"7次
# 2. 开启USB调试
# 设置 → 开发者选项 → USB调试
# 3. 授予adb root权限(如需要)
adb root
adb remount
2.2 抓取Systrace
基础用法:
bash
# 进入systrace目录
cd $ANDROID_HOME/platform-tools/systrace
# 抓取10秒的trace
python systrace.py -o trace.html -t 10 -b 32768 \
gfx input view webview wm am sm audio video camera hal \
app res dalvik rs bionic power pm ss database network
# 参数说明:
# -o trace.html : 输出文件名
# -t 10 : 抓取时长(秒)
# -b 32768 : buffer大小(KB),建议32MB以上
# -a package_name : 指定应用包名(可选)
# 后面是category列表
推荐配置(通用场景):
bash
python systrace.py \
-o trace.html \
-t 10 \
-b 32768 \
sched freq idle am wm gfx view binder_driver hal \
dalvik input res
2.3 常用Category说明
| Category | 说明 | 典型用途 |
|---|---|---|
| sched | CPU调度事件 | 必选,分析线程运行状态 |
| freq | CPU频率变化 | CPU性能分析 |
| idle | CPU idle状态 | 功耗分析 |
| gfx | SurfaceFlinger, VSYNC, Texture | 渲染分析必选 |
| input | Input事件分发 | 触摸响应分析 |
| view | View绘制(measure/layout/draw) | 卡顿分析必选 |
| webview | WebView性能 | H5页面分析 |
| wm | Window Manager | 窗口管理分析 |
| am | Activity Manager生命周期 | 启动分析必选 |
| sm | Sync Manager | 同步操作分析 |
| audio | Audio播放 | 音频卡顿分析 |
| video | Video解码 | 视频性能分析 |
| camera | Camera操作 | 相机性能分析 |
| hal | HAL层事件 | 底层硬件调用 |
| app | 应用自定义trace | 插桩分析必选 |
| res | 资源加载 | 资源加载性能 |
| dalvik | GC事件 | 内存抖动分析必选 |
| rs | RenderScript | 图像处理分析 |
| bionic | Bionic库调用 | Native性能分析 |
| power | Power HAL | 电量分析 |
| pm | Package Manager | 安装/卸载分析 |
| ss | System Server | 系统服务分析 |
| database | SQLite操作 | 数据库性能分析 |
| network | 网络I/O | 网络性能分析 |
| binder_driver | Binder驱动 | IPC分析必选 |
场景推荐配置:
bash
# 启动性能分析
python systrace.py -o launch.html -t 10 -a com.example.app \
sched freq idle am wm gfx view binder_driver hal dalvik input res
# 滑动卡顿分析
python systrace.py -o scroll.html -t 10 -a com.example.app \
sched gfx view input wm dalvik app
# 渲染性能分析
python systrace.py -o render.html -t 10 -a com.example.app \
sched freq gfx view wm
# 内存抖动分析
python systrace.py -o memory.html -t 10 -a com.example.app \
sched dalvik app
# 全面分析(文件会很大)
python systrace.py -o full.html -t 10 -a com.example.app \
sched freq idle am wm gfx view webview binder_driver hal \
dalvik input res power database network app
2.4 实战演练:抓取应用启动Trace
完整的启动Trace抓取流程:
bash
# Step 1: 确定应用包名
adb shell pm list packages | grep example
# 输出: package:com.example.myapp
# Step 2: 清理应用数据(模拟首次启动)
adb shell pm clear com.example.myapp
# Step 3: 启动Systrace抓取(不要立即启动App)
cd $ANDROID_HOME/platform-tools/systrace
python systrace.py -o launch.html -t 10 -a com.example.myapp \
sched freq idle am wm gfx view binder_driver hal dalvik input res &
# 注意: 最后的 & 让命令在后台运行
# Step 4: 等待2秒,让systrace准备好
sleep 2
# Step 5: 启动应用并记录启动时间
adb shell am start -W com.example.myapp/.MainActivity
# 输出示例:
# Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.myapp/.MainActivity }
# Status: ok
# Activity: com.example.myapp/.MainActivity
# ThisTime: 1245 ← Activity启动时间
# TotalTime: 1245 ← 总启动时间
# WaitTime: 1267 ← 等待时间
# Complete
# Step 6: 等待systrace抓取完成(10秒)
# 会自动生成 launch.html
# Step 7: 使用Chrome打开trace文件
google-chrome launch.html
# 或
open -a "Google Chrome" launch.html
Tip: 为了抓取到完整的启动流程,建议trace时长设置为15-20秒,确保从应用冷启动到首屏渲染完成都被记录。
2.5 查看Systrace
1. 打开Trace文件:
- 使用Chrome浏览器打开trace.html
- 文件较大时可能需要等待几秒加载
2. 界面布局:
┌──────────────────────────────────────────────────┐
│ [?] [W][A][S][D] 搜索:______ 时间范围:___ │ 工具栏
├──────────────────────────────────────────────────┤
│ CPU 0 │
│ CPU 1 │ CPU核心
│ CPU 2 │
│ CPU 3 │
├──────────────────────────────────────────────────┤
│ com.example.myapp │ 应用进程
│ └─ main ████████████████ │ 主线程
│ └─ RenderThread ██████ │ 渲染线程
│ └─ binder:xxx ███ │ Binder线程
├──────────────────────────────────────────────────┤
│ system_server │ 系统进程
│ └─ android.fg █████████ │
│ └─ android.ui ████ │
├──────────────────────────────────────────────────┤
│ surfaceflinger │ SurfaceFlinger
│ └─ surfaceflinger ████████████ │
└──────────────────────────────────────────────────┘
3. 基本操作:
| 操作 | 快捷键/方法 | 说明 |
|---|---|---|
| 放大 | W 或 鼠标滚轮上滚 |
放大时间轴 |
| 缩小 | S 或 鼠标滚轮下滚 |
缩小时间轴 |
| 左移 | A |
向左移动时间轴 |
| 右移 | D |
向右移动时间轴 |
| 选择区域 | 鼠标左键拖拽 | 选中一段时间范围 |
| 查看详情 | 点击色块 | 查看函数调用详情 |
| 搜索 | Ctrl+F 或 搜索框 |
搜索函数名 |
| 高亮 | 点击函数名 | 高亮所有同名函数 |
| 标记 | M |
添加书签标记 |
4. 查看函数详情 :
点击一个色块后,右侧会显示详细信息:
Title: onCreate
Wall Duration: 450ms ← 实际耗时
CPU Duration: 420ms ← CPU占用时间
Self Time: 50ms ← 自身耗时(不含子调用)
Start: 1234.567ms ← 开始时间
Args: ← 参数信息
thread_name: main
process_name: com.example.myapp
5. 分析技巧:
- 🔍 先缩小查看整体,识别大块耗时
- 🔍 再放大查看细节,定位具体函数
- 🔍 关注主线程的空白区域(可能在等待)
- 🔍 查看CPU调度,识别线程阻塞
- 🔍 对比VSYNC信号,分析掉帧原因
三、Perfetto使用指南
3.1 三种使用方式
Perfetto提供了三种抓取和分析trace的方式:
方式1: Web UI (⭐推荐)
优点 : 无需安装,功能完整,体验最佳
缺点: 需要网络连接
使用步骤:
1. 打开浏览器访问 https://ui.perfetto.dev/
2. 点击"Record new trace"
3. 连接设备 (会提示授权adb)
4. 配置抓取参数
5. 点击"Start Recording"
6. 执行待分析的操作
7. 点击"Stop"并下载trace文件
方式2: 命令行
优点 : 适合自动化脚本,CI集成
缺点: 需要手动配置
使用步骤:
bash
# 1. 下载Perfetto工具(如果设备上没有)
adb shell perfetto --version
# 如果没有,从 https://get.perfetto.dev/perfetto 下载
# 2. 推送配置文件到设备
adb push config.pbtx /data/local/tmp/
# 3. 开始抓取
adb shell perfetto \
-c /data/local/tmp/config.pbtx \
-o /data/local/tmp/trace.perfetto-trace
# 4. 导出trace文件
adb pull /data/local/tmp/trace.perfetto-trace ./
方式3: Android Studio集成
优点 : 与开发环境集成,操作简单
缺点: 功能相对基础
使用步骤:
1. 打开Android Studio
2. View → Tool Windows → Profiler
3. 点击"+"添加Session
4. 选择设备和进程
5. 点击"CPU" → "System Trace"
6. 点击"Record"开始抓取
7. 执行操作后点击"Stop"
8. 查看trace (会自动用Perfetto UI打开)
3.2 Web UI抓取流程详解
让我们详细讲解最推荐的Web UI方式:
Step 1: 打开Perfetto UI
浏览器访问: https://ui.perfetto.dev/
建议使用Chrome或Edge浏览器
Step 2: 选择Record new trace
点击左上角的"Record new trace"按钮
Step 3: 连接设备
- 确保adb已连接:
adb devices - 在"Target"下拉框中选择你的设备
- 首次使用会提示安装"Perfetto UI ADB Bridge",点击同意
Step 4: 配置抓取参数
yaml
# Recording Settings配置项:
Duration: 10000 ms # 抓取时长
Buffer size: 32768 KB # 缓冲区大小
# Probes (数据源)选择:
✅ Scheduling details # CPU调度(必选)
✅ CPU frequency and idle states # CPU频率
✅ Syscalls # 系统调用
✅ Process tracking # 进程跟踪
✅ Frame timeline # 帧时间线(渲染分析必选)
✅ Memory (RSS, PSS, etc) # 内存统计
✅ Battery drain & power rails # 电量分析
# Advanced settings:
Atrace categories: # 选择category
✅ gfx
✅ input
✅ view
✅ am
✅ wm
✅ dalvik
App name: com.example.myapp # 指定应用包名
Step 5: 开始抓取
- 点击"Start Recording"按钮
- 等待初始化(2-3秒)
- 执行你要分析的操作(启动App、滑动列表等)
Step 6: 停止并下载
- 点击"Stop and get trace"
- 等待数据传输(取决于trace大小)
- Trace会自动在浏览器中打开
- 可以点击"Download"保存到本地
3.3 命令行抓取
对于自动化场景,命令行抓取更适合。
创建配置文件 config.pbtx:
protobuf
# Perfetto配置文件格式 (Protocol Buffer Text)
buffers {
size_kb: 32768 # 缓冲区32MB
fill_policy: RING_BUFFER # 环形缓冲区策略
}
# 数据源配置
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
# ftrace事件
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_wakeup"
ftrace_events: "sched/sched_wakeup_new"
ftrace_events: "sched/sched_waking"
ftrace_events: "power/suspend_resume"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
# atrace categories
atrace_categories: "gfx"
atrace_categories: "view"
atrace_categories: "am"
atrace_categories: "wm"
atrace_categories: "input"
atrace_categories: "dalvik"
# 指定应用
atrace_apps: "com.example.myapp"
}
}
}
# 抓取时长
duration_ms: 10000 # 10秒
抓取脚本 capture_trace.sh:
bash
#!/bin/bash
# 配置
APP_PACKAGE="com.example.myapp"
CONFIG_FILE="config.pbtx"
OUTPUT_FILE="trace_$(date +%Y%m%d_%H%M%S).perfetto-trace"
echo "=== Perfetto Trace Capture ==="
echo "App: $APP_PACKAGE"
echo "Output: $OUTPUT_FILE"
# 1. 推送配置文件
echo "[1/4] Pushing config..."
adb push $CONFIG_FILE /data/local/tmp/config.pbtx
# 2. 清理应用数据(可选)
echo "[2/4] Clearing app data..."
adb shell pm clear $APP_PACKAGE
# 3. 开始抓取(后台运行)
echo "[3/4] Starting trace capture..."
adb shell perfetto \
-c /data/local/tmp/config.pbtx \
-o /data/local/tmp/trace.perfetto-trace &
PERFETTO_PID=$!
# 等待perfetto启动
sleep 2
# 4. 启动应用
echo "[4/4] Launching app..."
adb shell am start -W $APP_PACKAGE/.MainActivity
# 等待perfetto完成
wait $PERFETTO_PID
# 5. 导出trace
echo "Pulling trace file..."
adb pull /data/local/tmp/trace.perfetto-trace $OUTPUT_FILE
# 清理设备上的文件
adb shell rm /data/local/tmp/trace.perfetto-trace
adb shell rm /data/local/tmp/config.pbtx
echo "✅ Trace saved to: $OUTPUT_FILE"
echo "Open it at: https://ui.perfetto.dev/"
使用脚本:
bash
chmod +x capture_trace.sh
./capture_trace.sh
3.4 Perfetto UI详解
整体界面布局 :

核心功能区域:
-
Timeline (时间轴)
- 显示整个trace的时间范围
- 可以拖拽选择时间区间
- 双击可以跳转到该时间点
-
Pinned Tracks (置顶轨道)
- Frame Timeline: 每一帧的渲染情况
- 绿色: 正常帧 (< 16.6ms)
- 黄色: 轻微掉帧 (16.6-33ms)
- 红色: 严重掉帧 (> 33ms)
- 可以添加其他重要轨道
- Frame Timeline: 每一帧的渲染情况
-
CPU轨道
- 显示每个CPU核心的使用情况
- 可以看到线程在哪个核心上运行
- 颜色代表不同的进程
-
进程和线程轨道
- 每个进程可以展开查看线程
- 线程上的色块代表函数调用
- 点击色块查看函数详情
-
详情面板 (右侧或底部)
- 显示选中slice的详细信息
- 包含Wall Duration、CPU Time、Stack Trace
- 可以查看参数和属性
常用操作:
| 操作 | 方法 |
|---|---|
| 放大/缩小 | 鼠标滚轮 或 触控板双指缩放 |
| 平移 | 按住Shift+拖拽 或 触控板双指滑动 |
| 选择区域 | 按住左键拖拽 |
| 查看详情 | 点击slice(色块) |
| 搜索 | 点击顶部搜索图标 或 按/ |
| 测量时间 | 按住Shift点击两个时间点 |
| 添加标记 | 按M键 |
| 回到全景 | 双击时间轴空白处 |
Frame Timeline分析:
Frame Timeline是Perfetto最强大的功能之一,用于分析渲染性能。

分析每一帧:
- 绿色帧: 正常,< 16.6ms
- 黄色帧: 轻微掉帧,16.6-33ms (掉1帧)
- 红色帧: 严重掉帧,> 33ms (掉2帧以上)
点击一个帧,可以看到该帧的详细时间分解,快速定位瓶颈在哪个阶段。
3.5 Perfetto SQL查询入门
Perfetto最强大的功能之一就是SQL查询引擎,可以对trace数据进行复杂的分析。
打开SQL查询界面:
- 在Perfetto UI中打开trace文件
- 点击右上角的"Query (SQL)"按钮
- 在SQL编辑器中输入查询语句
常用表结构:
| 表名 | 说明 | 主要字段 |
|---|---|---|
slice |
所有函数调用 | ts, dur, name, depth, track_id |
thread |
线程信息 | utid, tid, name, upid |
process |
进程信息 | upid, pid, name |
thread_track |
线程轨道映射 | id, utid |
sched |
CPU调度信息 | ts, dur, cpu, utid |
counter |
计数器数据 | ts, value, track_id |
示例查询:
sql
-- 查询主线程的所有函数调用
SELECT
ts / 1e9 AS start_sec,
dur / 1e6 AS duration_ms,
name
FROM slice
JOIN thread_track ON slice.track_id = thread_track.id
JOIN thread ON thread_track.utid = thread.utid
WHERE thread.name = 'main'
AND dur > 10e6 -- 只看耗时超过10ms的
ORDER BY dur DESC
LIMIT 20;
-- 查询GC事件
SELECT
ts / 1e9 AS time_sec,
dur / 1e6 AS duration_ms,
name AS gc_type
FROM slice
WHERE name LIKE 'Heap%' OR name LIKE 'GC%'
ORDER BY ts;
-- 查询CPU使用率
SELECT
thread.name,
SUM(sched.dur) / 1e9 AS cpu_time_sec
FROM sched
JOIN thread USING (utid)
WHERE thread.name LIKE '%myapp%'
GROUP BY thread.name
ORDER BY cpu_time_sec DESC;
后续章节我们会详细介绍更多高级查询技巧。
四、Trace分析实战
掌握了工具的使用,接下来就是最核心的部分:如何分析Trace文件,快速定位性能瓶颈。
4.1 分析思路框架
不管分析什么性能问题,都可以遵循这个系统化的思路:

4.2 常见性能指标
在分析Trace时,需要关注以下关键指标:
1. Wall Duration (墙上时间)
- 函数从开始到结束的实际时间
- 包含CPU执行时间和等待时间
- 这是用户感知到的时间
2. CPU Time (CPU时间)
- 函数实际占用CPU的时间
- 不包含等待、睡眠等时间
- 如果
CPU Time << Wall Duration,说明大部分时间在等待
3. Self Time (自身时间)
- 函数自身的执行时间,不包含调用子函数的时间
- 用于识别真正的瓶颈函数
4. Thread State (线程状态)
- Running ®: 正在CPU上执行
- Runnable ®: 就绪状态,等待CPU调度
- Sleeping (S): 睡眠,等待事件
- Uninterruptible Sleep (D): 不可中断睡眠,通常是I/O等待
- Stopped (T): 停止状态
帧渲染指标:
一帧的理想时间分配 (60fps, 16.6ms):
┌────────────────────────┐
│ Input: 2-3ms │ 处理触摸事件
│ Animation: 1-2ms │ 执行动画
│ Measure: 2-3ms │ 测量View尺寸
│ Layout: 1-2ms │ 布局View位置
│ Draw: 2-3ms │ 绘制View
│ RenderThread: 4-5ms │ 渲染线程处理
│ GPU: 2-3ms │ GPU渲染
├────────────────────────┤
│ Total: 14-18ms │ 总耗时
└────────────────────────┘
4.3 案例1: 启动性能分析
场景: 一个社交App启动需要2.8秒,需要优化到1.5秒以内。
Step 1: 抓取启动Trace
bash
# 清理数据
adb shell pm clear com.example.social
# 抓取trace
python systrace.py -o launch.html -t 15 -a com.example.social \
sched freq am wm gfx view dalvik input res app
# 启动应用
adb shell am start -W com.example.social/.MainActivity
Step 2: 打开Trace,定位关键时间点
在Trace中搜索关键事件:
搜索 "activityStart" → 找到启动开始点 (T0)
搜索 "Choreographer#doFrame" → 找到首帧绘制 (T1)
总启动时间: T1 - T0 = 2850ms
Step 3: 分析主线程时间分布
放大主线程,查看各个阶段:
Timeline:
T0 Application.onCreate() 450ms
├─ SDK初始化 280ms ← ⚠️ 瓶颈1
├─ 数据库初始化 120ms
└─ 其他 50ms
T+450 Activity.onCreate() 380ms
├─ setContentView() 80ms
├─ loadUserInfo() (网络请求) 200ms ← ⚠️ 瓶颈2 (主线程!)
└─ RecyclerView.init() 100ms
T+830 onResume() 50ms
T+880 首帧测量布局绘制 150ms
T+1030 RenderThread渲染 80ms
T+1110 等待网络数据 1600ms ← ⚠️ 瓶颈3
(主线程在sleep!)
T+2710 刷新UI 140ms
T+2850 启动完成
Step 4: 根因分析
瓶颈1: SDK初始化 280ms
- 点击"SDK初始化"slice,查看堆栈
- 发现是第三方统计SDK在做网络请求
- 优化方案: 延迟到后台初始化
瓶颈2: 主线程网络请求 200ms
- loadUserInfo()在主线程执行同步网络请求
- 优化方案: 改为异步请求,使用缓存数据先展示
瓶颈3: 等待网络数据 1600ms
- 主线程在等待服务器返回
- 优化方案: 使用本地缓存,异步刷新
Step 5: 优化后验证
优化后再次抓取trace:
Timeline (优化后):
T0 Application.onCreate() 120ms ← 减少330ms
├─ 关键初始化 70ms
└─ 其他 50ms
T+120 Activity.onCreate() 180ms ← 减少200ms
├─ setContentView() 80ms
├─ 从缓存加载用户信息 30ms
└─ RecyclerView.init() 70ms
T+300 onResume() 50ms
T+350 首帧测量布局绘制 150ms
T+500 RenderThread渲染 80ms
T+580 启动完成 (显示缓存数据)
后台异步更新数据...
总启动时间: 580ms ✅ 达成目标!
优化效果:
- 启动时间从 2850ms → 580ms
- 提升了 79.6%
- 用户体验显著改善
4.4 案例2: 卡顿分析
场景: RecyclerView滑动时经常掉帧,体验很卡。
Step 1: 抓取滑动Trace
bash
python systrace.py -o scroll.html -t 10 -a com.example.social \
sched gfx view input wm dalvik app
抓取时在App中快速滑动列表。
Step 2: 查看Frame Timeline
在Perfetto中打开trace,查看Frames轨道:
Frame Timeline:
Frame #100: 16ms ✅ 绿色
Frame #101: 17ms ⚠️ 黄色 (轻微掉帧)
Frame #102: 34ms ❌ 红色 (掉1帧)
Frame #103: 18ms ⚠️ 黄色
Frame #104: 52ms ❌❌ 红色 (掉2帧)
Step 3: 分析掉帧的Frame
点击Frame #104 (52ms的那一帧),查看详情:
Frame #104 详细分析:
┌──────────────────────────────────────┐
│ Expected: 16.6ms │
│ Actual: 52ms (掉3帧!) │
├──────────────────────────────────────┤
│ Input: 2ms ██ │
│ Animation: 1ms █ │
│ Measure: 3ms ███ │
│ Layout: 2ms ██ │
│ Draw: 4ms ████ │
│ RecyclerView. 35ms ███████████ │ ← ⚠️ 瓶颈!
│ onBindViewHolder │
│ RenderThread: 5ms █████ │
└──────────────────────────────────────┘
Step 4: 深入分析onBindViewHolder
点击"onBindViewHolder"slice,查看堆栈:
java
onBindViewHolder (35ms)
├─ loadImage() (20ms) ← ⚠️ 主线程加载图片
│ ├─ decodeFile() (18ms) ← 解码耗时
│ └─ 其他 (2ms)
├─ setText() (8ms)
│ └─ measureText() (7ms) ← 复杂文本测量
└─ 其他 (7ms)
Step 5: 根因与优化
问题1: 主线程加载图片 20ms
java
// 【优化前】- 主线程同步加载
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val bitmap = BitmapFactory.decodeFile(imagePath) // ⚠️ 主线程解码!
holder.imageView.setImageBitmap(bitmap)
}
// 【优化后】- 使用Glide异步加载
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Glide.with(context)
.load(imagePath)
.placeholder(R.drawable.placeholder)
.into(holder.imageView)
}
问题2: 复杂文本测量 7ms
java
// 【优化前】- 每次都测量
textView.text = SpannableString(text).apply {
setSpan(ForegroundColorSpan(color), 0, length, 0)
setSpan(AbsoluteSizeSpan(20), 0, length, 0)
// 多个span导致复杂测量
}
// 【优化后】- 缓存测量结果
private val spannableCache = LruCache<String, SpannableString>(100)
fun getSpannable(text: String): SpannableString {
return spannableCache.get(text) ?: createSpannable(text).also {
spannableCache.put(text, it)
}
}
优化后验证:
Frame Timeline (优化后):
Frame #100: 15ms ✅ 绿色
Frame #101: 16ms ✅ 绿色
Frame #102: 14ms ✅ 绿色
Frame #103: 17ms ⚠️ 黄色 (偶尔)
Frame #104: 16ms ✅ 绿色
平均帧时间: 16ms
掉帧率: < 5% ✅ 达成目标!
4.5 案例3: CPU占用分析
场景: App在后台CPU占用高达30%,导致耗电快。
Step 1: 抓取后台运行Trace
bash
# 使用Perfetto抓取,包含CPU和电量数据
# 在ui.perfetto.dev配置:
# - Duration: 30s
# - Probes: Scheduling, CPU frequency, Battery
Step 2: 使用SQL查询分析CPU占用
在Perfetto SQL界面输入:
sql
-- 查询各线程的CPU占用时间
SELECT
thread.name AS thread_name,
process.name AS process_name,
SUM(sched.dur) / 1e9 AS cpu_time_seconds,
COUNT(*) AS schedule_count
FROM sched
JOIN thread USING (utid)
JOIN process USING (upid)
WHERE process.name = 'com.example.social'
GROUP BY thread.utid
ORDER BY cpu_time_seconds DESC
LIMIT 20;
查询结果:
thread_name cpu_time_seconds schedule_count
─────────────────────────────────────────────────────────
BackgroundWorker 8.2 1245 ← ⚠️ 异常!
main 2.1 856
RenderThread 0.8 234
binder:12345_1 0.5 123
...
Step 3: 分析BackgroundWorker线程
搜索"BackgroundWorker",放大查看:
BackgroundWorker Timeline:
00:00 - 00:02 运行 (处理数据) 2s
00:02 - 00:02 睡眠 0.1s
00:02 - 00:04 运行 (处理数据) 2s
00:04 - 00:04 睡眠 0.1s
00:04 - 00:06 运行 (处理数据) 2s
...
(每隔0.1秒唤醒一次,处理2秒!)
Step 4: 查看源码定位问题
查看堆栈,找到对应代码:
kotlin
// 【问题代码】
class BackgroundWorker : Thread() {
override fun run() {
while (true) {
processData() // 耗时操作
Thread.sleep(100) // 只睡眠0.1秒!
}
}
private fun processData() {
// 处理大量数据,耗时2秒
val data = loadDataFromDB()
analyzeData(data)
saveResult(data)
}
}
问题根因:
- 后台线程每0.1秒唤醒一次
- 每次执行耗时2秒的数据处理
- 实际上这个任务根本不需要这么频繁
Step 5: 优化方案
kotlin
// 【优化后】
class BackgroundWorker : Thread() {
override fun run() {
while (true) {
processData()
Thread.sleep(300_000) // 改为5分钟执行一次
}
}
}
// 【更好的方案】使用WorkManager
val workRequest = PeriodicWorkRequestBuilder<DataProcessor>(
15, TimeUnit.MINUTES // 每15分钟执行一次
).build()
WorkManager.getInstance(context).enqueue(workRequest)
优化效果:
优化前:
- 后台CPU占用: 30%
- 30秒内CPU时间: 8.2秒
- 电量消耗: 高
优化后:
- 后台CPU占用: < 1%
- 30秒内CPU时间: 0.1秒
- 电量消耗: 正常
五、代码插桩:自定义Trace事件
有时候系统默认的trace信息不够详细,我们需要在自己的代码中添加trace标记,这就是代码插桩(Instrumentation)。
5.1 Java/Kotlin代码插桩
使用android.os.Trace API添加trace标记:
基础用法:
kotlin
import android.os.Trace
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Trace.beginSection("MyActivity.onCreate")
super.onCreate(savedInstanceState)
Trace.beginSection("InitData")
initData()
Trace.endSection()
Trace.beginSection("SetupUI")
setupUI()
Trace.endSection()
Trace.endSection() // MyActivity.onCreate
}
private fun initData() {
Trace.beginSection("LoadConfig")
loadConfig()
Trace.endSection()
Trace.beginSection("ConnectDB")
connectDatabase()
Trace.endSection()
}
}
注意事项:
- ⚠️
beginSection()和endSection()必须成对出现 - ⚠️ 必须在同一个线程中调用
- ⚠️ Section名称最长127个字符
- ⚠️ 嵌套深度有限制(通常不超过20层)
使用扩展函数简化:
kotlin
// 定义扩展函数
inline fun <T> trace(sectionName: String, block: () -> T): T {
Trace.beginSection(sectionName)
try {
return block()
} finally {
Trace.endSection()
}
}
// 使用
override fun onCreate(savedInstanceState: Bundle?) {
trace("MyActivity.onCreate") {
super.onCreate(savedInstanceState)
trace("InitData") {
initData()
}
trace("SetupUI") {
setupUI()
}
}
}
5.2 Native代码插桩
对于C/C++代码,使用<cutils/trace.h>头文件:
cpp
#include <cutils/trace.h>
class MyNativeClass {
public:
void expensiveOperation() {
ATRACE_CALL(); // 自动跟踪整个函数
ATRACE_BEGIN("LoadData");
loadData();
ATRACE_END();
ATRACE_BEGIN("ProcessData");
processData();
ATRACE_END();
ATRACE_BEGIN("SaveResult");
saveResult();
ATRACE_END();
}
private:
void loadData() {
ATRACE_CALL(); // 跟踪loadData函数
// ... 加载数据
}
void processData() {
ATRACE_CALL();
// ... 处理数据
}
};
Android.mk配置:
makefile
LOCAL_SHARED_LIBRARIES := libcutils
CMake配置:
cmake
target_link_libraries(mynative cutils)
5.3 异步事件跟踪
对于异步操作(如网络请求、图片加载),使用异步trace:
kotlin
class ImageLoader {
private var cookie = 0
fun loadImageAsync(url: String, callback: (Bitmap) -> Unit) {
val currentCookie = cookie++
Trace.beginAsyncSection("LoadImage-$url", currentCookie)
thread {
try {
val bitmap = downloadAndDecode(url)
runOnUiThread {
Trace.endAsyncSection("LoadImage-$url", currentCookie)
callback(bitmap)
}
} catch (e: Exception) {
Trace.endAsyncSection("LoadImage-$url", currentCookie)
Log.e(TAG, "Failed to load image", e)
}
}
}
}
在Trace中的展示:
Timeline:
T0 beginAsyncSection("LoadImage", 123)
|
| (主线程继续执行其他操作)
|
T+200 endAsyncSection("LoadImage", 123)
在Trace中会显示一条横跨200ms的异步slice
5.4 Counter跟踪
跟踪数值变化(如内存使用、队列长度):
kotlin
class TaskQueue {
private val queue = ConcurrentLinkedQueue<Task>()
fun enqueue(task: Task) {
queue.add(task)
Trace.setCounter("TaskQueue.size", queue.size.toLong())
}
fun dequeue(): Task? {
val task = queue.poll()
Trace.setCounter("TaskQueue.size", queue.size.toLong())
return task
}
}
在Trace中会显示一条折线图,实时展示队列大小的变化。
5.5 最佳实践
1. 合理命名:
kotlin
// ❌ 不好的命名
Trace.beginSection("func1")
// ✅ 好的命名
Trace.beginSection("UserRepository.loadProfile")
2. 控制粒度:
kotlin
// ❌ 太细粒度 (每个小函数都trace)
fun processData(data: List<Item>) {
Trace.beginSection("processData")
for (item in data) {
Trace.beginSection("processItem") // 不必要
process(item)
Trace.endSection()
}
Trace.endSection()
}
// ✅ 合适的粒度
fun processData(data: List<Item>) {
Trace.beginSection("processData")
for (item in data) {
process(item) // 不需要每个item都trace
}
Trace.endSection()
}
3. Release版本优化:
kotlin
// 在release版本中禁用trace以减少开销
inline fun traceDebug(sectionName: String, block: () -> Unit) {
if (BuildConfig.DEBUG) {
Trace.beginSection(sectionName)
try {
block()
} finally {
Trace.endSection()
}
} else {
block()
}
}
4. 关键路径优先 :
优先在以下场景添加trace:
- ✅ 启动流程的关键步骤
- ✅ 网络请求的各个阶段
- ✅ 数据库操作
- ✅ 图片加载和解码
- ✅ 复杂计算
- ✅ RecyclerView的bind操作
六、进阶技巧
6.1 Perfetto高级SQL查询
查询所有GC事件及其耗时:
sql
SELECT
ts / 1e9 AS time_sec,
dur / 1e6 AS duration_ms,
name AS gc_type,
CASE
WHEN dur < 5e6 THEN 'Minor'
WHEN dur < 50e6 THEN 'Major'
ELSE 'Critical'
END AS severity
FROM slice
WHERE name GLOB 'Heap*' OR name GLOB 'GC*'
ORDER BY dur DESC;
查询主线程耗时最长的20个方法:
sql
SELECT
name,
dur / 1e6 AS duration_ms,
ts / 1e9 AS start_time_sec
FROM slice
JOIN thread_track ON slice.track_id = thread_track.id
JOIN thread ON thread_track.utid = thread.utid
WHERE
thread.name = 'main'
AND depth = 0 -- 只看顶层调用
AND dur > 10e6 -- 超过10ms
ORDER BY dur DESC
LIMIT 20;
查询Binder调用统计:
sql
SELECT
thread.name AS caller_thread,
slice.name AS binder_method,
COUNT(*) AS call_count,
SUM(dur) / 1e6 AS total_ms,
AVG(dur) / 1e6 AS avg_ms,
MAX(dur) / 1e6 AS max_ms
FROM slice
JOIN thread_track ON slice.track_id = thread_track.id
JOIN thread ON thread_track.utid = thread.utid
WHERE slice.name LIKE 'binder%'
GROUP BY thread.name, slice.name
ORDER BY total_ms DESC;
分析帧耗时分布:
sql
SELECT
CASE
WHEN dur < 16.6e6 THEN '正常 (<16.6ms)'
WHEN dur < 33e6 THEN '轻微掉帧 (16.6-33ms)'
WHEN dur < 50e6 THEN '中度掉帧 (33-50ms)'
ELSE '严重掉帧 (>50ms)'
END AS frame_category,
COUNT(*) AS frame_count,
ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM actual_frame_timeline_slice), 2) AS percentage
FROM actual_frame_timeline_slice
GROUP BY frame_category
ORDER BY frame_count DESC;
6.2 自动化分析脚本
使用Python和Perfetto Trace Processor进行自动化分析:
python
#!/usr/bin/env python3
"""
启动时间自动化分析脚本
"""
from perfetto.trace_processor import TraceProcessor
import sys
def analyze_startup(trace_file):
tp = TraceProcessor(trace=trace_file)
# 查询Application.onCreate的耗时
query = '''
SELECT
ts / 1e9 AS start_sec,
dur / 1e6 AS duration_ms
FROM slice
WHERE name = 'Application.onCreate'
'''
result = tp.query(query)
if len(result) > 0:
app_create_time = result.duration_ms[0]
print(f"Application.onCreate: {app_create_time:.2f}ms")
else:
print("未找到Application.onCreate事件")
return
# 查询Activity.onCreate的耗时
query = '''
SELECT
ts / 1e9 AS start_sec,
dur / 1e6 AS duration_ms
FROM slice
WHERE name LIKE '%Activity.onCreate%'
'''
result = tp.query(query)
if len(result) > 0:
activity_create_time = result.duration_ms[0]
print(f"Activity.onCreate: {activity_create_time:.2f}ms")
else:
print("未找到Activity.onCreate事件")
return
# 查询首帧渲染时间
query = '''
SELECT MIN(ts) / 1e9 AS first_frame_sec
FROM actual_frame_timeline_slice
'''
result = tp.query(query)
first_frame_time = result.first_frame_sec[0]
# 计算总启动时间
query = '''
SELECT MIN(ts) / 1e9 AS app_start_sec
FROM slice
WHERE name = 'Application.onCreate'
'''
result = tp.query(query)
app_start_time = result.app_start_sec[0]
total_startup = (first_frame_time - app_start_time) * 1000
print(f"总启动时间: {total_startup:.2f}ms")
# 性能评级
if total_startup < 1000:
print("✅ 性能评级: 优秀")
elif total_startup < 2000:
print("⚠️ 性能评级: 良好")
elif total_startup < 3000:
print("⚠️ 性能评级: 一般")
else:
print("❌ 性能评级: 需要优化")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("用法: python analyze_startup.py trace.perfetto-trace")
sys.exit(1)
analyze_startup(sys.argv[1])
使用方法:
bash
python analyze_startup.py launch.perfetto-trace
# 输出:
# Application.onCreate: 450.23ms
# Activity.onCreate: 380.45ms
# 总启动时间: 1245.67ms
# ⚠️ 性能评级: 良好
七、常见问题与解决
7.1 Trace抓取失败
问题: 执行systrace/perfetto命令后无输出或报错
可能原因与解决:
- 设备未授权
bash
# 检查设备连接
adb devices
# 如果显示 "unauthorized",重新授权
adb kill-server
adb start-server
- 权限不足
bash
# 需要root权限
adb root
adb remount
- buffer大小不足
bash
# 增大buffer
python systrace.py -b 65536 ... # 64MB
- category不支持
bash
# 查看设备支持的category
adb shell atrace --list_categories
7.2 Trace文件过大
问题: Trace文件几百MB,Chrome打不开或很卡
解决方案:
- 减少抓取时长
bash
# 从10秒改为5秒
-t 5
- 只选择必要的category
bash
# 不要用 --all,精确指定
python systrace.py ... sched gfx view am
-
使用Perfetto代替Systrace
Perfetto的protobuf格式文件更小,加载更快
-
使用Perfetto的在线查看
不要下载文件,直接在 ui.perfetto.dev 上分析
7.3 找不到关键事件
问题: 在Trace中搜索不到我的函数调用
可能原因:
- 没有启用app category
bash
# 确保包含 app category
python systrace.py ... app
- 代码没有插桩
kotlin
// 需要添加 Trace.beginSection()
Trace.beginSection("MyFunction")
myFunction()
Trace.endSection()
- 函数执行时间太短
- 小于0.1ms的函数可能不会显示
- 放大时间轴查看
- 没有指定应用包名
bash
# 指定包名以抓取应用的trace
python systrace.py -a com.example.myapp ...
7.4 Trace看不懂
问题: Trace文件打开了,但不知道怎么分析
建议:
-
从宏观到微观
- 先缩小查看整体
- 识别大块耗时
- 再放大查看细节
-
关注主线程
- 主线程的空白区域可能在等待
- 查看主线程在做什么
-
对比VSYNC信号
- 看渲染是否跟上VSYNC节奏
- 识别掉帧原因
-
搜索关键事件
activityStart,onCreate,draw,measure- 使用搜索功能快速定位
-
查看CPU调度
- 线程是在运行还是等待
- CPU是否被其他进程占用
八、工具对比与选择建议
8.1 何时使用Systrace
推荐场景:
- ✅ Android 9及以下系统
- ✅ 简单的性能分析(启动、卡顿)
- ✅ 不需要复杂查询
- ✅ 团队成员对Chrome Trace Viewer熟悉
优势:
- 简单易用,一行命令搞定
- 兼容老版本Android
- HTML文件可以离线查看
8.2 何时使用Perfetto
推荐场景:
- ✅ Android 10及以上系统 (强烈推荐)
- ✅ 需要SQL查询进行深度分析
- ✅ 需要内存、电量等额外数据
- ✅ 需要自动化分析(CI/CD)
- ✅ 大型trace文件
优势:
- 现代化UI,体验极佳
- SQL查询引擎,分析能力强
- 文件更小,加载更快
- 持续更新,功能丰富
8.3 选择建议
| 场景 | 推荐工具 | 理由 |
|---|---|---|
| 新项目 | Perfetto | 功能强大,体验好 |
| 老项目(Android 9-) | Systrace | 兼容性 |
| 启动优化 | Perfetto | SQL查询方便统计 |
| 卡顿分析 | Perfetto | Frame Timeline强大 |
| 快速排查 | Systrace | 简单快速 |
| 深度分析 | Perfetto | SQL查询 + 多数据源 |
| CI集成 | Perfetto | Trace Processor API |
| 学习入门 | Systrace | 简单易懂 |
总结: 如果你的项目主要支持Android 10+,强烈推荐从现在开始使用Perfetto!
九、总结与展望
9.1 核心要点回顾
通过本文的学习,我们全面掌握了Systrace和Perfetto两大性能分析工具:
工具使用:
- ✅ 理解了Trace的工作原理和应用场景
- ✅ 掌握了Systrace和Perfetto的抓取方法
- ✅ 学会了Perfetto UI的各项功能
- ✅ 了解了两种工具的对比和选择
分析方法:
- ✅ 建立了系统化的Trace分析思路
- ✅ 学会了定位启动慢、卡顿、CPU占用等问题
- ✅ 掌握了Frame Timeline分析技巧
- ✅ 理解了关键性能指标的含义
实战技能:
- ✅ 掌握了Java/Kotlin/Native代码插桩
- ✅ 学会了Perfetto SQL高级查询
- ✅ 了解了自动化分析和CI集成
- ✅ 能够独立完成性能问题的排查
9.2 最佳实践总结
DO (推荐做法):
- ✅ 优先使用Perfetto (Android 10+)
- ✅ 在关键路径添加trace标记
- ✅ 定期进行性能回归测试
- ✅ 建立性能基线和阈值
- ✅ 使用SQL查询进行深度分析
- ✅ 保存重要的trace文件以便对比
- ✅ 团队内分享trace分析经验
DON'T (避免做法):
- ❌ 不要凭感觉优化,要用数据说话
- ❌ 不要只优化局部,要看整体
- ❌ 不要在release版本保留过多trace
- ❌ 不要忽略小的性能问题
- ❌ 不要等用户反馈才开始优化
9.3 进一步学习资源
官方文档:
- Systrace: https://developer.android.com/topic/performance/tracing
- Perfetto: https://perfetto.dev/
- Perfetto SQL: https://perfetto.dev/docs/analysis/sql-tables
工具:
- Perfetto UI: https://ui.perfetto.dev/
- Trace Processor: https://perfetto.dev/docs/analysis/trace-processor
社区:
- Android Performance Patterns (YouTube)
- Android Developers Blog
- Stack Overflow (标签: android-performance)
相关文章:
记住:性能优化不是一次性的工作,而是一个持续的过程。定期使用Trace工具检查性能,及时发现和解决问题,才能保证应用始终保持最佳状态。
当你下次遇到性能问题时,不要慌张,打开Perfetto,抓个trace,答案就在那里! 📊✨
作者简介: 多年Android系统开发经验,专注于系统稳定性与性能优化领域。欢迎关注本系列,一起深入Android系统的精彩世界!