2. Android性能分析基础

在进入 Perfetto 的具体操作之前,必须先建立一套“性能语言”。否则抓到的 trace 只是一堆彩色条带,无法判断问题属于哪一类、严重到什么程度、应该由谁优化。本节围绕四个问题展开:看哪些指标、瓶颈通常出现在哪里、传统工具为什么不够用、Perfetto 在整套分析体系中处于什么位置。

一、核心性能指标:从用户感知到系统量化

Android 性能指标可以分为“用户可感知层”和“系统可测量层”。用户感知层描述卡不卡、烫不烫、掉不掉帧;系统可测量层则把这些感受拆成可采集的数值。二者必须建立映射,否则优化容易跑偏。

  • 流畅度:帧率 FPS、帧耗时 Frame Time、掉帧数 Jank、卡顿率。60Hz 屏幕单帧预算约 16.67ms,120Hz 约 8.33ms,超过预算即可能掉帧。
  • 响应速度:冷启动耗时、热启动耗时、界面切换时延、点击到首帧的延迟(TTFD)。
  • CPU:各进程/线程的 CPU 占用率、运行时长、调度延迟、大小核分布、频率与降频情况。
  • 内存:PSS/RSS、Java 堆、Native 堆、Graphics 显存、内存泄漏增长趋势、GC 次数与暂停时间。
  • 功耗与温度:电流、电压、功耗曲线、CPU/GPU/屏幕耗电占比、温升与热限频。
  • I/O 与网络:磁盘读写延迟、文件锁等待、网络请求耗时与重传。
性能分析的第一原则:先确定“用户可感知的问题”,再选择能解释该问题的系统指标。例如“滑动列表卡顿”应优先看帧耗时与主线程阻塞,而不是先看内存总量。

二、常见瓶颈:问题通常藏在哪一层

Android 是分层系统,瓶颈可能出现在应用层、框架层、Native 层、内核层甚至硬件层。经验上,绝大多数卡顿与启动慢可以归入以下几类:

  1. 主线程阻塞:在主线程做磁盘 I/O、网络、大循环、复杂布局解析或同步锁等待,直接导致帧超时。
  2. 过度绘制与布局层级过深:GPU 填充压力大、measure/layout 次数多,表现为渲染耗时高。
  3. 频繁 GC 与内存抖动:短时间内大量对象分配触发 GC,造成主线程暂停。
  4. 锁竞争与线程调度:多线程争抢同一把锁,或线程被长时间挂起,表现为“CPU 不忙但就是慢”。
  5. Binder 跨进程调用:系统服务响应慢或调用频繁,形成跨进程等待链。
  6. CPU 降频与热限:长时间高负载后频率下降,性能突然变差。
  7. I/O 抖动:低内存回收、日志写入、数据库操作引发磁盘等待。

这些瓶颈往往相互叠加。例如内存抖动引发 GC,GC 暂停主线程,主线程阻塞又导致掉帧。因此分析工具必须能同时呈现 CPU、内存、渲染、Binder、I/O 等多条时间线,并支持跨进程关联。

三、传统工具的局限:为什么需要新的分析方式

在 Perfetto 普及之前,Android 开发者常用 Logcat、Systrace、Traceview、MAT、top、dumpsys 等工具。它们各有价值,但在复杂场景下存在明显短板:

工具 主要能力 典型局限
Logcat 查看应用与系统日志 无时间线、无跨进程关联,难以定位卡顿根因
Systrace / atrace 采集系统与框架关键路径 数据维度有限,内存/功耗/长时间采集能力弱,界面老旧
Traceview / Method Tracing Java 方法级耗时 开销大、易失真,不适合线上或长时间分析
MAT / LeakCanary 内存快照与泄漏检测 偏静态分析,难以与 CPU、渲染时间线对齐
top / dumpsys 瞬时状态与统计 采样粒度粗,缺少因果链与精确时间戳
常见误区:把 Systrace 的“看起来很长的一段”直接当成根因。实际上,长条可能只是等待下游结果,真正的问题在它依赖的线程或进程里。缺少跨进程关联时,很容易误判。

四、Perfetto 的定位:统一采集、统一时间线、统一分析

Perfetto 是 Android 官方推荐的下一代性能分析平台,它并不是单一工具,而是一套从采集、存储到可视化的完整体系。其核心定位可以概括为三点:

  • 统一数据源:通过 trace 协议同时采集 ftrace、atrace、进程统计、内存计数器、GPU、功耗、Binder 等多类数据。
  • 统一时间线:所有事件带统一时钟,支持跨进程、跨线程、跨子系统对齐,形成完整因果链。
  • 统一分析入口:既可用 Web 端 Perfetto UI 交互分析,也可用 trace_processor 与 SQL 做批量、自动化分析。

下面这张图展示了从问题现象到 Perfetto 分析定位的整体关系:左侧是用户可感知问题,中间是系统指标与瓶颈层,右侧是 Perfetto 采集到的多源时间线,最终汇聚到根因判断。

从用户感知到 Perfetto 根因定位 用户可感知问题 滑动卡顿 / 掉帧 启动慢 / 白屏久 点击无响应 发热 / 耗电快 内存增长 / 被杀 现象层 系统指标与瓶颈层 流畅度 帧耗时 / Jank / FPS CPU 与调度 主线程阻塞 / 锁竞争 内存 GC / 抖动 / 泄漏 渲染与 GPU 过度绘制 / 布局层级 I/O 与 Binder 磁盘等待 / 跨进程调用 Perfetto 多源时间线 ftrace / atrace 系统调用与框架事件 进程 / 线程 CPU 调度与频率 内存计数器与 GC 事件 GPU 渲染与 SurfaceFlinger 合成 Binder 调用与跨进程等待链 功耗 / 温度 / 网络统计 统一时钟对齐 → 因果链定位根因 Perfetto UI / trace_processor SQL 传统工具各自为战、时间线割裂;Perfetto 把多源数据放进同一条时间轴,才能把“现象”追到“根因”。

五、一个典型场景:滑动卡顿如何用 Perfetto 定位

假设用户反馈“商品列表滑动时偶尔卡一下”。传统做法可能先看 Logcat,发现没有明显异常;再用 Systrace 抓一段,看到主线程有长条,但不知道在等谁。改用 Perfetto 后,流程更清晰:

# 采集包含调度、Binder、内存、渲染的 trace
adb shell perfetto -o /data/misc/perfetto-traces/trace.pftrace -t 10s \
  sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory

# 拉取到本地
adb pull /data/misc/perfetto-traces/trace.pftrace .

# 用 trace_processor 查询主线程阻塞最长的切片
SELECT name, dur, ts
FROM slice
WHERE track_id IN (SELECT id FROM track WHERE name LIKE '%main%')
ORDER BY dur DESC
LIMIT 10;

在 Perfetto UI 中,可以同时看到:主线程某段 RecyclerView 布局耗时过长;同一时间段内 RenderThread 在等待;再往下看,发现是某个图片解码在主线程执行,并且触发了 Binder 调用等待系统服务。这样,根因就从“卡顿”收敛到“主线程图片解码 + Binder 等待”这一具体问题。

分析时建议遵循“先看帧、再看线程、再看依赖”的顺序:先确认哪一帧超预算,再定位该帧对应主线程与渲染线程,最后沿 Binder、锁、I/O 追到真正阻塞点。

六、复盘与常见误区

  • 只看平均值:平均帧率正常不代表没有卡顿,必须看 P95、P99 与最大帧耗时。
  • 把等待当执行:时间线上的长条可能是等待,需结合唤醒链与依赖关系判断。
  • 忽略采集开销:过重的采集配置会改变系统行为,应控制 buffer 与数据源范围。
  • 缺少基线:没有优化前的 trace 对比,很难证明改动有效。
  • 过早下结论:先建立完整时间线,再下判断,避免被单条日志或单个长条误导。

掌握这些基础后,后续学习 Perfetto 的采集配置、UI 操作与 SQL 查询时,就能始终围绕“指标—瓶颈—证据—根因”这条主线展开,而不是停留在工具操作层面。