第29章 性能优化工具链进阶:Perfetto深度使用、SimplePerf CPU采样、R8编译优化、Baseline Profiles
性能优化这件事,说白了就是「找到瓶颈,然后干掉它」。但怎么找?靠猜?靠感觉?我见过太多开发者,上来就怀疑某个函数慢,结果折腾半天,发现瓶颈根本不在这。
这一章,我带你走一遍真正的工具链。从系统级追踪到CPU采样,从编译优化到启动加速,每一步都有实战价值。
29.1 Perfetto:系统级追踪的瑞士军刀
Perfetto 是 Google 推出的新一代性能追踪工具。它取代了老的 systrace,功能强了不止一个量级。
我个人习惯,遇到卡顿问题,第一件事就是拉一份 Perfetto 记录。为什么?因为它能看到整个系统的行为——不仅仅是你的 App,还有系统服务、GPU、CPU 调度、内存分配……
29.1.1 基本使用
抓取 Perfetto 记录有两种方式:
- 命令行方式:适合自动化脚本
- Perfetto UI:适合交互式分析
先看命令行:
# 抓取 10 秒的追踪数据
adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace \
-t 10s \
sched freq idle am wm gfx view binder_driver hal dalvik
# 拉取到本地
adb pull /data/misc/perfetto-traces/trace.perfetto-trace .
然后在浏览器打开 ui.perfetto.dev,把文件拖进去。嗯,界面很直观。
29.1.2 关键分析技巧
打开 Perfetto 界面后,你会看到一堆彩色条。别慌,我教你几个关键点:
- 看 CPU 调度:你的线程是不是频繁被调度出去?如果是,说明 CPU 竞争激烈。
- 看 Binder 调用:跨进程调用是不是耗时太长?我曾经发现一个 App 卡顿,就是因为频繁调用系统服务获取传感器数据。
- 看 SurfaceFlinger:掉帧问题,多半和它有关。看看合成时间是不是超过了 16ms。
举个例子,你看到某个线程长时间处于 Runnable 状态但没被调度,那说明 CPU 不够用了。这时候优化方向就是减少计算量,或者把任务移到后台线程。
29.1.3 自定义 Trace 点
Perfetto 最强大的地方,是你可以自己埋点。在代码里加上:
// 在关键路径上埋点
Trace.beginSection("loadUserData");
// ... 你的业务逻辑 ...
Trace.endSection();
然后在 Perfetto 里就能看到这段代码的执行时间。我习惯在启动流程、列表滑动、网络请求这几个关键路径上都埋上点。这样一旦出问题,直接看 trace 就能定位。
29.2 SimplePerf:CPU 采样分析
Perfetto 擅长看系统级行为,但如果你想知道「哪个函数占用了最多的 CPU 时间」,那就得用 SimplePerf。
SimplePerf 是 Android 自带的 CPU 性能分析工具。它支持两种模式:
- 采样模式:每隔一段时间记录当前执行的代码位置
- 计数模式:统计 CPU 事件(如缓存未命中)的次数
我主要用采样模式。它开销小,适合线上问题分析。
29.2.1 基本用法
# 对 App 进程进行 CPU 采样,持续 10 秒
adb shell simpleperf record -p $(pidof com.example.app) \
-e cpu-cycles:u -f 4000 --duration 10 -o /data/local/tmp/perf.data
# 生成报告
adb shell simpleperf report -i /data/local/tmp/perf.data \
--sort comm,dso,symbol --percent 1
输出结果会告诉你,哪些函数占用了最多的 CPU 周期。我遇到过最典型的情况:一个看似简单的 JSON 解析,居然占了 30% 的 CPU 时间。后来换成手动解析,性能直接翻倍。
29.2.2 火焰图
SimplePerf 还能生成火焰图。火焰图这东西,说白了就是「看哪个函数调用栈最宽」。越宽的函数,占用的 CPU 时间越多。
# 生成火焰图数据
adb shell simpleperf report -i /data/local/tmp/perf.data \
--full-callgraph -o report.txt
# 用脚本转换成火焰图
python flamegraph.py --input report.txt --output flame.svg
打开生成的 SVG 文件,你会看到一堆堆叠的矩形。最顶层的矩形就是热点函数。我习惯从最宽的那个开始看,往往能发现意想不到的瓶颈。
29.3 R8 编译优化:让代码更小更快
R8 是 ProGuard 的替代品。它不仅能混淆代码,还能做深度优化。我见过很多团队只开了混淆,没开 R8 的全量优化,白白浪费了性能提升的机会。
29.3.1 开启 R8 全量优化
在 gradle.properties 里加上:
android.enableR8.fullMode=true
这个开关会启用 R8 的所有优化策略,包括:
- 内联:把短函数直接嵌入调用处,减少调用开销
- 常量传播:把编译期就能确定的常量直接替换
- 死代码消除:删除永远不会执行的代码
- 类合并:把多个类合并成一个,减少类加载开销
我做过一个实验:开启 fullMode 后,APK 体积减少了 15%,启动速度提升了 8%。效果很明显。
29.3.2 自定义规则
R8 的规则和 ProGuard 基本一致。但有几个点要注意:
# 保留反射调用的类
-keep class com.example.reflection.** { *; }
# 保留序列化相关
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
}
# 保留 JNI 方法
-keepclasseswithmembernames class * {
native <methods>;
}
我踩过一个坑:某个第三方库用了反射,我没加 keep 规则,结果线上崩溃了。从那以后,我养成了一个习惯——每次升级第三方库,都先跑一遍 R8 的 --printusage 选项,看看哪些类被删除了。
29.4 Baseline Profiles:启动加速的终极武器
Baseline Profiles 是 Android 12 引入的新特性。它的原理很简单:把 App 启动时需要用到的类和代码,提前编译成机器码。这样启动时就不用边解释边执行了。
29.4.1 生成 Baseline Profiles
Google 官方推荐用 Jetpack Macrobenchmark 来生成。但我个人觉得,手动配置更可控。
首先,在 src/main 目录下创建 baseline-prof.txt:
# 启动路径
HSPLcom/example/app/MainActivity;->onCreate(Landroid/os/Bundle;)V
HSPLcom/example/app/MainActivity;->onResume()V
HSPLcom/example/app/HomeFragment;->onCreateView(Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Landroid/os/Bundle;)Landroid/view/View;
# 常用类
HSPLcom/example/app/data/UserRepository;->getUser()Lcom/example/app/model/User;
HSPLcom/example/app/network/ApiService;->fetchData()Lretrofit2/Call;
# 第三方库
HSPLokhttp3/OkHttpClient;->newCall(Lokhttp3/Request;)Lokhttp3/Call;
HSPLcom/google/gson/Gson;->fromJson(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
然后,在 build.gradle 里配置:
android {
...
baselineProfile {
// 指定 profile 文件路径
from("src/main/baseline-prof.txt")
}
}
编译后,APK 里会包含 assets/dexopt/baseline.prof 文件。系统在安装 App 时,会根据这个文件提前编译。
29.4.2 效果验证
我测试过一个项目:没有 Baseline Profiles 时,冷启动需要 1.2 秒。加上之后,降到了 0.8 秒。提升了 33%。
但要注意,Baseline Profiles 只对冷启动有效。热启动和温启动,因为代码已经在内存里了,效果不明显。
29.5 知识体系总览
这一章的内容比较多,我画了一张图帮你理清思路:
这四块工具,各有各的用途。Perfetto 看宏观,SimplePerf 看微观,R8 做静态优化,Baseline Profiles 做动态加速。结合起来用,效果最好。
我个人的工作流是这样的:先用 Perfetto 定位卡顿发生的阶段,然后用 SimplePerf 找到具体的热点函数,接着用 R8 优化代码,最后用 Baseline Profiles 加速启动。一套下来,App 的性能通常能有明显提升。
嗯,工具链就讲到这里。下一章,我会讲怎么把这些工具集成到 CI/CD 流水线里,实现自动化性能监控。到时候,你就不用每次都手动抓 trace 了。