第29章 性能优化与调试工具:Systrace与Perfetto使用、dumpsys与bugreport分析、StrictMode与ANR监控、Profiling与火焰图

性能优化,说白了就是跟时间赛跑。你写的代码再漂亮,用户一滑动就卡顿,那都是白搭。我在Android Framework这行摸爬滚打这么多年,最深的体会就是:没有数据,别谈优化。今天这一章,我就把压箱底的几个工具和思路掏出来,跟你好好聊聊。

核心观点:性能问题不是靠猜的,是靠「看」的。Systrace看整体,Perfetto看细节,dumpsys看状态,bugreport看全貌,StrictMode抓违规,火焰图找热点。这六板斧抡圆了,没有砍不动的性能问题。

29.1 Systrace与Perfetto:从「看个大概」到「看个明白」

先说说Systrace。这工具老牌了,我刚开始做性能优化时就用它。它的原理很简单——在系统关键路径上打桩,记录每个事件的开始和结束时间,然后画成一张彩色条带图。

你想想看,一条横轴是时间,纵轴是各个进程和线程。每个色块代表一个任务,色块越长,说明耗时越久。卡顿?说白了就是某个色块突然变长了,或者主线程被别的事抢占了。

我个人习惯用命令行抓取Systrace:

# 抓取5秒的trace,重点关注输入、视图、渲染
python systrace.py -t 5 -o mytrace.html sched freq idle am wm gfx view input

嗯,这里要注意:Systrace的开销其实不小。我在项目中遇到过,有些低端机开了Systrace后,卡顿反而消失了——因为trace本身拖慢了系统,改变了调度行为。这叫「观察者效应」,你得心里有数。

后来Google推出了Perfetto,说白了就是Systrace的升级版。它支持更细粒度的跟踪,而且可以记录更长时间的数据。Perfetto的SQL分析引擎特别强大,你可以像查数据库一样查trace数据。

# Perfetto命令行抓取
perfetto -c /data/misc/perfetto-config/trace_config.pbtx -o /data/misc/perfetto-traces/trace.perfetto-trace

# 然后用Perfetto UI打开,或者用SQL分析
SELECT * FROM slices WHERE name LIKE '%Choreographer%'

我的经验:日常调试用Systrace就够了,快速定位。遇到疑难杂症,比如偶现的冻屏、随机掉帧,就用Perfetto长时间录制,然后回放分析。Perfetto的SQL查询能帮你找到那些肉眼看不出来的规律。

29.2 dumpsys与bugreport:系统的「体检报告」

dumpsys这命令,我几乎每天都要用。它能把系统服务的内部状态dump出来,就像给系统做了一次全身CT。

举个例子,你想知道当前内存压力大不大:

adb shell dumpsys meminfo

想看哪个App在疯狂唤醒系统?

adb shell dumpsys alarm

想看窗口层级和焦点?

adb shell dumpsys window

我曾经遇到一个诡异的问题:手机待机一晚上掉电30%。用dumpsys battery_stats一看,发现有个第三方App每5秒唤醒一次系统。再查dumpsys alarm,锁定了一个后台定位服务。嗯,问题就这么找到了。

bugreport就更全面了。它把dumpsys、logcat、kernel日志、ANR trace等所有信息打包成一个zip文件。我建议你养成习惯:每次遇到难复现的bug,第一时间抓bugreport。因为很多现场信息,重启后就没了。

adb bugreport bugreport_$(date +%Y%m%d_%H%M%S).zip

注意:bugreport文件很大,动辄几十MB。别指望用文本编辑器打开看。我一般用Chrome的在线分析工具,或者自己写脚本解析关键字段。重点关注ANR、watchdog、lowmemorykiller这几个section。

29.3 StrictMode与ANR监控:把问题扼杀在摇篮里

StrictMode,这名字起得好——严格模式。它就像你代码里的一个「纪律委员」,专门抓那些不该在主线程做的事。

我个人习惯在Application的onCreate里就开启:

if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
            .detectDiskReads()
            .detectDiskWrites()
            .detectNetwork()
            .penaltyLog()
            .penaltyFlashScreen() // 屏幕闪一下,视觉警告
            .build());
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
            .detectLeakedSqlLiteObjects()
            .detectLeakedClosableObjects()
            .detectActivityLeaks()
            .penaltyLog()
            .build());
}

你想想看,如果每个开发者在调试阶段就能看到「你在主线程读了文件」的警告,那很多ANR根本不会流到线上。

说到ANR,我见过太多团队等到用户投诉了才去查。其实ANR监控可以做得更主动。我在项目中做过一个方案:监听系统的ANR广播,一旦发生ANR,自动抓取当时的CPU、内存、线程堆栈,上传到后台。

// 监听ANR广播(需要系统权限或root)
IntentFilter filter = new IntentFilter("android.intent.action.ANR");
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 抓取堆栈、CPU、内存信息
        // 上传到后台分析
    }
}, filter);

避坑指南:我曾经天真地以为只要主线程不做耗时操作就不会ANR。直到有一次,一个锁竞争导致主线程等了一个后台线程的锁,等了20秒。所以ANR不光是「慢」,还有「死锁」和「活锁」。StrictMode抓不到锁问题,你得靠systrace看线程状态。

29.4 Profiling与火焰图:找到那个「吃CPU的大户」

Profiling,说白了就是给代码做「体检」,看看每个函数花了多少CPU时间。火焰图是展示Profiling结果最直观的方式——横轴是时间占比,纵轴是调用栈。

我常用的Profiling工具有三个:

  • SimplePerf:Android原生,基于perf_event_open,开销小,适合采样CPU热点
  • Java Method Tracing:用Debug.startMethodTracing,适合分析Java层调用
  • Perfetto的Profiling:结合了CPU采样和堆栈信息,最全面

举个例子,用SimplePerf抓取一个App的CPU热点:

# 抓取10秒的CPU采样
simpleperf record -p $(pidof com.example.app) -e cpu-clock -f 1000 --duration 10 -o perf.data

# 生成火焰图
simpleperf report -i perf.data --proto -o report.proto
# 然后用FlameGraph工具生成SVG

火焰图怎么看?看「平顶山」和「烟囱」。平顶山说明某个函数占用了大量CPU时间,烟囱说明调用栈很深,可能有递归或循环嵌套。

我记得有一次,一个App的滑动卡顿问题查了三天没头绪。最后用SimplePerf抓了火焰图,发现一个自定义View的onDraw里调用了Bitmap.createBitmap——每次滑动都创建新Bitmap,GC频繁触发。改成一个复用池后,帧率从20fps直接飙到55fps。

核心原则:不要优化你不知道的东西。先Profiling,再优化。优化完再Profiling,验证效果。这叫「测量-优化-再测量」的闭环。

29.5 知识体系总览

下面这张图,是我自己梳理的性能优化工具知识体系。你可以把它当作一个「导航图」,遇到性能问题,先看属于哪个层面,再选对应的工具。

Android性能优化工具知识体系 性能问题定位 Systrace / Perfetto 整体时间线分析 卡顿、掉帧、调度 dumpsys / bugreport 系统状态快照 内存、Alarm、窗口 StrictMode / ANR 主线程违规检测 磁盘、网络、锁 Profiling / 火焰图 CPU热点采样 函数调用栈分析 测量 → 定位 → 优化 → 验证,循环迭代

29.6 实战建议:从「会用」到「用好」

工具再多,不会用也是白搭。我最后给你几条实战建议:

  1. 先看整体,再看局部。遇到卡顿,先用Systrace看全局,找到哪个阶段耗时异常,再用Profiling深入那个阶段。
  2. 对比是王道。我习惯抓「正常情况」和「异常情况」两份trace,对比差异。很多时候问题就出在「多了一个线程在跑」或者「某个锁的等待时间变长了」。
  3. 自动化监控。别等到用户投诉才查。在CI/CD流程中加入StrictMode检查,在线上加入ANR自动抓取和上报。我见过一个团队,ANR率从3%降到0.1%,靠的就是自动化监控+快速修复。
  4. 火焰图要「读」不要「看」。很多人打开火焰图,看一眼说「哦,这里很宽」,然后就关了。你要读的是:这个宽的函数为什么宽?是算法问题?是频繁GC?还是锁竞争?每个「宽」背后都有一个故事。

最后提醒一句:性能优化是「收益递减」的。把ANR率从5%降到1%,可能只需要修几个明显的bug。但从1%降到0.1%,可能需要重构整个架构。别为了优化而优化,先问自己:这个优化能带来多少用户体验提升?值不值得投入时间?

好了,这一章的内容就到这里。工具是死的,思路是活的。希望你能把这些工具变成自己的「肌肉记忆」,遇到性能问题,下意识就知道该掏出哪个工具。


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