16、性能监控(下):GPU 渲染、网络流量与卡顿定位

上一讲我们聊了 CPU 和内存,今天来补全性能监控的另一半——GPU 渲染和网络流量。这两个指标,说白了就是「画面流不流畅」和「数据跑没跑偏」。我在项目里见过太多应用,CPU 和内存看着都正常,但用户就是觉得卡——那问题多半出在 GPU 渲染上。

16.1 adb shell dumpsys gfxinfo:GPU 渲染分析

这个命令是 Android 系统自带的渲染性能分析工具。它能告诉你每一帧的绘制耗时,精确到毫秒级。我个人习惯用它来做第一轮排查,比 Profiler 轻量得多。

基本用法

# 获取最近 128 帧的渲染数据
adb shell dumpsys gfxinfo <package_name>

# 重置统计信息(建议每次测试前先重置)
adb shell dumpsys gfxinfo <package_name> reset

# 获取更详细的帧数据(Android 7.0+)
adb shell dumpsys gfxinfo <package_name> framestats

输出解读

执行后你会看到类似这样的输出:

Applications Graphics Acceleration Info:
Uptime: 1234567
Frame statistics:
  Total frames rendered: 120
  Janky frames: 15
  90th percentile: 24ms
  95th percentile: 32ms
  99th percentile: 48ms
  Number of frames with missed vsync: 8

关键指标我列一下:

指标 含义 健康值
Janky frames 掉帧数(超过 16ms 的帧) 占比 < 5%
90th percentile 90% 的帧耗时在此值以下 < 16ms
Missed vsync 错过垂直同步的帧数 越少越好

核心原则:Android 要求每帧在 16ms 内完成(60fps)。超过这个时间,用户就会感觉到卡顿。如果 90% 的帧都在 16ms 以内,那体验基本没问题。

framestats 模式详解

如果你用 framestats 参数,会得到更细粒度的数据。每一帧会拆成几个阶段:

Flags,IntendedVsync,Vsync,OldestInputEvent,...
0,123456,123457,0,...
1,123458,123459,0,...

嗯,这里要注意——这个输出是 CSV 格式的,你可以直接重定向到文件,然后用 Excel 或 Python 分析。我一般会关注 DrawPrepareProcess 这几个阶段的耗时。

我的小技巧:写个脚本把 framestats 数据拉下来,画成折线图。一眼就能看出哪些帧「冒尖」了。我曾经帮一个团队排查相册应用卡顿,就是用这个方法发现某张超大图片的 decode 耗时异常。

16.2 adb shell dumpsys netstats:网络流量监控

网络流量监控,很多人觉得不重要。但你想想看——如果某个应用在后台偷偷跑流量,用户会怎么想?而且流量异常往往意味着代码里有 bug,比如循环请求、重试机制写错了。

基本用法

# 查看所有应用的网络统计
adb shell dumpsys netstats

# 查看特定接口的流量(比如 wifi 或 mobile)
adb shell dumpsys netstats interface

# 查看详细的应用级流量(需要 root 或 Android 9+)
adb shell dumpsys netstats uid

输出解读

典型的输出长这样:

Active interfaces:
  iface=wlan0 ident=[{type=WIFI, subType=COMBINED, networkId="..."}]
    UID stats:
      UID 10001:
        Rx bytes: 12345678  Tx bytes: 87654321
        Rx packets: 12345  Tx packets: 67890
      UID 10002:
        Rx bytes: 9876543  Tx bytes: 1234567

这里 Rx bytes 是接收流量,Tx bytes 是发送流量。你可以通过 UID 反查对应的应用包名:

adb shell ps | grep <package_name>
# 或者直接
adb shell dumpsys package <package_name> | grep userId

注意:netstats 的数据是累计值,从设备启动或上次重置开始统计。如果你想测某个操作前后的流量变化,记得先记录基线值。

实战:抓取特定应用的流量

我常用的套路是这样的:

# 1. 先获取应用的 UID
adb shell dumpsys package com.example.app | grep userId

# 2. 查看该 UID 的流量
adb shell dumpsys netstats uid | grep -A 2 "UID 10001"

不过说实话,dumpsys netstats 的粒度比较粗。如果你需要更精细的分析,我建议配合 /proc/net/xt_qtaguid/stats 文件来读——但这个需要 root 权限。

16.3 实战:定位卡顿问题

好了,前面讲了工具,现在咱们来真刀真枪地干一场。假设你接到一个 bug:用户反馈某个页面滑动卡顿。你会怎么查?

我的排查流程是这样的:

  1. 先确认是不是渲染问题——跑 dumpsys gfxinfo,看 Janky frames 占比。
  2. 如果是渲染问题——用 framestats 看哪个阶段耗时最长。
  3. 如果不是渲染问题——检查网络请求是否阻塞了主线程。
  4. 网络层面——用 netstats 看流量是否异常,配合抓包工具确认请求频率。

案例:一个真实的卡顿排查

我记得有一次,一个社交应用的信息流页面滑动卡顿。CPU 和内存都正常,GPU 渲染也看不出大问题。后来我用 dumpsys gfxinfo framestats 仔细看,发现 Process 阶段偶尔会飙到 50ms。

为什么会这样?

进一步分析发现,是列表中的某个自定义 View 在 onDraw 里做了 Bitmap 的缩放操作。这个操作本身不重,但每次滑动都会触发重绘,累积起来就卡了。

解决方案很简单:把 Bitmap 的缩放放到后台线程做,或者用 BitmapFactory.OptionsinSampleSize 预缩放。

经验总结:卡顿问题 80% 出在渲染层面,而渲染问题里又有 60% 是「在 UI 线程做了不该做的事」。记住一条铁律:主线程只做 UI 操作,耗时任务统统扔到子线程。

快速定位脚本

我写了个小脚本,一键拉取关键数据:

#!/bin/bash
# 卡顿排查脚本
PACKAGE=$1

echo "=== 1. GPU 渲染统计 ==="
adb shell dumpsys gfxinfo $PACKAGE | grep -E "Janky|percentile|missed"

echo ""
echo "=== 2. 网络流量统计 ==="
UID=$(adb shell dumpsys package $PACKAGE | grep userId | awk '{print $1}' | cut -d= -f2)
adb shell dumpsys netstats uid | grep -A 2 "UID $UID"

echo ""
echo "=== 3. 当前帧率 ==="
adb shell dumpsys gfxinfo $PACKAGE framestats | tail -5

把这个脚本保存成 check_perf.sh,然后 bash check_perf.sh com.example.app 就能跑。嗯,虽然简陋,但胜在快——我平时排查问题基本就靠它。

16.4 知识体系图

下面这张图总结了本章的核心知识点,方便你对照复习:

性能监控(下)知识体系 性能监控命令 adb shell dumpsys gfxinfo Janky frames / 90th percentile framestats 各阶段耗时 Missed vsync 计数 adb shell dumpsys netstats Rx/Tx bytes 流量统计 UID 反查应用包名 累计 vs 增量数据 实战:卡顿定位四步法

一句话总结:gfxinfo 看渲染,netstats 看流量,两者结合基本能覆盖 90% 的卡顿场景。剩下的 10% 嘛——嗯,那就得上 Systrace 了,那是下一章的内容。


公众号:蓝海资料掘金营,微信deep3321