Files
mkdocs/docs/学习笔记/看应用卡顿问题.md
2026-02-11 00:40:25 +08:00

20 KiB
Raw Permalink Blame History

Perfetto看应用卡顿问题

一、卡顿问题概述

1. 什么是卡顿

卡顿是指应用在运行过程中出现画面不流畅、响应延迟、操作不跟手等现象。从技术角度来说,卡顿通常表现为:

  • 丢帧60Hz设备上1秒内显示的帧数少于60帧
  • 帧延迟单帧渲染时间超过16.67ms60Hz或8.33ms120Hz
  • 响应延迟:用户操作到界面响应的时间过长

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

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

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 基础录制

# 录制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 使用配置文件录制

# 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 后台录制

# 后台录制,适合长时间监控
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查询

-- 查询所有掉帧
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查询帧率

-- 计算平均帧率
SELECT 
    COUNT(*) * 1000000000.0 / (MAX(ts) - MIN(ts)) AS fps
FROM slice 
WHERE name = 'Choreographer#doFrame';

查看帧率分布:

-- 统计帧耗时分布
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 连续掉帧分析

-- 查询连续掉帧的情况
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查询主线程阻塞

-- 查询主线程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查询耗时操作

-- 查询主线程耗时最长的操作
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查询渲染耗时

-- 查询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 常见渲染问题

问题1dequeueBuffer耗时

  • 特征RenderThread等待Buffer分配
  • 原因SurfaceFlinger繁忙或Buffer不足
  • 分析查看SurfaceFlinger进程状态
  • 解决优化SurfaceFlinger增加Buffer数量

问题2queueBuffer耗时

  • 特征RenderThread等待Buffer提交
  • 原因SurfaceFlinger处理延迟
  • 分析查看SurfaceFlinger合成耗时
  • 解决优化SurfaceFlinger合成流程

问题3drawFrame耗时

  • 特征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使用率

-- 查询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频率

-- 查询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年