背景
在我的项目界面里有多个选择器(如下图)用于筛选发送网络请求,但是在点击选择器进行选择的过程中十分卡顿。

我就想着刚好学习一下使用Perfetto并锻炼自己性能分析的能力。因此,下面都是在我的第一人称的视角进行的分析,也是我自己如何使用Perfetto的分享。
过程

点击Android Studio的Profiler,然后点击第三个System Trace。System Trace 是Android Studio提供的Perfetto可视化分析工具,其底层就是Perfetto。下面的Start profiler task from有两种,分别是Now 和Process Start。Process Start表示从应用进程启动开始计算,这个更适合研究App启动时的性能分析;Now表示的是从应用当前界面为基准,由于我研究单个页面的组件为何卡顿,不需要从启动App开始。因此,选择Now即可。
点击Start anyway后,Trace 便开始录制,此时需要你复现界面卡顿流程。录制完毕,即出现如下图界面。

首先看左边,从上往下看分别是时间轴、Lifecycle(生命周期)、Janky frames(卡顿帧)、MainThread(也可以说是UI Thread)、RenderThread,主要就看这几个。
- 时间轴: 主要用于缩小发生卡顿的范围
- 生命周期: 主要用于判断卡顿处于什么周期,适合与时间轴搭配缩小出现卡顿的范围,但是我没用上。
- Janky frames: 这个最重要,Android要求60HZ下,每帧时间不超过1000ms/60,也就是16.6ms。也就是说绘制一帧的时间不超过16.6ms。如果某一帧超过了16.6ms,也就被称为Jank,也就是说Janky frames记录的是掉帧的片段。
- MainThread: 在这里面主要是你的UI主线程,这里大部分是Compose布局、重组的业务。
- RenderThread: 这个主要负责GPU渲染。
OK,大概的介绍就到这里,我们直接开始实操分析。
首先缩小时间轴,我录制的时候看到出现卡顿的时间是4s以后,那么就将时间轴控制在3-5秒左右
发现4s后的第一个Janky frmaes就很可疑,点击这个Janky然后按M键来将这个Janky frames高亮并居中显示。
这时候可以看右侧Analysis(如下图),发现Layout name为TX-弹出式窗口,恰好符合所描述的卡顿现象,其中Actual duration为237.86ms,远超16.6ms。

Main thread states表示这个Janky frame 里UI线程的情况,图中Running的状态为占据69.49%、Duration时长为26.31ms,这远超过正常范围。说明卡顿原因大概率在CPU处理上,比如Compose布局、重组、业务逻辑。
RenderThread states的Running时长为2.7ms,Runnable时长为221μs,这说明GPU渲染是没有问题的。
查看事件名发现Events里大量出现DrawFrames、query、syncFrameState、prepareTree、beginFrame。这些都是准备事件,因此推断点击选择框后popup首帧的窗口的准备成本很高,回到代码,找到DropdownMenu发现两点:
expanded从false变true会触发重组options.forEach会一次性组合所有的选项
因为我们options数量多,所以点击时会一口气创建大量DropdownMenuItem和Text。
假设:
- 点击选择框后,
expanded = true - Compose创建
DropdownMenu的popup options.forEach一次性组合全部选项- 系统对
popup window进行测量、布局、绘制、图层同步。
验证:
- 限制选项数量
scss
options.take(20).forEach { ... }
- 打印数量
bash
Log.d("SelectorDropdown", "$label options=${options.size}")
查看打印日志发现同一个下拉框会重复打印多次,说明它在重复重组,而不是只创建一次。所以这个卡顿的本质不是数据量大,而是点击触发了整棵选择器树的级联重组,再叠加DropdownMenu弹出窗口本身的首帧创建和绘制成本,就出现掉帧、卡顿现象。
解决方案:
-
减小
DropdownMenu创建PopupWindow的首帧成本- 把
DropdownMenu/ExposedDropdownMenuBox改成页面内展开的Card + LazyColumn
- 把
-
减少无意义状态触发
对state.xxx.map {...}加remember,避免每次重新构造选项列表

卡顿延迟从237.86ms->10.81ms