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 分析。我一般会关注 Draw、Prepare、Process 这几个阶段的耗时。
我的小技巧:写个脚本把 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:用户反馈某个页面滑动卡顿。你会怎么查?
我的排查流程是这样的:
- 先确认是不是渲染问题——跑
dumpsys gfxinfo,看 Janky frames 占比。 - 如果是渲染问题——用 framestats 看哪个阶段耗时最长。
- 如果不是渲染问题——检查网络请求是否阻塞了主线程。
- 网络层面——用 netstats 看流量是否异常,配合抓包工具确认请求频率。
案例:一个真实的卡顿排查
我记得有一次,一个社交应用的信息流页面滑动卡顿。CPU 和内存都正常,GPU 渲染也看不出大问题。后来我用 dumpsys gfxinfo framestats 仔细看,发现 Process 阶段偶尔会飙到 50ms。
为什么会这样?
进一步分析发现,是列表中的某个自定义 View 在 onDraw 里做了 Bitmap 的缩放操作。这个操作本身不重,但每次滑动都会触发重绘,累积起来就卡了。
解决方案很简单:把 Bitmap 的缩放放到后台线程做,或者用 BitmapFactory.Options 的 inSampleSize 预缩放。
经验总结:卡顿问题 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 知识体系图
下面这张图总结了本章的核心知识点,方便你对照复习:
一句话总结:gfxinfo 看渲染,netstats 看流量,两者结合基本能覆盖 90% 的卡顿场景。剩下的 10% 嘛——嗯,那就得上 Systrace 了,那是下一章的内容。
公众号:蓝海资料掘金营,微信deep3321