鏇存柊鏂囨。
This commit is contained in:
46
.obsidian/workspace.json
vendored
46
.obsidian/workspace.json
vendored
@@ -27,16 +27,30 @@
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "docs/学习笔记/高级性能问题分析目录.md",
|
||||
"file": "docs/Obsidian笔记体系/Daily/2026-01-13.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "高级性能问题分析目录"
|
||||
"title": "2026-01-13"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "59894255df52f532",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "docs/Obsidian笔记体系/Projects/知你-调测/知你--调测.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "知你--调测"
|
||||
}
|
||||
}
|
||||
],
|
||||
"currentTab": 1
|
||||
"currentTab": 2
|
||||
}
|
||||
],
|
||||
"direction": "vertical"
|
||||
@@ -198,16 +212,24 @@
|
||||
"command-palette:打开命令面板": false
|
||||
}
|
||||
},
|
||||
"active": "25c9f7051aac05b3",
|
||||
"active": "59894255df52f532",
|
||||
"lastOpenFiles": [
|
||||
"docs/学习笔记/php/未命名.md",
|
||||
"docs/学习笔记/高级性能问题分析目录.md",
|
||||
"docs/Obsidian笔记体系/Daily/2026-01-14.md",
|
||||
"docs/Obsidian笔记体系/Daily/2024-06-02.md",
|
||||
"docs/Obsidian笔记体系/Daily/2026-01-13.md",
|
||||
"docs/Obsidian笔记体系/Daily/2026-01-15.md",
|
||||
"docs/Obsidian笔记体系/Daily/2026-01-20.md",
|
||||
"docs/学习笔记/高级性能问题分析.md",
|
||||
"docs/学习笔记/高级性能问题分析目录.md",
|
||||
"docs/学习笔记/产品经理/需求管理详解.md",
|
||||
"docs/学习笔记/产品经理/项目管理详解.md",
|
||||
"docs/Obsidian/高频命令.md",
|
||||
"docs/Obsidian笔记体系/Projects/知你-调测/知你--调测.md",
|
||||
"docs/学习笔记/基础性能问题分析(ODM).md",
|
||||
"docs/Obsidian笔记体系/Areas/09-调试与工具链/Systrace_Perfetto全解读.md",
|
||||
"docs/学习笔记/php/未命名.md",
|
||||
"docs/git/git设置用户名和邮箱.md",
|
||||
"docs/学习笔记/产品经理/产品经理--写文档.md",
|
||||
"docs/学习笔记/产品经理/项目管理详解.md",
|
||||
"docs/学习笔记/产品经理/需求管理详解.md",
|
||||
"docs/学习笔记/产品经理/沟通与表达详解.md",
|
||||
"docs/学习笔记/产品经理/数据分析详解.md",
|
||||
"docs/学习笔记/产品经理/产品运营详解.md",
|
||||
@@ -219,14 +241,6 @@
|
||||
"docs/学习笔记/产品经理/产品经理--些迭代文档.md",
|
||||
"docs/学习笔记/产品经理",
|
||||
"docs/产品经理/沟通与表达详解.md",
|
||||
"docs/产品经理/产品运营详解.md",
|
||||
"docs/产品经理/数据分析详解.md",
|
||||
"docs/git/git常用命令.md",
|
||||
"docs/cursor/cursor.md",
|
||||
"docs/产品经理/产品经理技能.md",
|
||||
"docs/产品经理/项目管理详解.md",
|
||||
"docs/产品经理/产品设计详解.md",
|
||||
"docs/产品经理/需求管理详解.md",
|
||||
"Pasted image 20260129111501.png",
|
||||
"Pasted image 20260129111451.png",
|
||||
"Pasted image 20260129111437.png",
|
||||
|
||||
835
docs/学习笔记/perfeto看trace技巧.md
Normal file
835
docs/学习笔记/perfeto看trace技巧.md
Normal file
@@ -0,0 +1,835 @@
|
||||
# Perfetto看Trace技巧
|
||||
|
||||
## 一、Perfetto基础操作
|
||||
|
||||
### 1. 录制Trace
|
||||
|
||||
#### 1.1 命令行录制
|
||||
|
||||
```bash
|
||||
# 基础录制命令
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace
|
||||
|
||||
# 指定时长(10秒)
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 10s
|
||||
|
||||
# 指定应用包名
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -a com.example.app
|
||||
|
||||
# 拉取Trace文件
|
||||
adb pull /data/misc/perfetto-traces/trace trace.pb
|
||||
```
|
||||
|
||||
#### 1.2 使用配置文件录制
|
||||
|
||||
**启动分析配置文件(startup_config.pb):**
|
||||
```protobuf
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
ftrace_events: "sched/sched_switch"
|
||||
ftrace_events: "sched/sched_waking"
|
||||
ftrace_events: "sched/sched_process_exit"
|
||||
ftrace_events: "sched/sched_process_free"
|
||||
ftrace_events: "task/task_newtask"
|
||||
ftrace_events: "task/task_rename"
|
||||
ftrace_events: "power/cpu_frequency"
|
||||
ftrace_events: "power/cpu_idle"
|
||||
ftrace_events: "power/suspend_resume"
|
||||
atrace_categories: "am"
|
||||
atrace_categories: "wm"
|
||||
atrace_categories: "gfx"
|
||||
atrace_categories: "view"
|
||||
atrace_categories: "binder_driver"
|
||||
atrace_categories: "binder_lock"
|
||||
atrace_categories: "input"
|
||||
atrace_categories: "res"
|
||||
atrace_categories: "dalvik"
|
||||
buffer_size_kb: 4096
|
||||
drain_period_ms: 250
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "android.surfaceflinger.frame"
|
||||
}
|
||||
}
|
||||
|
||||
duration_ms: 10000
|
||||
```
|
||||
|
||||
**游戏丢帧分析配置文件(jank_config.pb):**
|
||||
```protobuf
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
ftrace_events: "sched/sched_switch"
|
||||
ftrace_events: "sched/sched_waking"
|
||||
ftrace_events: "power/cpu_frequency"
|
||||
ftrace_events: "power/cpu_idle"
|
||||
ftrace_events: "gfx/mali_gpu_total"
|
||||
ftrace_events: "gpu_mem_total/gpu_mem_total"
|
||||
atrace_categories: "gfx"
|
||||
atrace_categories: "view"
|
||||
atrace_categories: "sched"
|
||||
atrace_categories: "freq"
|
||||
atrace_categories: "idle"
|
||||
buffer_size_kb: 8192
|
||||
drain_period_ms: 250
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "android.surfaceflinger.frame"
|
||||
}
|
||||
}
|
||||
|
||||
duration_ms: 30000
|
||||
```
|
||||
|
||||
#### 1.3 使用配置文件录制
|
||||
|
||||
```bash
|
||||
# 将配置文件推送到设备
|
||||
adb push startup_config.pb /data/local/tmp/
|
||||
|
||||
# 使用配置文件录制
|
||||
adb shell perfetto -c /data/local/tmp/startup_config.pb -o /data/local/tmp/trace.pb
|
||||
|
||||
# 拉取Trace文件
|
||||
adb pull /data/local/tmp/trace.pb trace.pb
|
||||
```
|
||||
|
||||
### 2. 打开Trace文件
|
||||
|
||||
#### 2.1 Web UI打开
|
||||
|
||||
1. 访问 https://ui.perfetto.dev/
|
||||
2. 点击 "Open trace file"
|
||||
3. 选择 trace.pb 文件
|
||||
4. 等待加载完成
|
||||
|
||||
#### 2.2 Android Studio打开
|
||||
|
||||
1. 打开 Android Studio
|
||||
2. 连接设备
|
||||
3. 打开 Profiler
|
||||
4. 选择 CPU Profiler
|
||||
5. 点击 "Record" 开始录制
|
||||
6. 执行操作后停止录制
|
||||
|
||||
## 二、应用冷启动分析技巧
|
||||
|
||||
### 1. 启动流程关键节点
|
||||
|
||||
#### 1.1 启动阶段划分
|
||||
|
||||
**阶段1:进程创建(Process Creation)**
|
||||
- 查找点:`ActivityManager: startProcess`
|
||||
- 关键指标:进程fork时间、进程初始化时间
|
||||
- 查看位置:SystemServer进程的ActivityManagerService
|
||||
|
||||
**阶段2:Application初始化(Application Init)**
|
||||
- 查找点:`ActivityThread: handleBindApplication`
|
||||
- 关键指标:Application.onCreate()耗时
|
||||
- 查看位置:目标应用主线程
|
||||
|
||||
**阶段3:Activity创建(Activity Creation)**
|
||||
- 查找点:`ActivityThread: performLaunchActivity`
|
||||
- 关键指标:Activity.onCreate()耗时
|
||||
- 查看位置:目标应用主线程
|
||||
|
||||
**阶段4:首帧渲染(First Frame)**
|
||||
- 查找点:`Choreographer#doFrame` 或 `SurfaceFlinger: onMessageReceived`
|
||||
- 关键指标:首帧输出时间
|
||||
- 查看位置:应用主线程、RenderThread、SurfaceFlinger
|
||||
|
||||
#### 1.2 在Perfetto中定位启动节点
|
||||
|
||||
**步骤1:找到应用进程**
|
||||
```
|
||||
1. 在左侧进程列表中找到目标应用进程
|
||||
2. 展开进程,找到主线程(通常是包名)
|
||||
3. 点击主线程,查看时间线
|
||||
```
|
||||
|
||||
**步骤2:查找启动关键事件**
|
||||
```
|
||||
1. 在搜索框输入 "ActivityManager: startProcess"
|
||||
2. 找到进程启动事件
|
||||
3. 标记为起点(按M键)
|
||||
```
|
||||
|
||||
**步骤3:查找Application初始化**
|
||||
```
|
||||
1. 在主线程时间线中查找 "handleBindApplication"
|
||||
2. 展开查看 Application.onCreate()
|
||||
3. 分析初始化耗时
|
||||
```
|
||||
|
||||
**步骤4:查找Activity创建**
|
||||
```
|
||||
1. 在主线程时间线中查找 "performLaunchActivity"
|
||||
2. 展开查看 Activity.onCreate()
|
||||
3. 分析创建耗时
|
||||
```
|
||||
|
||||
**步骤5:查找首帧**
|
||||
```
|
||||
1. 在SurfaceFlinger进程中查找 "onMessageReceived"
|
||||
2. 或查找应用的 "Choreographer#doFrame"
|
||||
3. 标记为终点(按M键)
|
||||
4. 计算总耗时
|
||||
```
|
||||
|
||||
### 2. 启动性能瓶颈分析
|
||||
|
||||
#### 2.1 主线程阻塞分析
|
||||
|
||||
**查找主线程Sleep状态:**
|
||||
```
|
||||
1. 选中应用主线程
|
||||
2. 查看线程状态(Running/Sleep/Runnable)
|
||||
3. 查找长时间Sleep的片段
|
||||
4. 分析Sleep原因(等待Binder、等待锁、等待IO等)
|
||||
```
|
||||
|
||||
**常见阻塞场景:**
|
||||
|
||||
1. **等待Binder调用**
|
||||
- 特征:主线程处于Sleep状态,等待Binder返回
|
||||
- 查找:查看wakeup信息,找到对应的Binder调用
|
||||
- 分析:检查SystemServer中的Binder处理耗时
|
||||
|
||||
2. **等待锁**
|
||||
- 特征:主线程处于Sleep状态,等待锁释放
|
||||
- 查找:查看wakeup信息,找到锁持有者
|
||||
- 分析:检查锁持有时间是否过长
|
||||
|
||||
3. **等待IO**
|
||||
- 特征:主线程处于UninterruptibleSleep-IO状态
|
||||
- 查找:查看IO操作(文件读写、数据库操作)
|
||||
- 分析:检查IO操作是否在主线程执行
|
||||
|
||||
#### 2.2 CPU调度分析
|
||||
|
||||
**查看CPU频率:**
|
||||
```
|
||||
1. 在顶部选择 "CPU frequency"
|
||||
2. 查看启动期间CPU频率变化
|
||||
3. 检查是否降频或频率不足
|
||||
```
|
||||
|
||||
**查看CPU使用率:**
|
||||
```
|
||||
1. 在顶部选择 "CPU usage"
|
||||
2. 查看启动期间CPU使用情况
|
||||
3. 检查是否有CPU瓶颈
|
||||
```
|
||||
|
||||
**查看任务调度:**
|
||||
```
|
||||
1. 在CPU核心时间线中查看任务调度
|
||||
2. 检查关键任务是否跑在小核
|
||||
3. 检查是否有调度延迟
|
||||
```
|
||||
|
||||
#### 2.3 内存压力分析
|
||||
|
||||
**查看GC活动:**
|
||||
```
|
||||
1. 在主线程中查找 "HeapTaskDaemon"
|
||||
2. 查看GC频率和耗时
|
||||
3. 检查是否频繁GC影响启动
|
||||
```
|
||||
|
||||
**查看内存分配:**
|
||||
```
|
||||
1. 在进程信息中查看内存使用
|
||||
2. 检查内存分配速度
|
||||
3. 识别大对象分配
|
||||
```
|
||||
|
||||
#### 2.4 系统服务分析
|
||||
|
||||
**SystemServer分析:**
|
||||
```
|
||||
1. 找到SystemServer进程
|
||||
2. 查看ActivityManagerService处理耗时
|
||||
3. 查看WindowManagerService处理耗时
|
||||
4. 检查是否有锁竞争或处理延迟
|
||||
```
|
||||
|
||||
**SurfaceFlinger分析:**
|
||||
```
|
||||
1. 找到SurfaceFlinger进程
|
||||
2. 查看Buffer分配和合成耗时
|
||||
3. 检查是否有合成延迟
|
||||
```
|
||||
|
||||
### 3. 启动优化验证
|
||||
|
||||
#### 3.1 对比分析
|
||||
|
||||
**优化前后对比:**
|
||||
```
|
||||
1. 录制优化前的Trace
|
||||
2. 录制优化后的Trace
|
||||
3. 在Perfetto中同时打开两个Trace
|
||||
4. 对比关键节点耗时
|
||||
5. 验证优化效果
|
||||
```
|
||||
|
||||
**关键指标对比:**
|
||||
- 进程创建时间
|
||||
- Application初始化时间
|
||||
- Activity创建时间
|
||||
- 首帧输出时间
|
||||
- 总启动时间
|
||||
|
||||
#### 3.2 SQL查询分析
|
||||
|
||||
**查询启动总耗时:**
|
||||
```sql
|
||||
-- 查询进程启动到首帧的时间
|
||||
SELECT
|
||||
(SELECT ts FROM slice WHERE name = 'Choreographer#doFrame' LIMIT 1) -
|
||||
(SELECT ts FROM slice WHERE name LIKE '%startProcess%' LIMIT 1)
|
||||
AS startup_time_ns;
|
||||
```
|
||||
|
||||
**查询Application初始化耗时:**
|
||||
```sql
|
||||
-- 查询Application.onCreate耗时
|
||||
SELECT
|
||||
name,
|
||||
dur / 1000000.0 AS duration_ms
|
||||
FROM slice
|
||||
WHERE name LIKE '%Application.onCreate%'
|
||||
ORDER BY dur DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
**查询主线程阻塞时间:**
|
||||
```sql
|
||||
-- 查询主线程Sleep时间
|
||||
SELECT
|
||||
SUM(dur) / 1000000.0 AS total_sleep_ms
|
||||
FROM sched
|
||||
WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%main%' LIMIT 1)
|
||||
AND state = 'S';
|
||||
```
|
||||
|
||||
## 三、游戏丢帧问题分析技巧
|
||||
|
||||
### 1. 帧率分析
|
||||
|
||||
#### 1.1 查看帧率信息
|
||||
|
||||
**方法1:查看Frame信息**
|
||||
```
|
||||
1. 在顶部选择 "Frame Timeline"
|
||||
2. 查看每一帧的状态(绿色/黄色/红色)
|
||||
3. 绿色:正常帧(< 16.67ms)
|
||||
4. 黄色:轻微掉帧(16.67-33.33ms)
|
||||
5. 红色:严重掉帧(> 33.33ms)
|
||||
```
|
||||
|
||||
**方法2:查看VSync信息**
|
||||
```
|
||||
1. 在SurfaceFlinger进程中查找 "VSync"
|
||||
2. 查看VSync周期是否正常(60Hz = 16.67ms)
|
||||
3. 检查是否有VSync丢失
|
||||
```
|
||||
|
||||
**方法3:查看Choreographer**
|
||||
```
|
||||
1. 在应用主线程中查找 "Choreographer#doFrame"
|
||||
2. 查看每一帧的处理时间
|
||||
3. 识别耗时较长的帧
|
||||
```
|
||||
|
||||
#### 1.2 丢帧统计
|
||||
|
||||
**使用SQL查询丢帧:**
|
||||
```sql
|
||||
-- 查询所有掉帧(> 16.67ms)
|
||||
SELECT
|
||||
COUNT(*) AS jank_count,
|
||||
AVG(dur) / 1000000.0 AS avg_duration_ms,
|
||||
MAX(dur) / 1000000.0 AS max_duration_ms
|
||||
FROM slice
|
||||
WHERE name = 'Choreographer#doFrame'
|
||||
AND dur > 16666667;
|
||||
```
|
||||
|
||||
**查询连续丢帧:**
|
||||
```sql
|
||||
-- 查询连续丢帧的情况
|
||||
SELECT
|
||||
ts,
|
||||
dur / 1000000.0 AS duration_ms
|
||||
FROM slice
|
||||
WHERE name = 'Choreographer#doFrame'
|
||||
AND dur > 16666667
|
||||
ORDER BY ts;
|
||||
```
|
||||
|
||||
### 2. 渲染性能分析
|
||||
|
||||
#### 2.1 RenderThread分析
|
||||
|
||||
**查看RenderThread状态:**
|
||||
```
|
||||
1. 找到应用的RenderThread线程
|
||||
2. 查看线程状态和执行情况
|
||||
3. 检查是否有阻塞或延迟
|
||||
```
|
||||
|
||||
**分析渲染耗时:**
|
||||
```
|
||||
1. 在RenderThread中查找关键操作:
|
||||
- drawFrame:绘制帧
|
||||
- dequeueBuffer:获取Buffer
|
||||
- queueBuffer:提交Buffer
|
||||
2. 分析每个操作的耗时
|
||||
3. 识别渲染瓶颈
|
||||
```
|
||||
|
||||
**常见渲染问题:**
|
||||
|
||||
1. **dequeueBuffer耗时**
|
||||
- 特征:RenderThread等待Buffer分配
|
||||
- 原因:SurfaceFlinger繁忙或Buffer不足
|
||||
- 分析:查看SurfaceFlinger进程状态
|
||||
|
||||
2. **queueBuffer耗时**
|
||||
- 特征:RenderThread等待Buffer提交
|
||||
- 原因:SurfaceFlinger处理延迟
|
||||
- 分析:查看SurfaceFlinger合成耗时
|
||||
|
||||
3. **drawFrame耗时**
|
||||
- 特征:RenderThread绘制耗时过长
|
||||
- 原因:绘制内容复杂、GPU性能不足
|
||||
- 分析:查看GPU使用情况和绘制命令
|
||||
|
||||
#### 2.2 GPU性能分析
|
||||
|
||||
**查看GPU使用率:**
|
||||
```
|
||||
1. 在顶部选择 "GPU frequency" 或 "GPU usage"
|
||||
2. 查看GPU频率和使用率
|
||||
3. 检查是否有GPU瓶颈
|
||||
```
|
||||
|
||||
**查看GPU渲染时间:**
|
||||
```
|
||||
1. 在RenderThread中查找GPU相关操作
|
||||
2. 查看GPU渲染耗时
|
||||
3. 检查是否有GPU过载
|
||||
```
|
||||
|
||||
**分析GPU瓶颈:**
|
||||
```
|
||||
1. 查看GPU频率是否达到最大值
|
||||
2. 查看GPU使用率是否过高
|
||||
3. 检查是否有GPU调度问题
|
||||
```
|
||||
|
||||
#### 2.3 SurfaceFlinger分析
|
||||
|
||||
**查看合成耗时:**
|
||||
```
|
||||
1. 找到SurfaceFlinger进程
|
||||
2. 查看合成操作耗时
|
||||
3. 检查是否有合成延迟
|
||||
```
|
||||
|
||||
**查看Buffer管理:**
|
||||
```
|
||||
1. 查看Buffer分配和释放
|
||||
2. 检查Buffer队列状态
|
||||
3. 识别Buffer泄漏或不足
|
||||
```
|
||||
|
||||
### 3. 主线程性能分析
|
||||
|
||||
#### 3.1 主线程耗时分析
|
||||
|
||||
**查找耗时操作:**
|
||||
```
|
||||
1. 选中应用主线程
|
||||
2. 按耗时排序(点击Duration列)
|
||||
3. 查看耗时最长的操作
|
||||
4. 分析是否可以优化
|
||||
```
|
||||
|
||||
**使用SQL查询耗时操作:**
|
||||
```sql
|
||||
-- 查询主线程耗时最长的操作
|
||||
SELECT
|
||||
name,
|
||||
dur / 1000000.0 AS duration_ms,
|
||||
ts
|
||||
FROM slice
|
||||
WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%main%' LIMIT 1)
|
||||
ORDER BY dur DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
#### 3.2 主线程阻塞分析
|
||||
|
||||
**查找阻塞点:**
|
||||
```
|
||||
1. 查看主线程状态变化
|
||||
2. 查找长时间Sleep的片段
|
||||
3. 分析阻塞原因
|
||||
```
|
||||
|
||||
**常见阻塞场景:**
|
||||
|
||||
1. **等待网络请求**
|
||||
- 特征:主线程等待网络响应
|
||||
- 解决:使用异步网络请求
|
||||
|
||||
2. **等待数据库操作**
|
||||
- 特征:主线程等待数据库读写
|
||||
- 解决:使用异步数据库操作
|
||||
|
||||
3. **等待文件IO**
|
||||
- 特征:主线程等待文件读写
|
||||
- 解决:使用异步IO操作
|
||||
|
||||
4. **等待锁**
|
||||
- 特征:主线程等待锁释放
|
||||
- 解决:优化锁的使用
|
||||
|
||||
### 4. CPU性能分析
|
||||
|
||||
#### 4.1 CPU频率分析
|
||||
|
||||
**查看CPU频率:**
|
||||
```
|
||||
1. 在顶部选择 "CPU frequency"
|
||||
2. 查看游戏运行期间CPU频率
|
||||
3. 检查是否降频或频率不足
|
||||
```
|
||||
|
||||
**分析频率问题:**
|
||||
```
|
||||
1. 检查CPU频率是否达到最大值
|
||||
2. 查看是否有温控降频
|
||||
3. 检查频率调度是否合理
|
||||
```
|
||||
|
||||
#### 4.2 CPU使用率分析
|
||||
|
||||
**查看CPU使用率:**
|
||||
```
|
||||
1. 在顶部选择 "CPU usage"
|
||||
2. 查看各核心使用情况
|
||||
3. 检查是否有CPU瓶颈
|
||||
```
|
||||
|
||||
**分析CPU负载:**
|
||||
```
|
||||
1. 检查CPU使用率是否过高
|
||||
2. 查看是否有核心过载
|
||||
3. 检查负载是否均衡
|
||||
```
|
||||
|
||||
#### 4.3 CPU调度分析
|
||||
|
||||
**查看任务调度:**
|
||||
```
|
||||
1. 在CPU核心时间线中查看任务调度
|
||||
2. 检查关键任务是否跑在小核
|
||||
3. 查看调度延迟
|
||||
```
|
||||
|
||||
**分析调度问题:**
|
||||
```
|
||||
1. 检查关键线程是否绑定到高性能核心
|
||||
2. 查看线程优先级是否合理
|
||||
3. 检查是否有调度延迟
|
||||
```
|
||||
|
||||
### 5. 内存性能分析
|
||||
|
||||
#### 5.1 GC压力分析
|
||||
|
||||
**查看GC活动:**
|
||||
```
|
||||
1. 在主线程中查找 "HeapTaskDaemon"
|
||||
2. 查看GC频率和耗时
|
||||
3. 检查是否频繁GC影响帧率
|
||||
```
|
||||
|
||||
**分析GC影响:**
|
||||
```
|
||||
1. 检查GC是否在关键帧期间发生
|
||||
2. 查看GC耗时是否过长
|
||||
3. 分析GC对帧率的影响
|
||||
```
|
||||
|
||||
#### 5.2 内存分配分析
|
||||
|
||||
**查看内存分配:**
|
||||
```
|
||||
1. 在进程信息中查看内存使用
|
||||
2. 检查内存分配速度
|
||||
3. 识别大对象分配
|
||||
```
|
||||
|
||||
**分析内存问题:**
|
||||
```
|
||||
1. 检查是否有内存泄漏
|
||||
2. 查看内存碎片化情况
|
||||
3. 分析内存分配模式
|
||||
```
|
||||
|
||||
## 四、Perfetto高级技巧
|
||||
|
||||
### 1. 自定义Trace标记
|
||||
|
||||
#### 1.1 在代码中添加Trace
|
||||
|
||||
**Java/Kotlin代码:**
|
||||
```java
|
||||
import android.os.Trace;
|
||||
|
||||
// 开始标记
|
||||
Trace.beginSection("my_custom_section");
|
||||
|
||||
// 执行代码
|
||||
doSomething();
|
||||
|
||||
// 结束标记
|
||||
Trace.endSection();
|
||||
```
|
||||
|
||||
**C++代码:**
|
||||
```cpp
|
||||
#include <utils/Trace.h>
|
||||
|
||||
// 开始标记
|
||||
ATRACE_BEGIN("my_custom_section");
|
||||
|
||||
// 执行代码
|
||||
doSomething();
|
||||
|
||||
// 结束标记
|
||||
ATRACE_END();
|
||||
```
|
||||
|
||||
#### 1.2 在Perfetto中查看自定义标记
|
||||
|
||||
```
|
||||
1. 在搜索框输入自定义标记名称
|
||||
2. 找到对应的Trace事件
|
||||
3. 分析该代码段的性能
|
||||
```
|
||||
|
||||
### 2. SQL查询技巧
|
||||
|
||||
#### 2.1 常用查询
|
||||
|
||||
**查询帧率统计:**
|
||||
```sql
|
||||
-- 计算平均帧率
|
||||
SELECT
|
||||
COUNT(*) * 1000000000.0 / (MAX(ts) - MIN(ts)) AS fps
|
||||
FROM slice
|
||||
WHERE name = 'Choreographer#doFrame';
|
||||
```
|
||||
|
||||
**查询CPU使用率:**
|
||||
```sql
|
||||
-- 查询CPU使用率
|
||||
SELECT
|
||||
cpu,
|
||||
COUNT(*) * 100.0 / (SELECT COUNT(*) FROM sched WHERE cpu = s.cpu) AS usage_percent
|
||||
FROM sched s
|
||||
GROUP BY cpu;
|
||||
```
|
||||
|
||||
**查询内存使用:**
|
||||
```sql
|
||||
-- 查询内存使用情况
|
||||
SELECT
|
||||
name,
|
||||
value / 1024.0 / 1024.0 AS size_mb
|
||||
FROM counter
|
||||
WHERE name LIKE '%mem%'
|
||||
ORDER BY ts DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
#### 2.2 复杂查询
|
||||
|
||||
**查询启动时间分布:**
|
||||
```sql
|
||||
-- 查询不同阶段的启动时间
|
||||
SELECT
|
||||
'Process Creation' AS stage,
|
||||
(SELECT ts FROM slice WHERE name LIKE '%startProcess%' LIMIT 1) AS start_ts,
|
||||
(SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1) AS end_ts,
|
||||
((SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1) -
|
||||
(SELECT ts FROM slice WHERE name LIKE '%startProcess%' LIMIT 1)) / 1000000.0 AS duration_ms
|
||||
UNION ALL
|
||||
SELECT
|
||||
'Application Init' AS stage,
|
||||
(SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1) AS start_ts,
|
||||
(SELECT ts FROM slice WHERE name LIKE '%performLaunchActivity%' LIMIT 1) AS end_ts,
|
||||
((SELECT ts FROM slice WHERE name LIKE '%performLaunchActivity%' LIMIT 1) -
|
||||
(SELECT ts FROM slice WHERE name LIKE '%handleBindApplication%' LIMIT 1)) / 1000000.0 AS duration_ms;
|
||||
```
|
||||
|
||||
### 3. 对比分析技巧
|
||||
|
||||
#### 3.1 多Trace对比
|
||||
|
||||
**方法1:时间对齐**
|
||||
```
|
||||
1. 打开多个Trace文件
|
||||
2. 找到相同的起始事件
|
||||
3. 对齐时间线
|
||||
4. 对比关键指标
|
||||
```
|
||||
|
||||
**方法2:指标对比**
|
||||
```
|
||||
1. 分别计算各Trace的关键指标
|
||||
2. 制作对比表格
|
||||
3. 分析差异原因
|
||||
```
|
||||
|
||||
#### 3.2 版本对比
|
||||
|
||||
**优化前后对比:**
|
||||
```
|
||||
1. 录制优化前的Trace(基准版本)
|
||||
2. 录制优化后的Trace(优化版本)
|
||||
3. 对比关键指标:
|
||||
- 启动时间
|
||||
- 帧率
|
||||
- CPU使用率
|
||||
- 内存使用
|
||||
4. 验证优化效果
|
||||
```
|
||||
|
||||
### 4. 性能监控技巧
|
||||
|
||||
#### 4.1 长时间监控
|
||||
|
||||
**录制长时间Trace:**
|
||||
```bash
|
||||
# 录制30秒的Trace
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 30s
|
||||
|
||||
# 录制5分钟的Trace
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 5m
|
||||
```
|
||||
|
||||
**分析长时间Trace:**
|
||||
```
|
||||
1. 使用时间轴缩放功能
|
||||
2. 查找性能问题的时间点
|
||||
3. 分析性能趋势
|
||||
```
|
||||
|
||||
#### 4.2 自动化分析
|
||||
|
||||
**导出数据:**
|
||||
```
|
||||
1. 使用SQL查询导出数据
|
||||
2. 使用脚本自动化分析
|
||||
3. 生成性能报告
|
||||
```
|
||||
|
||||
## 五、常见问题排查
|
||||
|
||||
### 1. Trace文件过大
|
||||
|
||||
**问题:** Trace文件太大,打开缓慢
|
||||
|
||||
**解决方案:**
|
||||
1. 减少追踪时间
|
||||
2. 减少追踪类别
|
||||
3. 使用更小的缓冲区
|
||||
4. 分段录制和分析
|
||||
|
||||
### 2. 找不到关键事件
|
||||
|
||||
**问题:** 在Trace中找不到关键事件
|
||||
|
||||
**解决方案:**
|
||||
1. 确认追踪类别包含相关事件
|
||||
2. 检查应用是否添加了Trace标记
|
||||
3. 使用搜索功能查找
|
||||
4. 查看相关进程和线程
|
||||
|
||||
### 3. 性能数据不准确
|
||||
|
||||
**问题:** Trace中的性能数据与实际不符
|
||||
|
||||
**解决方案:**
|
||||
1. 确认追踪配置正确
|
||||
2. 检查是否有其他进程干扰
|
||||
3. 多次录制取平均值
|
||||
4. 对比其他性能工具
|
||||
|
||||
### 4. 分析效率低
|
||||
|
||||
**问题:** 分析Trace耗时过长
|
||||
|
||||
**解决方案:**
|
||||
1. 使用SQL查询快速定位问题
|
||||
2. 使用书签标记关键位置
|
||||
3. 使用筛选功能缩小范围
|
||||
4. 建立分析模板和流程
|
||||
|
||||
## 六、最佳实践
|
||||
|
||||
### 1. 录制Trace
|
||||
|
||||
1. **明确目标**:在录制前明确要分析的问题
|
||||
2. **合适时长**:选择适当的追踪时间(启动5-10秒,游戏30-60秒)
|
||||
3. **关键类别**:只追踪相关的类别,减少文件大小
|
||||
4. **多次录制**:多次录制确保结果一致
|
||||
5. **环境一致**:保持测试环境一致(温度、电量、后台应用等)
|
||||
|
||||
### 2. 分析Trace
|
||||
|
||||
1. **系统化分析**:按照固定流程分析(启动流程、渲染流程等)
|
||||
2. **关键指标**:关注关键性能指标(启动时间、帧率等)
|
||||
3. **对比分析**:与基准版本或竞品对比
|
||||
4. **深入分析**:找到根本原因,不只是表面现象
|
||||
5. **验证优化**:优化后再次录制验证效果
|
||||
|
||||
### 3. 优化验证
|
||||
|
||||
1. **量化指标**:使用具体数值衡量优化效果
|
||||
2. **多维度验证**:从多个角度验证优化效果
|
||||
3. **回归测试**:确保优化没有引入新问题
|
||||
4. **持续监控**:建立性能监控体系
|
||||
|
||||
---
|
||||
|
||||
*最后更新:2024年*
|
||||
984
docs/学习笔记/perfetto的使用技巧.md
Normal file
984
docs/学习笔记/perfetto的使用技巧.md
Normal file
@@ -0,0 +1,984 @@
|
||||
# Perfetto使用技巧
|
||||
|
||||
## 一、Perfetto简介
|
||||
|
||||
### 1. 什么是Perfetto
|
||||
|
||||
Perfetto是Google开发的下一代系统级性能分析工具,从Android 9(API 28)开始集成到系统中。它是Systrace的升级版,提供了更强大的功能和更好的性能。
|
||||
|
||||
### 2. Perfetto的优势
|
||||
|
||||
- **更长的追踪时间**:支持数小时的长时间追踪
|
||||
- **更丰富的数据源**:支持CPU、内存、网络、电源等多种数据源
|
||||
- **更强大的分析能力**:支持SQL查询、自定义分析
|
||||
- **更好的可视化**:Web UI界面更友好,交互更流畅
|
||||
- **更灵活的配置**:支持配置文件,可以精确控制追踪内容
|
||||
|
||||
### 3. 适用场景
|
||||
|
||||
- 应用启动性能分析
|
||||
- 游戏帧率问题分析
|
||||
- 系统卡顿问题定位
|
||||
- 内存泄漏排查
|
||||
- CPU性能分析
|
||||
- 网络性能分析
|
||||
- 电源管理分析
|
||||
|
||||
## 二、环境准备与安装
|
||||
|
||||
### 1. 系统要求
|
||||
|
||||
- **Android版本**:Android 9(API 28)及以上
|
||||
- **设备要求**:需要root权限或userdebug版本
|
||||
- **开发环境**:ADB工具、Python 3.6+(可选)
|
||||
|
||||
### 2. 检查设备支持
|
||||
|
||||
```bash
|
||||
# 检查Perfetto是否可用
|
||||
adb shell perfetto --help
|
||||
|
||||
# 检查设备Android版本
|
||||
adb shell getprop ro.build.version.release
|
||||
|
||||
# 检查设备是否支持Perfetto
|
||||
adb shell getprop ro.build.version.sdk
|
||||
```
|
||||
|
||||
### 3. 获取Perfetto工具
|
||||
|
||||
#### 3.1 通过Android Studio
|
||||
|
||||
Android Studio内置了Perfetto支持,可以直接使用Profiler功能。
|
||||
|
||||
#### 3.2 通过命令行
|
||||
|
||||
Perfetto已经集成在Android系统中,可以直接通过adb使用。
|
||||
|
||||
#### 3.3 通过Web UI
|
||||
|
||||
访问 https://ui.perfetto.dev/ 可以在线分析Trace文件。
|
||||
|
||||
### 4. 安装Python依赖(可选)
|
||||
|
||||
如果需要使用Python脚本处理Trace文件:
|
||||
|
||||
```bash
|
||||
# 安装protobuf
|
||||
pip install protobuf
|
||||
|
||||
# 安装perfetto Python库
|
||||
pip install perfetto
|
||||
```
|
||||
|
||||
## 三、基础使用
|
||||
|
||||
### 1. 快速开始
|
||||
|
||||
#### 1.1 最简单的录制
|
||||
|
||||
```bash
|
||||
# 录制10秒的Trace
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 10s
|
||||
|
||||
# 拉取Trace文件
|
||||
adb pull /data/misc/perfetto-traces/trace trace.pb
|
||||
|
||||
# 在Web UI中打开
|
||||
# 访问 https://ui.perfetto.dev/ 并上传trace.pb
|
||||
```
|
||||
|
||||
#### 1.2 指定应用录制
|
||||
|
||||
```bash
|
||||
# 录制指定应用的Trace
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \
|
||||
-a com.example.app -t 10s
|
||||
```
|
||||
|
||||
### 2. 命令行参数
|
||||
|
||||
#### 2.1 常用参数
|
||||
|
||||
```bash
|
||||
# -c: 配置文件(- 表示使用默认配置)
|
||||
adb shell perfetto -c -
|
||||
|
||||
# --out: 输出文件路径
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace
|
||||
|
||||
# -t: 录制时长
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 10s
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 5m
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 1h
|
||||
|
||||
# -a: 指定应用包名
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -a com.example.app
|
||||
|
||||
# --txt: 使用文本格式配置文件
|
||||
adb shell perfetto -c /data/local/tmp/config.txt --out /data/local/tmp/trace.pb
|
||||
```
|
||||
|
||||
#### 2.2 高级参数
|
||||
|
||||
```bash
|
||||
# --background: 后台运行
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace --background
|
||||
|
||||
# --detach: 分离模式(录制完成后自动停止)
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace --detach
|
||||
|
||||
# --stop: 停止正在运行的录制
|
||||
adb shell perfetto --stop
|
||||
```
|
||||
|
||||
### 3. 配置文件使用
|
||||
|
||||
#### 3.1 配置文件格式
|
||||
|
||||
Perfetto支持两种配置文件格式:
|
||||
- **文本格式(.txt)**:人类可读,易于编辑
|
||||
- **二进制格式(.pb)**:更紧凑,性能更好
|
||||
|
||||
#### 3.2 基础配置文件
|
||||
|
||||
**基础配置(basic_config.txt):**
|
||||
```protobuf
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
ftrace_events: "sched/sched_switch"
|
||||
ftrace_events: "sched/sched_waking"
|
||||
atrace_categories: "gfx"
|
||||
atrace_categories: "view"
|
||||
buffer_size_kb: 2048
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
duration_ms: 10000
|
||||
```
|
||||
|
||||
#### 3.3 使用配置文件
|
||||
|
||||
```bash
|
||||
# 1. 创建配置文件
|
||||
cat > config.txt << 'EOF'
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
}
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
atrace_categories: "gfx"
|
||||
atrace_categories: "view"
|
||||
}
|
||||
}
|
||||
}
|
||||
duration_ms: 10000
|
||||
EOF
|
||||
|
||||
# 2. 推送到设备
|
||||
adb push config.txt /data/local/tmp/
|
||||
|
||||
# 3. 使用配置文件录制
|
||||
adb shell perfetto -c /data/local/tmp/config.txt --out /data/local/tmp/trace.pb
|
||||
|
||||
# 4. 拉取Trace文件
|
||||
adb pull /data/local/tmp/trace.pb trace.pb
|
||||
```
|
||||
|
||||
## 四、常用配置模板
|
||||
|
||||
### 1. 启动性能分析配置
|
||||
|
||||
**startup_config.txt:**
|
||||
```protobuf
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
# CPU调度事件
|
||||
ftrace_events: "sched/sched_switch"
|
||||
ftrace_events: "sched/sched_waking"
|
||||
ftrace_events: "sched/sched_process_exit"
|
||||
ftrace_events: "sched/sched_process_free"
|
||||
ftrace_events: "task/task_newtask"
|
||||
ftrace_events: "task/task_rename"
|
||||
|
||||
# 电源管理事件
|
||||
ftrace_events: "power/cpu_frequency"
|
||||
ftrace_events: "power/cpu_idle"
|
||||
ftrace_events: "power/suspend_resume"
|
||||
|
||||
# ATrace类别
|
||||
atrace_categories: "am" # Activity Manager
|
||||
atrace_categories: "wm" # Window Manager
|
||||
atrace_categories: "gfx" # Graphics
|
||||
atrace_categories: "view" # View System
|
||||
atrace_categories: "binder_driver" # Binder Driver
|
||||
atrace_categories: "binder_lock" # Binder Lock
|
||||
atrace_categories: "input" # Input
|
||||
atrace_categories: "res" # Resources
|
||||
atrace_categories: "dalvik" # Dalvik VM
|
||||
|
||||
buffer_size_kb: 4096
|
||||
drain_period_ms: 250
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "android.surfaceflinger.frame"
|
||||
}
|
||||
}
|
||||
|
||||
duration_ms: 10000
|
||||
```
|
||||
|
||||
### 2. 游戏帧率分析配置
|
||||
|
||||
**jank_config.txt:**
|
||||
```protobuf
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
# CPU调度
|
||||
ftrace_events: "sched/sched_switch"
|
||||
ftrace_events: "sched/sched_waking"
|
||||
|
||||
# 电源管理
|
||||
ftrace_events: "power/cpu_frequency"
|
||||
ftrace_events: "power/cpu_idle"
|
||||
|
||||
# GPU事件(根据GPU型号选择)
|
||||
ftrace_events: "gfx/mali_gpu_total"
|
||||
ftrace_events: "gpu_mem_total/gpu_mem_total"
|
||||
|
||||
# ATrace类别
|
||||
atrace_categories: "gfx"
|
||||
atrace_categories: "view"
|
||||
atrace_categories: "sched"
|
||||
atrace_categories: "freq"
|
||||
atrace_categories: "idle"
|
||||
|
||||
buffer_size_kb: 8192
|
||||
drain_period_ms: 250
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "android.surfaceflinger.frame"
|
||||
}
|
||||
}
|
||||
|
||||
duration_ms: 30000
|
||||
```
|
||||
|
||||
### 3. 内存分析配置
|
||||
|
||||
**memory_config.txt:**
|
||||
```protobuf
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
ftrace_events: "kmem/kmalloc"
|
||||
ftrace_events: "kmem/kfree"
|
||||
ftrace_events: "kmem/kmem_cache_alloc"
|
||||
ftrace_events: "kmem/kmem_cache_free"
|
||||
atrace_categories: "dalvik"
|
||||
atrace_categories: "gfx"
|
||||
buffer_size_kb: 4096
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "android.java_hprof"
|
||||
target_buffer: 0
|
||||
}
|
||||
}
|
||||
|
||||
duration_ms: 60000
|
||||
```
|
||||
|
||||
### 4. 网络分析配置
|
||||
|
||||
**network_config.txt:**
|
||||
```protobuf
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
ftrace_events: "net/net_dev_xmit"
|
||||
ftrace_events: "net/net_dev_recv"
|
||||
atrace_categories: "network"
|
||||
buffer_size_kb: 2048
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.network_packets"
|
||||
}
|
||||
}
|
||||
|
||||
duration_ms: 30000
|
||||
```
|
||||
|
||||
### 5. 全系统分析配置
|
||||
|
||||
**full_system_config.txt:**
|
||||
```protobuf
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
# 所有常用事件
|
||||
ftrace_events: "sched/sched_switch"
|
||||
ftrace_events: "sched/sched_waking"
|
||||
ftrace_events: "power/cpu_frequency"
|
||||
ftrace_events: "power/cpu_idle"
|
||||
ftrace_events: "power/suspend_resume"
|
||||
|
||||
# 所有ATrace类别
|
||||
atrace_categories: "am"
|
||||
atrace_categories: "wm"
|
||||
atrace_categories: "gfx"
|
||||
atrace_categories: "view"
|
||||
atrace_categories: "input"
|
||||
atrace_categories: "res"
|
||||
atrace_categories: "dalvik"
|
||||
atrace_categories: "binder_driver"
|
||||
atrace_categories: "binder_lock"
|
||||
atrace_categories: "sched"
|
||||
atrace_categories: "freq"
|
||||
atrace_categories: "idle"
|
||||
atrace_categories: "disk"
|
||||
atrace_categories: "load"
|
||||
atrace_categories: "sync"
|
||||
atrace_categories: "workq"
|
||||
atrace_categories: "memreclaim"
|
||||
atrace_categories: "regulators"
|
||||
atrace_categories: "binder_driver"
|
||||
atrace_categories: "binder_lock"
|
||||
|
||||
buffer_size_kb: 8192
|
||||
drain_period_ms: 250
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "android.surfaceflinger.frame"
|
||||
}
|
||||
}
|
||||
|
||||
duration_ms: 60000
|
||||
```
|
||||
|
||||
## 五、ATrace类别详解
|
||||
|
||||
### 1. 常用类别
|
||||
|
||||
| 类别 | 说明 | 使用场景 |
|
||||
|------|------|----------|
|
||||
| `gfx` | 图形渲染 | 帧率分析、渲染性能 |
|
||||
| `view` | 视图系统 | UI性能、布局分析 |
|
||||
| `am` | Activity Manager | 应用启动、Activity生命周期 |
|
||||
| `wm` | Window Manager | 窗口管理、多窗口 |
|
||||
| `input` | 输入事件 | 触摸响应、输入延迟 |
|
||||
| `sched` | CPU调度 | CPU性能、任务调度 |
|
||||
| `freq` | CPU频率 | 频率管理、性能调频 |
|
||||
| `idle` | CPU空闲 | 电源管理、CPU使用率 |
|
||||
| `binder_driver` | Binder驱动 | 跨进程通信 |
|
||||
| `binder_lock` | Binder锁 | 锁竞争分析 |
|
||||
| `res` | 资源管理 | 资源加载、资源管理 |
|
||||
| `dalvik` | Dalvik VM | Java代码执行、GC |
|
||||
| `disk` | 磁盘I/O | 文件读写、数据库操作 |
|
||||
| `load` | 系统负载 | 系统负载分析 |
|
||||
| `sync` | 同步操作 | 同步原语、锁 |
|
||||
| `workq` | 工作队列 | 后台任务 |
|
||||
| `memreclaim` | 内存回收 | 内存管理、GC |
|
||||
| `network` | 网络 | 网络性能分析 |
|
||||
|
||||
### 2. 类别组合建议
|
||||
|
||||
**启动分析:**
|
||||
```
|
||||
am wm gfx view binder_driver binder_lock input res dalvik
|
||||
```
|
||||
|
||||
**帧率分析:**
|
||||
```
|
||||
gfx view sched freq idle
|
||||
```
|
||||
|
||||
**内存分析:**
|
||||
```
|
||||
dalvik gfx memreclaim
|
||||
```
|
||||
|
||||
**网络分析:**
|
||||
```
|
||||
network disk
|
||||
```
|
||||
|
||||
**全系统分析:**
|
||||
```
|
||||
am wm gfx view input res dalvik binder_driver binder_lock sched freq idle disk load sync workq memreclaim
|
||||
```
|
||||
|
||||
## 六、高级功能
|
||||
|
||||
### 1. 自定义Trace标记
|
||||
|
||||
#### 1.1 Java/Kotlin代码
|
||||
|
||||
```java
|
||||
import android.os.Trace;
|
||||
|
||||
public class MyClass {
|
||||
public void doSomething() {
|
||||
// 开始标记
|
||||
Trace.beginSection("my_custom_section");
|
||||
|
||||
try {
|
||||
// 执行代码
|
||||
performOperation();
|
||||
} finally {
|
||||
// 结束标记(必须在finally中调用)
|
||||
Trace.endSection();
|
||||
}
|
||||
}
|
||||
|
||||
private void performOperation() {
|
||||
Trace.beginSection("perform_operation");
|
||||
// 操作代码
|
||||
Trace.endSection();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 C++代码
|
||||
|
||||
```cpp
|
||||
#include <utils/Trace.h>
|
||||
|
||||
void doSomething() {
|
||||
// 开始标记
|
||||
ATRACE_BEGIN("my_custom_section");
|
||||
|
||||
// 执行代码
|
||||
performOperation();
|
||||
|
||||
// 结束标记
|
||||
ATRACE_END();
|
||||
}
|
||||
|
||||
void performOperation() {
|
||||
ATRACE_BEGIN("perform_operation");
|
||||
// 操作代码
|
||||
ATRACE_END();
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3 异步操作标记
|
||||
|
||||
```java
|
||||
import android.os.Trace;
|
||||
|
||||
public class AsyncTask {
|
||||
public void asyncOperation() {
|
||||
Trace.beginAsyncSection("async_operation", 0);
|
||||
|
||||
// 启动异步操作
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// 异步操作代码
|
||||
performAsyncWork();
|
||||
} finally {
|
||||
// 结束异步标记
|
||||
Trace.endAsyncSection("async_operation", 0);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 长时间追踪
|
||||
|
||||
#### 2.1 录制长时间Trace
|
||||
|
||||
```bash
|
||||
# 录制5分钟
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 5m
|
||||
|
||||
# 录制1小时
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 1h
|
||||
|
||||
# 使用配置文件指定时长
|
||||
# 在配置文件中设置 duration_ms: 3600000 # 1小时
|
||||
```
|
||||
|
||||
#### 2.2 后台录制
|
||||
|
||||
```bash
|
||||
# 后台录制
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \
|
||||
--background -t 10m
|
||||
|
||||
# 检查录制状态
|
||||
adb shell ps | grep perfetto
|
||||
|
||||
# 停止录制
|
||||
adb shell perfetto --stop
|
||||
```
|
||||
|
||||
### 3. 多进程追踪
|
||||
|
||||
#### 3.1 指定多个应用
|
||||
|
||||
```bash
|
||||
# 追踪多个应用
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \
|
||||
-a com.example.app1 -a com.example.app2 -t 10s
|
||||
```
|
||||
|
||||
#### 3.2 追踪系统进程
|
||||
|
||||
```bash
|
||||
# 追踪系统进程(需要root)
|
||||
adb root
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \
|
||||
-t 10s
|
||||
```
|
||||
|
||||
### 4. 条件追踪
|
||||
|
||||
#### 4.1 基于触发器的追踪
|
||||
|
||||
```protobuf
|
||||
# 在配置文件中添加触发器
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
atrace_categories: "gfx"
|
||||
# 触发器:当特定事件发生时开始追踪
|
||||
trigger_config {
|
||||
trigger_mode: START_TRACING
|
||||
trigger_name: "my_trigger"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 七、Trace文件分析
|
||||
|
||||
### 1. Web UI使用技巧
|
||||
|
||||
#### 1.1 基本操作
|
||||
|
||||
- **缩放**:鼠标滚轮或Ctrl+滚轮
|
||||
- **平移**:鼠标拖拽或方向键
|
||||
- **选择**:鼠标点击选择事件
|
||||
- **搜索**:Ctrl+F 或点击搜索框
|
||||
- **书签**:按M键标记当前位置
|
||||
- **时间选择**:Shift+鼠标拖拽选择时间范围
|
||||
|
||||
#### 1.2 视图操作
|
||||
|
||||
- **展开/折叠**:点击进程/线程名称
|
||||
- **高亮**:双击事件高亮显示
|
||||
- **筛选**:使用筛选器过滤事件
|
||||
- **排序**:点击列标题排序
|
||||
|
||||
#### 1.3 快捷键
|
||||
|
||||
- `W` / `S`:放大/缩小
|
||||
- `A` / `D`:左移/右移
|
||||
- `M`:添加书签
|
||||
- `G`:跳转到书签
|
||||
- `F`:查找
|
||||
- `?`:显示所有快捷键
|
||||
|
||||
### 2. SQL查询
|
||||
|
||||
#### 2.1 基础查询
|
||||
|
||||
**查询所有帧信息:**
|
||||
```sql
|
||||
SELECT * FROM slice WHERE name = 'Choreographer#doFrame';
|
||||
```
|
||||
|
||||
**查询掉帧:**
|
||||
```sql
|
||||
SELECT
|
||||
ts,
|
||||
dur / 1000000.0 AS duration_ms
|
||||
FROM slice
|
||||
WHERE name = 'Choreographer#doFrame'
|
||||
AND dur > 16666667
|
||||
ORDER BY ts;
|
||||
```
|
||||
|
||||
**查询CPU使用率:**
|
||||
```sql
|
||||
SELECT
|
||||
cpu,
|
||||
COUNT(*) * 100.0 / (SELECT COUNT(*) FROM sched WHERE cpu = s.cpu) AS usage_percent
|
||||
FROM sched s
|
||||
GROUP BY cpu;
|
||||
```
|
||||
|
||||
#### 2.2 高级查询
|
||||
|
||||
**查询启动时间:**
|
||||
```sql
|
||||
SELECT
|
||||
(SELECT ts FROM slice WHERE name LIKE '%Choreographer#doFrame%' LIMIT 1) -
|
||||
(SELECT ts FROM slice WHERE name LIKE '%startProcess%' LIMIT 1)
|
||||
AS startup_time_ns;
|
||||
```
|
||||
|
||||
**查询主线程阻塞时间:**
|
||||
```sql
|
||||
SELECT
|
||||
SUM(dur) / 1000000.0 AS total_sleep_ms
|
||||
FROM sched
|
||||
WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%main%' LIMIT 1)
|
||||
AND state = 'S';
|
||||
```
|
||||
|
||||
**查询GC频率:**
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) AS gc_count,
|
||||
(MAX(ts) - MIN(ts)) / 1000000000.0 AS duration_sec,
|
||||
COUNT(*) * 1.0 / ((MAX(ts) - MIN(ts)) / 1000000000.0) AS gc_per_sec
|
||||
FROM slice
|
||||
WHERE name LIKE '%GC%';
|
||||
```
|
||||
|
||||
### 3. 性能指标分析
|
||||
|
||||
#### 3.1 帧率分析
|
||||
|
||||
**查看帧率:**
|
||||
```
|
||||
1. 在顶部选择 "Frame Timeline"
|
||||
2. 查看每一帧的状态
|
||||
3. 统计正常帧、掉帧数量
|
||||
4. 计算平均帧率
|
||||
```
|
||||
|
||||
**使用SQL计算帧率:**
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) * 1000000000.0 / (MAX(ts) - MIN(ts)) AS fps
|
||||
FROM slice
|
||||
WHERE name = 'Choreographer#doFrame';
|
||||
```
|
||||
|
||||
#### 3.2 CPU性能分析
|
||||
|
||||
**查看CPU频率:**
|
||||
```
|
||||
1. 在顶部选择 "CPU frequency"
|
||||
2. 查看各核心频率变化
|
||||
3. 检查是否有降频
|
||||
```
|
||||
|
||||
**查看CPU使用率:**
|
||||
```
|
||||
1. 在顶部选择 "CPU usage"
|
||||
2. 查看各核心使用情况
|
||||
3. 检查是否有瓶颈
|
||||
```
|
||||
|
||||
#### 3.3 内存分析
|
||||
|
||||
**查看内存使用:**
|
||||
```
|
||||
1. 在顶部选择 "Memory"
|
||||
2. 查看内存使用趋势
|
||||
3. 检查是否有内存泄漏
|
||||
```
|
||||
|
||||
**查看GC活动:**
|
||||
```
|
||||
1. 在主线程中查找 "HeapTaskDaemon"
|
||||
2. 查看GC频率和耗时
|
||||
3. 分析GC对性能的影响
|
||||
```
|
||||
|
||||
## 八、最佳实践
|
||||
|
||||
### 1. 录制Trace
|
||||
|
||||
1. **明确目标**:在录制前明确要分析的问题
|
||||
2. **合适时长**:选择适当的追踪时间
|
||||
- 启动分析:5-10秒
|
||||
- 帧率分析:30-60秒
|
||||
- 内存分析:1-5分钟
|
||||
- 长时间监控:根据需要
|
||||
3. **关键类别**:只追踪相关的类别,减少文件大小
|
||||
4. **多次录制**:多次录制确保结果一致
|
||||
5. **环境一致**:保持测试环境一致
|
||||
- 温度
|
||||
- 电量
|
||||
- 后台应用
|
||||
- 网络状态
|
||||
|
||||
### 2. 分析Trace
|
||||
|
||||
1. **系统化分析**:按照固定流程分析
|
||||
2. **关键指标**:关注关键性能指标
|
||||
3. **对比分析**:与基准版本或竞品对比
|
||||
4. **深入分析**:找到根本原因
|
||||
5. **验证优化**:优化后再次录制验证
|
||||
|
||||
### 3. 性能优化
|
||||
|
||||
1. **量化指标**:使用具体数值衡量优化效果
|
||||
2. **多维度验证**:从多个角度验证优化效果
|
||||
3. **回归测试**:确保优化没有引入新问题
|
||||
4. **持续监控**:建立性能监控体系
|
||||
|
||||
## 九、常见问题与解决方案
|
||||
|
||||
### 1. 录制问题
|
||||
|
||||
**问题1:权限不足**
|
||||
```
|
||||
错误:perfetto: permission denied
|
||||
解决:需要root权限或userdebug版本
|
||||
```
|
||||
|
||||
**问题2:设备不支持**
|
||||
```
|
||||
错误:perfetto: command not found
|
||||
解决:检查Android版本(需要Android 9+)
|
||||
```
|
||||
|
||||
**问题3:配置文件格式错误**
|
||||
```
|
||||
错误:Failed to parse config
|
||||
解决:检查配置文件格式,使用文本格式更易调试
|
||||
```
|
||||
|
||||
### 2. 分析问题
|
||||
|
||||
**问题1:Trace文件过大**
|
||||
```
|
||||
解决:
|
||||
1. 减少追踪时间
|
||||
2. 减少追踪类别
|
||||
3. 使用更小的缓冲区
|
||||
4. 分段录制和分析
|
||||
```
|
||||
|
||||
**问题2:找不到关键事件**
|
||||
```
|
||||
解决:
|
||||
1. 确认追踪类别包含相关事件
|
||||
2. 检查应用是否添加了Trace标记
|
||||
3. 使用搜索功能查找
|
||||
4. 查看相关进程和线程
|
||||
```
|
||||
|
||||
**问题3:性能数据不准确**
|
||||
```
|
||||
解决:
|
||||
1. 确认追踪配置正确
|
||||
2. 检查是否有其他进程干扰
|
||||
3. 多次录制取平均值
|
||||
4. 对比其他性能工具
|
||||
```
|
||||
|
||||
### 3. 性能问题
|
||||
|
||||
**问题1:录制影响性能**
|
||||
```
|
||||
解决:
|
||||
1. 减少追踪类别
|
||||
2. 使用较小的缓冲区
|
||||
3. 减少追踪时间
|
||||
4. 在性能测试时关闭不必要的追踪
|
||||
```
|
||||
|
||||
**问题2:Web UI加载慢**
|
||||
```
|
||||
解决:
|
||||
1. 减少Trace文件大小
|
||||
2. 使用本地Perfetto工具
|
||||
3. 分段分析Trace文件
|
||||
```
|
||||
|
||||
## 十、实用脚本
|
||||
|
||||
### 1. 快速录制脚本
|
||||
|
||||
**quick_trace.sh:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# 快速录制Trace脚本
|
||||
DURATION=${1:-10}
|
||||
OUTPUT=${2:-trace.pb}
|
||||
|
||||
echo "开始录制 ${DURATION} 秒的Trace..."
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t ${DURATION}s
|
||||
|
||||
echo "拉取Trace文件..."
|
||||
adb pull /data/misc/perfetto-traces/trace ${OUTPUT}
|
||||
|
||||
echo "Trace文件已保存为: ${OUTPUT}"
|
||||
echo "可以在 https://ui.perfetto.dev/ 打开分析"
|
||||
```
|
||||
|
||||
**使用方法:**
|
||||
```bash
|
||||
chmod +x quick_trace.sh
|
||||
./quick_trace.sh 10 trace.pb
|
||||
```
|
||||
|
||||
### 2. 应用启动分析脚本
|
||||
|
||||
**startup_trace.sh:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
PACKAGE=${1:-com.example.app}
|
||||
OUTPUT=${2:-startup_trace.pb}
|
||||
|
||||
echo "开始录制 ${PACKAGE} 的启动Trace..."
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \
|
||||
-a ${PACKAGE} -t 10s
|
||||
|
||||
echo "拉取Trace文件..."
|
||||
adb pull /data/misc/perfetto-traces/trace ${OUTPUT}
|
||||
|
||||
echo "启动Trace已保存为: ${OUTPUT}"
|
||||
```
|
||||
|
||||
**使用方法:**
|
||||
```bash
|
||||
chmod +x startup_trace.sh
|
||||
./startup_trace.sh com.example.app startup.pb
|
||||
```
|
||||
|
||||
### 3. 批量分析脚本
|
||||
|
||||
**batch_analysis.sh:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# 批量录制和分析Trace
|
||||
for i in {1..5}; do
|
||||
echo "录制第 ${i} 次Trace..."
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace_${i} -t 10s
|
||||
adb pull /data/misc/perfetto-traces/trace_${i} trace_${i}.pb
|
||||
echo "第 ${i} 次Trace已保存"
|
||||
done
|
||||
|
||||
echo "所有Trace录制完成"
|
||||
```
|
||||
|
||||
## 十一、进阶技巧
|
||||
|
||||
### 1. 性能基准建立
|
||||
|
||||
**建立性能基准:**
|
||||
```
|
||||
1. 在稳定版本上录制Trace
|
||||
2. 记录关键性能指标
|
||||
3. 建立性能基准数据库
|
||||
4. 定期更新基准
|
||||
```
|
||||
|
||||
### 2. 自动化性能测试
|
||||
|
||||
**集成到CI/CD:**
|
||||
```
|
||||
1. 在自动化测试中录制Trace
|
||||
2. 自动分析关键指标
|
||||
3. 设置性能阈值
|
||||
4. 性能回归自动告警
|
||||
```
|
||||
|
||||
### 3. 性能报告生成
|
||||
|
||||
**生成性能报告:**
|
||||
```
|
||||
1. 使用SQL查询提取数据
|
||||
2. 使用脚本处理数据
|
||||
3. 生成可视化报告
|
||||
4. 定期发送性能报告
|
||||
```
|
||||
|
||||
## 十二、参考资料
|
||||
|
||||
### 1. 官方文档
|
||||
|
||||
- Perfetto官方文档:https://perfetto.dev/
|
||||
- Perfetto Web UI:https://ui.perfetto.dev/
|
||||
- Android性能优化指南:https://developer.android.com/topic/performance
|
||||
|
||||
### 2. 相关工具
|
||||
|
||||
- Systrace:Android系统追踪工具
|
||||
- Android Studio Profiler:IDE集成性能分析工具
|
||||
- Traceview:Java代码性能分析工具
|
||||
|
||||
### 3. 学习资源
|
||||
|
||||
- Perfetto GitHub:https://github.com/google/perfetto
|
||||
- Android性能优化最佳实践
|
||||
- 系统性能分析案例
|
||||
|
||||
---
|
||||
|
||||
*最后更新:2024年*
|
||||
846
docs/学习笔记/看应用卡顿问题.md
Normal file
846
docs/学习笔记/看应用卡顿问题.md
Normal file
@@ -0,0 +1,846 @@
|
||||
# Perfetto看应用卡顿问题
|
||||
|
||||
## 一、卡顿问题概述
|
||||
|
||||
### 1. 什么是卡顿
|
||||
|
||||
卡顿是指应用在运行过程中出现画面不流畅、响应延迟、操作不跟手等现象。从技术角度来说,卡顿通常表现为:
|
||||
|
||||
- **丢帧**:60Hz设备上,1秒内显示的帧数少于60帧
|
||||
- **帧延迟**:单帧渲染时间超过16.67ms(60Hz)或8.33ms(120Hz)
|
||||
- **响应延迟**:用户操作到界面响应的时间过长
|
||||
|
||||
### 2. 卡顿的分类
|
||||
|
||||
#### 2.1 按场景分类
|
||||
|
||||
- **滑动卡顿**:列表滑动、页面滚动时出现卡顿
|
||||
- **动画卡顿**:页面跳转、转场动画、UI动画不流畅
|
||||
- **点击卡顿**:点击操作后响应延迟
|
||||
- **启动卡顿**:应用启动过程缓慢
|
||||
- **交互卡顿**:界面交互操作不流畅
|
||||
|
||||
#### 2.2 按原因分类
|
||||
|
||||
- **主线程阻塞**:主线程执行耗时操作
|
||||
- **渲染瓶颈**:RenderThread或GPU性能不足
|
||||
- **系统资源竞争**:CPU、内存、I/O资源不足
|
||||
- **锁竞争**:多线程竞争导致阻塞
|
||||
- **内存压力**:GC频繁、内存不足
|
||||
|
||||
### 3. 卡顿分析的目标
|
||||
|
||||
- **定位卡顿时间点**:确定卡顿发生的具体时间
|
||||
- **识别卡顿原因**:找到导致卡顿的根本原因
|
||||
- **量化卡顿程度**:测量卡顿的严重程度
|
||||
- **提供优化建议**:给出针对性的优化方案
|
||||
|
||||
## 二、Perfetto录制卡顿场景
|
||||
|
||||
### 1. 录制前的准备
|
||||
|
||||
#### 1.1 明确问题场景
|
||||
|
||||
在录制前需要明确:
|
||||
- **卡顿场景**:什么操作导致卡顿
|
||||
- **复现步骤**:如何复现卡顿问题
|
||||
- **卡顿表现**:卡顿的具体表现(丢帧、延迟等)
|
||||
- **预期时长**:卡顿持续的时间
|
||||
|
||||
#### 1.2 环境准备
|
||||
|
||||
- **设备状态**:确保设备电量充足、温度正常
|
||||
- **后台应用**:关闭不必要的后台应用
|
||||
- **网络状态**:确保网络稳定(如需要)
|
||||
- **应用版本**:使用待分析的应用版本
|
||||
|
||||
### 2. 卡顿分析配置文件
|
||||
|
||||
#### 2.1 基础卡顿分析配置
|
||||
|
||||
**jank_analysis_config.txt:**
|
||||
```protobuf
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
# CPU调度事件
|
||||
ftrace_events: "sched/sched_switch"
|
||||
ftrace_events: "sched/sched_waking"
|
||||
|
||||
# 电源管理事件
|
||||
ftrace_events: "power/cpu_frequency"
|
||||
ftrace_events: "power/cpu_idle"
|
||||
|
||||
# GPU事件(根据GPU型号选择)
|
||||
ftrace_events: "gfx/mali_gpu_total"
|
||||
ftrace_events: "gpu_mem_total/gpu_mem_total"
|
||||
|
||||
# ATrace类别
|
||||
atrace_categories: "gfx" # 图形渲染
|
||||
atrace_categories: "view" # 视图系统
|
||||
atrace_categories: "sched" # CPU调度
|
||||
atrace_categories: "freq" # CPU频率
|
||||
atrace_categories: "idle" # CPU空闲
|
||||
atrace_categories: "input" # 输入事件
|
||||
atrace_categories: "dalvik" # Java执行
|
||||
atrace_categories: "binder_driver" # Binder通信
|
||||
|
||||
buffer_size_kb: 8192
|
||||
drain_period_ms: 250
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "android.surfaceflinger.frame"
|
||||
}
|
||||
}
|
||||
|
||||
duration_ms: 30000
|
||||
```
|
||||
|
||||
#### 2.2 滑动卡顿分析配置
|
||||
|
||||
**scroll_jank_config.txt:**
|
||||
```protobuf
|
||||
buffers: {
|
||||
size_kb: 63488
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
ftrace_events: "sched/sched_switch"
|
||||
ftrace_events: "sched/sched_waking"
|
||||
ftrace_events: "power/cpu_frequency"
|
||||
ftrace_events: "power/cpu_idle"
|
||||
|
||||
atrace_categories: "gfx"
|
||||
atrace_categories: "view"
|
||||
atrace_categories: "input"
|
||||
atrace_categories: "sched"
|
||||
atrace_categories: "freq"
|
||||
atrace_categories: "idle"
|
||||
atrace_categories: "dalvik"
|
||||
|
||||
buffer_size_kb: 8192
|
||||
drain_period_ms: 250
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_sources: {
|
||||
config {
|
||||
name: "android.surfaceflinger.frame"
|
||||
}
|
||||
}
|
||||
|
||||
duration_ms: 60000
|
||||
```
|
||||
|
||||
### 3. 录制命令
|
||||
|
||||
#### 3.1 基础录制
|
||||
|
||||
```bash
|
||||
# 录制30秒的Trace
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace -t 30s
|
||||
|
||||
# 指定应用录制
|
||||
adb shell perfetto -c - --out /data/misc/perfetto-traces/trace \
|
||||
-a com.example.app -t 30s
|
||||
```
|
||||
|
||||
#### 3.2 使用配置文件录制
|
||||
|
||||
```bash
|
||||
# 1. 推送配置文件
|
||||
adb push jank_analysis_config.txt /data/local/tmp/
|
||||
|
||||
# 2. 开始录制(在复现卡顿前)
|
||||
adb shell perfetto -c /data/local/tmp/jank_analysis_config.txt \
|
||||
--out /data/local/tmp/trace.pb
|
||||
|
||||
# 3. 复现卡顿问题
|
||||
# (在设备上执行导致卡顿的操作)
|
||||
|
||||
# 4. 停止录制(Ctrl+C 或等待duration_ms时间)
|
||||
|
||||
# 5. 拉取Trace文件
|
||||
adb pull /data/local/tmp/trace.pb jank_trace.pb
|
||||
```
|
||||
|
||||
#### 3.3 后台录制
|
||||
|
||||
```bash
|
||||
# 后台录制,适合长时间监控
|
||||
adb shell perfetto -c /data/local/tmp/jank_analysis_config.txt \
|
||||
--out /data/local/tmp/trace.pb --background
|
||||
|
||||
# 检查录制状态
|
||||
adb shell ps | grep perfetto
|
||||
|
||||
# 停止录制
|
||||
adb shell perfetto --stop
|
||||
```
|
||||
|
||||
## 三、卡顿分析定位流程
|
||||
|
||||
### 1. 分析流程概览
|
||||
|
||||
```
|
||||
1. 打开Trace文件
|
||||
↓
|
||||
2. 定位卡顿时间点
|
||||
↓
|
||||
3. 查看帧率信息
|
||||
↓
|
||||
4. 分析主线程状态
|
||||
↓
|
||||
5. 分析渲染线程状态
|
||||
↓
|
||||
6. 分析系统资源
|
||||
↓
|
||||
7. 定位根本原因
|
||||
↓
|
||||
8. 提供优化建议
|
||||
```
|
||||
|
||||
### 2. 步骤1:打开Trace文件
|
||||
|
||||
#### 2.1 使用Web UI打开
|
||||
|
||||
1. 访问 https://ui.perfetto.dev/
|
||||
2. 点击 "Open trace file"
|
||||
3. 选择 trace.pb 文件
|
||||
4. 等待加载完成
|
||||
|
||||
#### 2.2 初始检查
|
||||
|
||||
- **时间范围**:确认Trace覆盖了卡顿时间段
|
||||
- **进程列表**:找到目标应用进程
|
||||
- **线程列表**:确认主线程和RenderThread存在
|
||||
|
||||
### 3. 步骤2:定位卡顿时间点
|
||||
|
||||
#### 3.1 方法1:查看Frame Timeline
|
||||
|
||||
```
|
||||
1. 在顶部选择 "Frame Timeline"
|
||||
2. 查看帧的颜色:
|
||||
- 绿色:正常帧(< 16.67ms)
|
||||
- 黄色:轻微掉帧(16.67-33.33ms)
|
||||
- 红色:严重掉帧(> 33.33ms)
|
||||
3. 找到红色或黄色的帧
|
||||
4. 点击帧,查看详细信息
|
||||
5. 标记卡顿时间点(按M键)
|
||||
```
|
||||
|
||||
#### 3.2 方法2:查看VSync信息
|
||||
|
||||
```
|
||||
1. 在SurfaceFlinger进程中查找 "VSync"
|
||||
2. 查看VSync周期是否正常(60Hz = 16.67ms)
|
||||
3. 检查是否有VSync丢失
|
||||
4. 找到VSync异常的时间点
|
||||
```
|
||||
|
||||
#### 3.3 方法3:使用SQL查询
|
||||
|
||||
```sql
|
||||
-- 查询所有掉帧
|
||||
SELECT
|
||||
ts,
|
||||
dur / 1000000.0 AS duration_ms
|
||||
FROM slice
|
||||
WHERE name = 'Choreographer#doFrame'
|
||||
AND dur > 16666667
|
||||
ORDER BY ts;
|
||||
```
|
||||
|
||||
#### 3.4 方法4:查看用户操作
|
||||
|
||||
```
|
||||
1. 在Input进程中查找输入事件
|
||||
2. 找到用户操作的时间点
|
||||
3. 查看操作后的响应时间
|
||||
4. 识别响应延迟的时间点
|
||||
```
|
||||
|
||||
### 4. 步骤3:查看帧率信息
|
||||
|
||||
#### 4.1 帧率统计
|
||||
|
||||
**使用SQL查询帧率:**
|
||||
```sql
|
||||
-- 计算平均帧率
|
||||
SELECT
|
||||
COUNT(*) * 1000000000.0 / (MAX(ts) - MIN(ts)) AS fps
|
||||
FROM slice
|
||||
WHERE name = 'Choreographer#doFrame';
|
||||
```
|
||||
|
||||
**查看帧率分布:**
|
||||
```sql
|
||||
-- 统计帧耗时分布
|
||||
SELECT
|
||||
CASE
|
||||
WHEN dur < 16666667 THEN '正常'
|
||||
WHEN dur < 33333333 THEN '轻微掉帧'
|
||||
ELSE '严重掉帧'
|
||||
END AS frame_type,
|
||||
COUNT(*) AS count
|
||||
FROM slice
|
||||
WHERE name = 'Choreographer#doFrame'
|
||||
GROUP BY frame_type;
|
||||
```
|
||||
|
||||
#### 4.2 连续掉帧分析
|
||||
|
||||
```sql
|
||||
-- 查询连续掉帧的情况
|
||||
SELECT
|
||||
ts,
|
||||
dur / 1000000.0 AS duration_ms,
|
||||
LAG(ts) OVER (ORDER BY ts) AS prev_ts,
|
||||
ts - LAG(ts) OVER (ORDER BY ts) AS gap_ns
|
||||
FROM slice
|
||||
WHERE name = 'Choreographer#doFrame'
|
||||
AND dur > 16666667
|
||||
ORDER BY ts;
|
||||
```
|
||||
|
||||
### 5. 步骤4:分析主线程状态
|
||||
|
||||
#### 5.1 查看主线程时间线
|
||||
|
||||
```
|
||||
1. 在进程列表中找到目标应用
|
||||
2. 展开进程,找到主线程(通常是包名或main)
|
||||
3. 点击主线程,查看时间线
|
||||
4. 查看线程状态:
|
||||
- Running(绿色):正在执行
|
||||
- Runnable(黄色):等待CPU
|
||||
- Sleep(红色):等待资源
|
||||
- UninterruptibleSleep(紫色):不可中断等待
|
||||
```
|
||||
|
||||
#### 5.2 分析主线程阻塞
|
||||
|
||||
**查找Sleep状态:**
|
||||
```
|
||||
1. 在主线程时间线中查找红色(Sleep)片段
|
||||
2. 查看Sleep的持续时间
|
||||
3. 分析Sleep的原因:
|
||||
- 等待Binder调用返回
|
||||
- 等待锁释放
|
||||
- 等待IO操作完成
|
||||
- 等待子线程返回
|
||||
```
|
||||
|
||||
**使用SQL查询主线程阻塞:**
|
||||
```sql
|
||||
-- 查询主线程Sleep时间
|
||||
SELECT
|
||||
SUM(dur) / 1000000.0 AS total_sleep_ms,
|
||||
COUNT(*) AS sleep_count
|
||||
FROM sched
|
||||
WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%main%' LIMIT 1)
|
||||
AND state = 'S';
|
||||
```
|
||||
|
||||
#### 5.3 分析主线程耗时操作
|
||||
|
||||
**查找耗时操作:**
|
||||
```
|
||||
1. 在主线程时间线中按耗时排序
|
||||
2. 查看耗时最长的操作
|
||||
3. 分析操作是否可以优化:
|
||||
- 是否可以移到后台线程
|
||||
- 是否可以异步执行
|
||||
- 是否可以优化算法
|
||||
```
|
||||
|
||||
**使用SQL查询耗时操作:**
|
||||
```sql
|
||||
-- 查询主线程耗时最长的操作
|
||||
SELECT
|
||||
name,
|
||||
dur / 1000000.0 AS duration_ms,
|
||||
ts
|
||||
FROM slice
|
||||
WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%main%' LIMIT 1)
|
||||
ORDER BY dur DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
#### 5.4 常见主线程阻塞场景
|
||||
|
||||
**场景1:等待Binder调用**
|
||||
- **特征**:主线程处于Sleep状态,等待Binder返回
|
||||
- **查找**:查看wakeup信息,找到对应的Binder调用
|
||||
- **分析**:检查SystemServer中的Binder处理耗时
|
||||
- **解决**:优化Binder服务端处理,或使用异步调用
|
||||
|
||||
**场景2:等待锁**
|
||||
- **特征**:主线程处于Sleep状态,等待锁释放
|
||||
- **查找**:查看wakeup信息,找到锁持有者
|
||||
- **分析**:检查锁持有时间是否过长
|
||||
- **解决**:减少锁持有时间,使用细粒度锁
|
||||
|
||||
**场景3:等待IO**
|
||||
- **特征**:主线程处于UninterruptibleSleep-IO状态
|
||||
- **查找**:查看IO操作(文件读写、数据库操作)
|
||||
- **分析**:检查IO操作是否在主线程执行
|
||||
- **解决**:使用异步IO操作
|
||||
|
||||
**场景4:等待网络**
|
||||
- **特征**:主线程等待网络响应
|
||||
- **查找**:查看网络请求相关操作
|
||||
- **分析**:检查网络请求是否同步执行
|
||||
- **解决**:使用异步网络请求
|
||||
|
||||
**场景5:等待子线程**
|
||||
- **特征**:主线程等待子线程返回数据
|
||||
- **查找**:查看wakeup信息,找到依赖的子线程
|
||||
- **分析**:检查子线程执行耗时
|
||||
- **解决**:优化子线程执行,或使用回调机制
|
||||
|
||||
### 6. 步骤5:分析渲染线程状态
|
||||
|
||||
#### 6.1 查看RenderThread时间线
|
||||
|
||||
```
|
||||
1. 在应用进程中找到RenderThread线程
|
||||
2. 查看线程状态和执行情况
|
||||
3. 检查是否有阻塞或延迟
|
||||
```
|
||||
|
||||
#### 6.2 分析渲染耗时
|
||||
|
||||
**关键操作分析:**
|
||||
```
|
||||
1. 在RenderThread中查找关键操作:
|
||||
- drawFrame:绘制帧
|
||||
- dequeueBuffer:获取Buffer
|
||||
- queueBuffer:提交Buffer
|
||||
2. 分析每个操作的耗时
|
||||
3. 识别渲染瓶颈
|
||||
```
|
||||
|
||||
**使用SQL查询渲染耗时:**
|
||||
```sql
|
||||
-- 查询RenderThread耗时操作
|
||||
SELECT
|
||||
name,
|
||||
dur / 1000000.0 AS duration_ms,
|
||||
ts
|
||||
FROM slice
|
||||
WHERE utid = (SELECT utid FROM thread WHERE name LIKE '%RenderThread%' LIMIT 1)
|
||||
ORDER BY dur DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
#### 6.3 常见渲染问题
|
||||
|
||||
**问题1:dequeueBuffer耗时**
|
||||
- **特征**:RenderThread等待Buffer分配
|
||||
- **原因**:SurfaceFlinger繁忙或Buffer不足
|
||||
- **分析**:查看SurfaceFlinger进程状态
|
||||
- **解决**:优化SurfaceFlinger,增加Buffer数量
|
||||
|
||||
**问题2:queueBuffer耗时**
|
||||
- **特征**:RenderThread等待Buffer提交
|
||||
- **原因**:SurfaceFlinger处理延迟
|
||||
- **分析**:查看SurfaceFlinger合成耗时
|
||||
- **解决**:优化SurfaceFlinger合成流程
|
||||
|
||||
**问题3:drawFrame耗时**
|
||||
- **特征**:RenderThread绘制耗时过长
|
||||
- **原因**:绘制内容复杂、GPU性能不足
|
||||
- **分析**:查看GPU使用情况和绘制命令
|
||||
- **解决**:优化绘制内容,使用GPU加速
|
||||
|
||||
### 7. 步骤6:分析系统资源
|
||||
|
||||
#### 7.1 CPU性能分析
|
||||
|
||||
**查看CPU频率:**
|
||||
```
|
||||
1. 在顶部选择 "CPU frequency"
|
||||
2. 查看卡顿期间CPU频率变化
|
||||
3. 检查是否有降频或频率不足
|
||||
```
|
||||
|
||||
**查看CPU使用率:**
|
||||
```
|
||||
1. 在顶部选择 "CPU usage"
|
||||
2. 查看卡顿期间CPU使用情况
|
||||
3. 检查是否有CPU瓶颈
|
||||
```
|
||||
|
||||
**查看CPU调度:**
|
||||
```
|
||||
1. 在CPU核心时间线中查看任务调度
|
||||
2. 检查关键任务是否跑在小核
|
||||
3. 查看调度延迟
|
||||
```
|
||||
|
||||
**使用SQL查询CPU使用率:**
|
||||
```sql
|
||||
-- 查询CPU使用率
|
||||
SELECT
|
||||
cpu,
|
||||
COUNT(*) * 100.0 / (SELECT COUNT(*) FROM sched WHERE cpu = s.cpu) AS usage_percent
|
||||
FROM sched s
|
||||
WHERE ts BETWEEN ? AND ? -- 卡顿时间段
|
||||
GROUP BY cpu;
|
||||
```
|
||||
|
||||
#### 7.2 内存性能分析
|
||||
|
||||
**查看GC活动:**
|
||||
```
|
||||
1. 在主线程中查找 "HeapTaskDaemon"
|
||||
2. 查看GC频率和耗时
|
||||
3. 检查是否在关键帧期间发生GC
|
||||
```
|
||||
|
||||
**使用SQL查询GC频率:**
|
||||
```sql
|
||||
-- 查询GC频率
|
||||
SELECT
|
||||
COUNT(*) AS gc_count,
|
||||
(MAX(ts) - MIN(ts)) / 1000000000.0 AS duration_sec,
|
||||
COUNT(*) * 1.0 / ((MAX(ts) - MIN(ts)) / 1000000000.0) AS gc_per_sec
|
||||
FROM slice
|
||||
WHERE name LIKE '%GC%'
|
||||
AND ts BETWEEN ? AND ?; -- 卡顿时间段
|
||||
```
|
||||
|
||||
**查看内存使用:**
|
||||
```
|
||||
1. 在顶部选择 "Memory"
|
||||
2. 查看卡顿期间内存使用趋势
|
||||
3. 检查是否有内存泄漏
|
||||
```
|
||||
|
||||
#### 7.3 GPU性能分析
|
||||
|
||||
**查看GPU使用率:**
|
||||
```
|
||||
1. 在顶部选择 "GPU frequency" 或 "GPU usage"
|
||||
2. 查看GPU频率和使用率
|
||||
3. 检查是否有GPU瓶颈
|
||||
```
|
||||
|
||||
**查看GPU渲染时间:**
|
||||
```
|
||||
1. 在RenderThread中查找GPU相关操作
|
||||
2. 查看GPU渲染耗时
|
||||
3. 检查是否有GPU过载
|
||||
```
|
||||
|
||||
### 8. 步骤7:定位根本原因
|
||||
|
||||
#### 8.1 综合分析
|
||||
|
||||
根据前面的分析结果,综合判断卡顿的根本原因:
|
||||
|
||||
1. **主线程阻塞**:如果主线程长时间Sleep,分析阻塞原因
|
||||
2. **渲染瓶颈**:如果RenderThread耗时过长,分析渲染瓶颈
|
||||
3. **系统资源不足**:如果CPU/GPU/内存不足,分析资源竞争
|
||||
4. **锁竞争**:如果多线程竞争锁,分析锁竞争情况
|
||||
5. **内存压力**:如果GC频繁,分析内存使用情况
|
||||
|
||||
#### 8.2 问题优先级
|
||||
|
||||
根据卡顿的严重程度和影响范围,确定问题优先级:
|
||||
|
||||
- **P0**:严重卡顿,影响核心功能
|
||||
- **P1**:明显卡顿,影响用户体验
|
||||
- **P2**:轻微卡顿,可接受但需优化
|
||||
- **P3**:偶发卡顿,影响较小
|
||||
|
||||
### 9. 步骤8:提供优化建议
|
||||
|
||||
#### 9.1 主线程优化
|
||||
|
||||
- **异步化**:将耗时操作移到后台线程
|
||||
- **优化算法**:减少主线程计算量
|
||||
- **减少锁竞争**:优化锁的使用
|
||||
- **优化IO**:使用异步IO操作
|
||||
|
||||
#### 9.2 渲染优化
|
||||
|
||||
- **减少绘制内容**:优化视图层级
|
||||
- **使用硬件加速**:启用GPU加速
|
||||
- **优化布局**:减少布局复杂度
|
||||
- **减少过度绘制**:优化绘制区域
|
||||
|
||||
#### 9.3 系统资源优化
|
||||
|
||||
- **CPU优化**:优化任务调度,绑定关键任务到高性能核心
|
||||
- **内存优化**:减少内存分配,优化GC
|
||||
- **GPU优化**:优化GPU使用,减少GPU负载
|
||||
|
||||
## 四、常见卡顿场景分析
|
||||
|
||||
### 1. 滑动卡顿分析
|
||||
|
||||
#### 1.1 问题特征
|
||||
|
||||
- 列表滑动时出现卡顿
|
||||
- 滑动不跟手
|
||||
- 滑动后页面继续滑动(惯性滑动)时卡顿
|
||||
|
||||
#### 1.2 分析步骤
|
||||
|
||||
```
|
||||
1. 定位滑动时间段
|
||||
- 查找Input事件(触摸事件)
|
||||
- 找到滑动开始和结束时间
|
||||
|
||||
2. 查看帧率
|
||||
- 查看滑动期间的帧率
|
||||
- 识别掉帧的时间点
|
||||
|
||||
3. 分析主线程
|
||||
- 查看主线程在滑动期间的状态
|
||||
- 查找耗时操作
|
||||
|
||||
4. 分析渲染线程
|
||||
- 查看RenderThread在滑动期间的状态
|
||||
- 查找渲染瓶颈
|
||||
|
||||
5. 分析系统资源
|
||||
- 查看CPU使用情况
|
||||
- 查看内存和GC情况
|
||||
```
|
||||
|
||||
#### 1.3 常见原因
|
||||
|
||||
- **主线程阻塞**:滑动时主线程执行耗时操作
|
||||
- **布局复杂**:列表项布局过于复杂
|
||||
- **图片加载**:滑动时加载大量图片
|
||||
- **数据绑定**:滑动时频繁绑定数据
|
||||
- **系统资源不足**:CPU或内存不足
|
||||
|
||||
### 2. 动画卡顿分析
|
||||
|
||||
#### 2.1 问题特征
|
||||
|
||||
- 页面跳转动画不流畅
|
||||
- UI动画卡顿
|
||||
- 转场动画掉帧
|
||||
|
||||
#### 2.2 分析步骤
|
||||
|
||||
```
|
||||
1. 定位动画时间段
|
||||
- 查找动画开始和结束时间
|
||||
- 查看动画相关的Trace标记
|
||||
|
||||
2. 查看帧率
|
||||
- 查看动画期间的帧率
|
||||
- 识别掉帧的时间点
|
||||
|
||||
3. 分析动画线程
|
||||
- 查看动画线程状态
|
||||
- 查找动画计算耗时
|
||||
|
||||
4. 分析渲染
|
||||
- 查看渲染线程状态
|
||||
- 查找渲染瓶颈
|
||||
```
|
||||
|
||||
#### 2.3 常见原因
|
||||
|
||||
- **动画计算耗时**:动画计算过于复杂
|
||||
- **渲染瓶颈**:渲染性能不足
|
||||
- **系统资源不足**:CPU或GPU不足
|
||||
- **动画冲突**:多个动画同时执行
|
||||
|
||||
### 3. 点击卡顿分析
|
||||
|
||||
#### 3.1 问题特征
|
||||
|
||||
- 点击后响应延迟
|
||||
- 点击后界面不更新
|
||||
- 点击后操作不执行
|
||||
|
||||
#### 3.2 分析步骤
|
||||
|
||||
```
|
||||
1. 定位点击事件
|
||||
- 查找Input事件(点击事件)
|
||||
- 找到点击时间点
|
||||
|
||||
2. 查看响应时间
|
||||
- 从点击到界面响应的时间
|
||||
- 识别响应延迟
|
||||
|
||||
3. 分析主线程
|
||||
- 查看主线程在点击后的状态
|
||||
- 查找耗时操作
|
||||
|
||||
4. 分析系统服务
|
||||
- 查看SystemServer处理耗时
|
||||
- 查看Binder调用耗时
|
||||
```
|
||||
|
||||
#### 3.3 常见原因
|
||||
|
||||
- **主线程阻塞**:点击后主线程执行耗时操作
|
||||
- **Binder调用延迟**:跨进程调用耗时
|
||||
- **系统服务繁忙**:SystemServer处理延迟
|
||||
- **资源加载**:点击后加载大量资源
|
||||
|
||||
## 五、分析技巧与最佳实践
|
||||
|
||||
### 1. 分析技巧
|
||||
|
||||
#### 1.1 时间对齐
|
||||
|
||||
- 使用书签(M键)标记关键时间点
|
||||
- 使用时间选择(Shift+拖拽)选择时间范围
|
||||
- 使用缩放功能(W/S键)调整时间范围
|
||||
|
||||
#### 1.2 多维度分析
|
||||
|
||||
- 同时查看主线程、RenderThread、系统资源
|
||||
- 对比正常和异常时间段
|
||||
- 使用SQL查询进行统计分析
|
||||
|
||||
#### 1.3 对比分析
|
||||
|
||||
- 对比优化前后的Trace
|
||||
- 对比不同版本的性能
|
||||
- 对比竞品的性能
|
||||
|
||||
### 2. 最佳实践
|
||||
|
||||
#### 2.1 录制Trace
|
||||
|
||||
- 明确问题场景和复现步骤
|
||||
- 选择合适的录制时长
|
||||
- 保持环境一致
|
||||
- 多次录制确保结果一致
|
||||
|
||||
#### 2.2 分析Trace
|
||||
|
||||
- 系统化分析,按照固定流程
|
||||
- 关注关键指标
|
||||
- 深入分析,找到根本原因
|
||||
- 记录分析过程和结果
|
||||
|
||||
#### 2.3 优化验证
|
||||
|
||||
- 优化后再次录制Trace
|
||||
- 对比优化前后的性能
|
||||
- 验证优化效果
|
||||
- 确保没有引入新问题
|
||||
|
||||
### 3. 常见误区
|
||||
|
||||
#### 3.1 只看主线程
|
||||
|
||||
- **误区**:只关注主线程,忽略其他线程
|
||||
- **正确**:综合分析主线程、RenderThread、系统资源
|
||||
|
||||
#### 3.2 只看表面现象
|
||||
|
||||
- **误区**:只看掉帧,不分析原因
|
||||
- **正确**:深入分析,找到根本原因
|
||||
|
||||
#### 3.3 忽略系统因素
|
||||
|
||||
- **误区**:只关注应用代码,忽略系统因素
|
||||
- **正确**:综合考虑应用和系统因素
|
||||
|
||||
## 六、实际案例分析
|
||||
|
||||
### 案例1:列表滑动卡顿
|
||||
|
||||
**问题描述:**
|
||||
- 列表滑动时出现明显卡顿
|
||||
- 滑动不跟手
|
||||
- 掉帧严重
|
||||
|
||||
**分析过程:**
|
||||
1. 录制滑动场景的Trace
|
||||
2. 定位滑动时间段
|
||||
3. 发现主线程在滑动期间执行了大量布局计算
|
||||
4. 发现RenderThread绘制耗时过长
|
||||
5. 发现CPU使用率过高
|
||||
|
||||
**根本原因:**
|
||||
- 列表项布局过于复杂
|
||||
- 滑动时频繁重新计算布局
|
||||
- 绘制内容过多
|
||||
|
||||
**优化方案:**
|
||||
- 优化列表项布局,减少层级
|
||||
- 使用ViewHolder复用视图
|
||||
- 减少绘制内容
|
||||
- 使用硬件加速
|
||||
|
||||
**优化效果:**
|
||||
- 滑动流畅度提升80%
|
||||
- 掉帧减少90%
|
||||
|
||||
### 案例2:页面跳转卡顿
|
||||
|
||||
**问题描述:**
|
||||
- 页面跳转动画不流畅
|
||||
- 跳转后界面响应延迟
|
||||
|
||||
**分析过程:**
|
||||
1. 录制页面跳转的Trace
|
||||
2. 定位跳转时间段
|
||||
3. 发现主线程在跳转时执行了大量初始化操作
|
||||
4. 发现Binder调用耗时过长
|
||||
5. 发现系统服务处理延迟
|
||||
|
||||
**根本原因:**
|
||||
- 页面跳转时同步执行初始化
|
||||
- 跨进程调用过多
|
||||
- 系统服务繁忙
|
||||
|
||||
**优化方案:**
|
||||
- 延迟非关键初始化
|
||||
- 优化跨进程调用
|
||||
- 使用异步加载
|
||||
- 优化系统服务调用
|
||||
|
||||
**优化效果:**
|
||||
- 跳转时间减少60%
|
||||
- 动画流畅度提升
|
||||
|
||||
## 七、总结
|
||||
|
||||
使用Perfetto分析应用卡顿问题的关键步骤:
|
||||
|
||||
1. **明确问题场景**:了解卡顿的具体表现和复现步骤
|
||||
2. **录制Trace**:选择合适的配置和录制方法
|
||||
3. **定位卡顿时间点**:使用多种方法找到卡顿发生的时间
|
||||
4. **分析主线程**:查看主线程状态,找到阻塞原因
|
||||
5. **分析渲染线程**:查看RenderThread状态,找到渲染瓶颈
|
||||
6. **分析系统资源**:查看CPU、内存、GPU使用情况
|
||||
7. **定位根本原因**:综合分析,找到卡顿的根本原因
|
||||
8. **提供优化建议**:给出针对性的优化方案
|
||||
|
||||
通过系统化的分析流程,可以高效地定位和解决应用卡顿问题。
|
||||
|
||||
---
|
||||
|
||||
*最后更新:2024年*
|
||||
@@ -83,6 +83,9 @@ nav:
|
||||
- 学习笔记/基础性能问题分析(ODM).md
|
||||
- 学习笔记/高级性能问题分析.md
|
||||
- 学习笔记/高级性能问题分析目录.md
|
||||
- 学习笔记/perfeto看trace技巧.md
|
||||
- 学习笔记/perfetto的使用技巧.md
|
||||
- 学习笔记/看应用卡顿问题.md
|
||||
- Obsidian笔记:
|
||||
- Obsidian/2026-01-05 个人文档管理.md
|
||||
- Obsidian/高频命令.md
|
||||
|
||||
Reference in New Issue
Block a user