# 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年*
