Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

性能工具专题

性能优化不是 “凭感觉改几行代码”, 而是先观测, 再定位, 再验证. 工具章节的面试重点是: 每个工具看什么, 适合什么问题, 输出如何转化为工程动作. 方法论, 指标口径与跨域取舍见 性能优化总览.

一, 工具选型总览

问题类型首选工具看到什么常见动作
启动慢Android Studio Profiler / Perfetto / MacrobenchmarkCPU, 主线程, 启动阶段耗时延迟初始化, 异步化, Baseline Profile
卡顿掉帧Perfetto / Systrace / gfxinfoChoreographer, RenderThread, 帧耗时减少主线程阻塞, 优化布局/绘制
内存泄漏LeakCanary / Memory Profiler / meminfo引用链, 堆大小, PSS解除生命周期引用, 缓存上限
I/O/网络违规StrictMode / Trace sections主线程磁盘/网络, 慢调用切线程, 缓存, 预加载
Native 热点simpleperf / PerfettoCPU sample, 调用栈, 符号优化算法, 减少 JNI 往返
线上复现困难dumpsys / bugreport / 自定义 trace系统状态快照关联日志, 设备维度排查

工具回答要避免 “列名词”, 而要说清楚 “我怀疑什么 → 用什么采样 → 看哪个指标 → 怎么验证改动”.

二, Android Studio Profiler

Android Studio Profiler 适合本地开发阶段快速观察 CPU, Memory, Network, Energy.

  • CPU Profiler: 看主线程是否有长任务, 方法调用耗时, 线程调度. 可用 Java/Kotlin Method Trace (分 Sampled 与 Instrumented 两种模式), 采样开销更低, 插桩更细但扰动更大.
  • Memory Profiler: 看 Java/Kotlin 堆, 对象分配, GC 频率, Heap Dump. 适合发现短时间内对象抖动和明显泄漏.
  • Network Profiler: 看请求时序, 流量大小, 频率, 但复杂线上网络问题仍要配合 OkHttp event listener, 服务端日志和抓包合规流程.
  • Energy Profiler: 关注 wakelock, 定位, 网络等耗电行为.

局限: Profiler 连接调试进程会带来观测扰动, 结论要用 release-like 包, Macrobenchmark 或 Perfetto 再验证.

三, Perfetto, Systrace 与 Trace sections

Perfetto 是现代 Android 系统级 tracing 工具, 可观察 CPU 调度, Binder, 线程状态, Choreographer, SurfaceFlinger, RenderThread, I/O 等. Systrace 是旧版工具, 很多概念仍沿用, 但新项目优先用 Perfetto.

import androidx.tracing.Trace

fun bindFeed(items: List<Item>) {
    Trace.beginSection("Feed.bind")
    try {
        adapter.submitList(items)
    } finally {
        Trace.endSection()
    }
}

Trace sections 的价值是把业务阶段写进系统 trace, 让你在 Perfetto 里看到 “哪段业务代码” 对应主线程长任务. 命名要稳定, 短, 能定位模块, 避免把用户敏感信息写入 trace 名称.

看 Perfetto 的常见路径:

  1. 先看主线程是否 Running 太久或频繁 Blocked.
  2. 看耗时段是否跨过 VSync 导致 missed frame.
  3. 看 RenderThread/GPU 是否被复杂绘制或纹理上传拖慢.
  4. 看 Binder, I/O, 锁等待是否阻塞主线程.
  5. 结合自定义 Trace sections 还原业务阶段.

四, Macrobenchmark 与 Baseline Profile

Macrobenchmark 用独立测试 APK 驱动目标 App, 在接近真实环境下测启动, 滚动, 页面跳转等宏观性能. 它适合做性能回归门禁, 比普通单元测试更贴近用户体验.

@Test
fun startup() = benchmarkRule.measureRepeated(
    packageName = "com.example.app",
    metrics = listOf(StartupTimingMetric()),
    iterations = 10,
    startupMode = StartupMode.COLD
) {
    pressHome()
    startActivityAndWait()
}

Baseline Profile 是把关键路径的类和方法提前提供给 ART, 安装后可更早编译热点代码, 改善冷启动和首帧性能. 典型流程是用 profileinstaller + androidx.baselineprofile Gradle 插件生成 / 合并 profile, 再用 Macrobenchmark 验证收益.

面试要点:

  • Macrobenchmark 关注稳定设备, 固定版本, 足够迭代次数和噪声控制.
  • Baseline Profile 不是万能优化, 主要改善解释执行 / JIT 预热带来的启动和关键路径抖动.
  • 性能数据要保存历史趋势, 否则无法判断回归.

五, LeakCanary 与内存定位

LeakCanary 自动观察 Activity, Fragment, ViewModel 等对象生命周期, 对象应被回收却仍被引用时触发 heap dump 并给出泄漏引用链.

常见泄漏原因:

  • 静态单例持有 Activity/Context.
  • Handler/Runnable/Coroutine 未随生命周期取消.
  • Adapter, Listener, Callback 未解绑.
  • Dialog/PopupWindow/Animator 持有 View.
  • 全局缓存无上限或 key/value 持有页面对象.

Memory Profiler 更适合看对象分配和堆变化, LeakCanary 更适合自动发现泄漏. 线上内存问题则要结合 OOM 日志, dumpsys meminfo, 业务埋点和设备分布分析.

六, StrictMode 与开发期红线

StrictMode 用来在开发/测试阶段发现主线程磁盘 I/O, 网络, 资源未关闭, Activity 泄漏等问题.

if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(
        StrictMode.ThreadPolicy.Builder()
            .detectDiskReads()
            .detectDiskWrites()
            .detectNetwork()
            .penaltyLog()
            .build()
    )
}

它不是线上性能监控方案, 而是把 “本不该发生的开发期违规” 尽早暴露. 不要为了消除告警简单 permitAll, 应该定位调用链并把 I/O 移到合适线程或启动阶段之外.

七, dumpsys, gfxinfo, meminfo

dumpsys 是 Android 系统服务状态入口, 适合连接真机后快速拿到系统级快照.

命令关注点用法场景
adb shell dumpsys gfxinfo <pkg>帧耗时, Janky frames, 渲染统计页面滑动 / 动画掉帧
adb shell dumpsys meminfo <pkg>PSS, Java/Native/Graphics 内存OOM, 内存增长
adb shell dumpsys activity <pkg>Activity/Service/进程状态生命周期, 进程保活排查
adb shell dumpsys batterystats耗电归因后台任务, 网络, 唤醒

gfxinfo framestats 可导出每帧各阶段时间, 适合脚本化对比优化前后. meminfo 的 PSS 更接近进程实际占用视角, 但仍要结合 Android 版本, 厂商和图形内存差异理解.

gfxinfo 示意输出, 非真实测量:

Stats since: 123456789ns
Total frames rendered: 240
Janky frames: 18 (7.50%)
50th percentile: 10ms
90th percentile: 21ms
95th percentile: 28ms
99th percentile: 44ms
Number Missed Vsync: 12

Total frames rendered 是统计窗口内的渲染帧数, Janky frames 和百分比是工具口径下的提示, 分位数展示长尾, Number Missed Vsync 表示错过 VSync 的计数. 先确认采样窗口, 刷新率, 设备和场景, 再与同条件基线比较; 这些字段不能单独证明根因, 仍需用 Frame Timeline/Perfetto 区分 UI, RenderThread, GPU 或调度问题.

八, simpleperf 与 Native CPU 热点

simpleperf 是 Android 官方 native profiling 工具, 可采样 CPU 指令, 调用栈和符号, 适合 NDK/JNI, 音视频, 加解密, 图像处理等热点定位.

典型关注:

  • native 函数是否占用异常 CPU.
  • JNI 往返是否过多.
  • 锁竞争, 内存拷贝, 算法复杂度是否是瓶颈.
  • so 是否保留符号或能通过符号表 / 映射文件还原调用栈.

面试表达可以说: Java/Kotlin 层先用 Perfetto/Profiler 定位到 native 调用段, 再用 simpleperf 下钻 native 热点, 最后用基准测试和真实场景回归验证.

九, 电量优化的证据链

电量问题要先说明耗电来源, 再在相同条件下测量. 不应根据一次使用感受或工具截图宣称节电百分比.

耗电来源常见触发条件优先核查可能的工程动作
Wakelock后台任务未结束, 播放或下载未正确收尾batterystats 中的持锁时间, 任务生命周期缩短持锁范围, 用系统调度约束任务, 确保取消与完成路径释放资源
网络无线电高频小请求, 失败重试, 前后台切换请求批次, 网络类型, 传输字节, 重试次数合并可延迟请求, 使用缓存和退避, 避免非必要轮询
定位高精度持续订阅, 未随页面/任务停止精度, 间隔, 前后台状态, 定位回调次数按业务精度分级, 设置时长/距离条件, 任务结束后移除更新
后台任务无约束的定时任务, 重复调度WorkManager/Job 约束, 执行次数, 失败链路使用网络, 充电和空闲约束, 合并唯一工作, 设置合理退避
渲染负载长时间动画, 高频重组 / 绘制, 屏幕常亮Frame Timeline, RenderThread, GPU 和刷新率减少无意义重绘, 在不可见时暂停动画, 降低刷新频率或质量

测量步骤与记录表

  1. 写明唯一假设, 例如 “后台同步的网络批次会减少无线电激活次数”; 不把多个改动混入同一次对比.
  2. 固定或记录设备型号, 电池健康状态, Android 版本, App 构建版本, 屏幕亮度, 网络类型, 账号数据量和测试时长.
  3. 准备优化前后两个构建, 使用相同起始电量与业务脚本, 至少保留空闲或未执行业务的对照组.
  4. 用 Battery Historian 解析 batterystats, 用 Perfetto 观察调度, wakelock 和网络相关轨道, 或使用系统电量报告交叉核对. 工具字段和可见数据会随系统版本变化.
  5. 记录原始报告路径或可复现命令, 比较任务次数, 持锁时长, 网络字节, 定位回调和帧时间等中间证据; 再判断是否需要扩大样本或回滚改动.
字段本次记录
假设与改动版本[填写可复核的假设和 commit/build 标识]
设备与系统[型号, Android 版本, 电池状态]
网络, 屏幕与数据条件[Wi-Fi/蜂窝, 亮度, 账号/数据集]
测试时长与重复次数[填写]
对照组与工具报告[填写报告位置或命令]
观察到的中间证据[wakelock/网络/定位/帧时间等]
结论与限制[只填写可由上述条件复核的结论]

该表是读者练习和实验记录模板, 其中字段不是本项目实测数据. 电量受设备温度, 基带状态, 电池老化和系统调度影响; 单次测量只能形成假设证据, 不应外推为普遍指标.

命令, 输出与 Baseline Profile 操作

以下命令要求已启用 adb 调试, 目标设备允许 profile/trace, 且应在 release-like, 可复现场景中执行; 工具和权限限制随 Android 版本, user/userdebug 构建变化. 命令输出字段仅作解读示例, 不是本项目实测结果.

adb shell perfetto -o /data/misc/perfetto-traces/app.perfetto-trace -t 10s sched freq binder_driver gfx view
adb pull /data/misc/perfetto-traces/app.perfetto-trace .
adb shell dumpsys gfxinfo com.example.app framestats
adb shell simpleperf record -p <pid> -g --duration 10 -o /data/local/tmp/perf.data
adb pull /data/local/tmp/perf.data ./perf.data
simpleperf report -i ./perf.data

Perfetto 要先选择与假设对应的 data source; 打开 trace 后看 main/RenderThread 的 slice, sched 状态和 Binder/I/O 是否跨过 frame deadline. gfxinfo framestats 的每行代表一帧时间戳, Janky frames 是统计提示, 不能脱离刷新率和采样窗口判断根因. simpleperf 的顺序是: 设备端 record 写入 /data/local/tmp/perf.data, adb pull 生成其主机端副本 ./perf.data, 主机端 simpleperf report -i ./perf.data 读取该副本. 它们是同一采样数据文件的设备端与主机端副本, 并非 report 直接读取设备路径. report 按 sample 占比列函数; 先确认符号可用和调用栈, 再检查热点是否在目标场景稳定出现. 设备必须允许目标进程被 profile, user 构建, SELinux, profileable/debuggable 状态可能限制采样; 应按权限错误调整到获准的构建, 不能伪造结果.

Baseline Profile 配置片段 (需 androidx.baselineprofile 插件和独立 benchmark/profile module; 版本按项目 Gradle 统一管理):

baselineProfile {
    filter { include("com.example.app") }
}

Profile 生成场景应覆盖冷启动和关键交互, 生成后随 APK/AAB 打包; 用 Macrobenchmark 在固定设备, 构建和迭代数下对比. 若关键路径变动, 重生成 profile; 不把 Profile 当作替代主线程, 算法或 I/O 优化的手段.


问题, 工具, 证据与限制

选工具先从问题出发: 启动/帧用 Macrobenchmark 与 Perfetto, Java heap 用 heap dump, native allocation 用 heapprofd, 线上退出原因用平台 API/vitals. 每次记录设备, OS, 构建类型, profileable/debuggable, trace 配置和噪声. Profiler UI 与命令可能随 Android Studio/Perfetto 版本变化; 操作步骤应带版本, 单次 trace 只支持一个假设, 不直接证明因果.

高频面试题

Q1: Profiler, Perfetto, Systrace 怎么选? Profiler 适合开发期快速看单进程 CPU/内存/网络; Perfetto 适合系统级时序分析, 能看调度, 渲染, Binder, I/O; Systrace 是旧链路但概念相通. 复杂卡顿优先 Perfetto, 内存泄漏优先 LeakCanary/Memory Profiler.

Q2: Trace.beginSection 有什么价值? 它把业务阶段标记进系统 trace, 让 Perfetto 里能把主线程长任务和具体模块对应起来. 注意 section 名称要稳定, 简洁, 无敏感信息, 并保证 begin/end 成对.

Q3: Macrobenchmark 和普通 benchmark 区别? Macrobenchmark 从 App 外部驱动真实启动, 滚动, 跳转等宏观场景, 更贴近用户体验; 普通 microbenchmark 更适合测小函数 / 算法. 性能门禁通常用 Macrobenchmark 看趋势和回归.

Q4: Baseline Profile 解决什么问题? 它把启动和关键路径热点提前提供给 ART 编译, 减少冷启动 / 首帧阶段解释执行和 JIT 预热成本. 它不是替代业务优化, 仍要用 Macrobenchmark 验证收益.

Q5: LeakCanary 报出泄漏后怎么处理? 先看泄漏对象和引用链, 判断是否生命周期结束后仍被持有; 再定位静态引用, 回调, 协程, Handler, Adapter 等持有者; 修复后重新进入 / 退出页面并确认泄漏不再出现.

易错点 / 追问

  • 不要只贴工具截图, 要能说明指标含义和下一步工程动作.
  • 不要在 debug, 插桩, 连接 Profiler 的环境下直接宣称线上性能收益.
  • Perfetto 看到主线程长任务后, 要继续区分 CPU 忙, 锁等待, I/O, Binder 等原因.
  • Baseline Profile 需要随关键路径变化更新, 否则 profile 会逐渐失效.
  • StrictMode 告警不能靠关闭规则解决, 应修正线程和资源使用.