Android 应用性能分析工具 — CPU Profiler

1,099

相信大家对于 Android Studio 的分析工具都不陌生,我们可以借助分析工具对 App 性能数据进行很直观的阅读。文章内容参考了 Google 官方教程上面的说明。

下图时 Android Studio 3.0 中的 Android Profiler 面板,相较于之前版本,对于开发者而言更友好了。比如我们如果感觉到应用启动比较慢、用户操作有些卡顿,怀疑是代码性能问题但是又不知道什么原因造成的,通常很多性能问题都是主线程的执行耗时操作导致的,要想定位问题就得先知道实际运行时的性能数据,下面我们就拿 CPU Profiler 来学习如何借助分析工具优化性能。

Android Profiler

CPU Profiler概述

当你打开 CPU Profiler 时,它会立即开始显示应用程序的 CPU 使用情况和线程活动,如下图所示:

图中标示的位置代表了主要功能视图,具体功能如下: **(1) 事件时间轴:**显示应用程序在其生命周期中转换不同状态的活动,并指示用户与设备的交互,包括屏幕旋转事件。 要了解有关事件时间轴的更多信息,包括如何启用它,请阅读启用高级分析 。

**(2) CPU 时间轴:**显示应用程序的实时 CPU 使用率(占总可用CPU时间的百分比)以及应用程序使用的线程总数。 时间轴还显示其他进程(如系统进程或其他应用程序)的 CPU 使用情况,因此可以将其与应用程序的使用情况进行比较。 您可以通过沿着时间轴的水平轴移动鼠标来检查历史 CPU 使用率数据。

**(3) 线程活动时间轴:**列出属于应用进程的每个线程,并使用下面列出的颜色在时间线上指示其活动。 记录方法跟踪后,可以从此时间轴中选择一个线程,以在跟踪窗格中检查其数据。 **绿色:**线程处于活动状态或准备好使用 CPU ,也就是说它处于“运行”或“可运行”状态。 **黄色:**线程处于活动状态,它正在等待I / O操作(如磁盘或网络I / O)完成后继续工作。 **灰色:**线程处于休眠状态,不会占用任何 CPU 时间。这种状态可能会发生在线程需要访问尚不可用的资源时,线程主动进入休眠状态或者系统内核让线程进行休眠,直到所需的资源变得可用后再恢复。

**(4) 录制配置:**允许您选择以下选项之一来确定分析器如何记录方法跟踪。 **Sampled:**这是一种基于数据抽样的跟踪,分析器会在应用执行期间频繁地捕获应用程序的调用堆栈,通过收集比对捕获到的数据集来获得有关应用程序代码执行的时间和资源使用信息,适合跟踪生命周期相对较长的方法,否则你应该使用 Instrumented 配置。 **Instrumented:**这是一种可在运行时调整应用程序以便记录每个方法开始和结束时的时间戳,适合跟踪生命周期相对较短的方法。通过收集和比较时间戳生成方法跟踪数据,包括时序信息和 CPU 使用情况等等。 请注意,由于与每种方法测量相关的开销会影响运行时性能,并可能影响分析数据,对于生命周期相对较短的方法,这一点更为显着。 此外,如果应用程序在短时间内执行大量方法,则分析器可能会快速超过其文件大小限制,并且无法记录任何其他跟踪数据。 **自定义编辑配置:**允许自行更改上述采样和检测录制配置的某些默认值,并将其另存为自定义配置。 要了解更多信息,请参阅有关创建录制配置的部分。

**(5) 录制按钮:**开始和停止录制方法跟踪。 要了解更多信息,请参阅有关录制和分析跟踪方法 。

**注意:**分析器还展示了 Android Studio 和 Android 平台添加到应用进程的线程的 CPU 使用情况,例如 JDWP, Profile Saver, Studio:VMStats, Studio:Perfa 和 Studio:Heartbeat等名称的线程(尽管如此,确切的名称显示在线程活动时间表可能会有所不同)。

录制和分析跟踪方法

简单的说,就是点击 Android Studio 的 Android Profiler 窗口的录制按钮开始 CPU 跟踪记录,然后对应用程序进行一些操作,操作完成后再点击停止录制按钮结束录制。这时分析器会自动选择记录的时间范围并在方法跟踪窗格中显示其跟踪信息,如下图所示。如果要检查不同线程的方法跟踪,只需从线程活动时间轴中选择它即可。

录制方法跟踪后的 CPU Profiler 视图,主要有如下信息展示:

**(1) 所选时间范围:**在跟踪窗格中确定要检查的记录时间段的一部分。 当你首次记录方法跟踪时,CPU分析器会自动在CPU时间轴中选择录制的整个长度。 如果要仅在记录的时间段的一部分内检查方法跟踪数据,则可以单击并拖动突出显示区域的边缘以修改其长度。 **(2) 时间戳记:**指示记录的方法跟踪的开始和结束时间(相对于分析器开始从设备收集CPU使用情况信息)。 你可以单击时间戳以自动选择整个录制作为所选的时间范围 - 如果您要在其间切换多个录像,这一点特别有用。 **(3) 跟踪窗格:**显示你选择的时间范围和线程的方法跟踪数据。 仅当你记录至少一个方法跟踪后,此窗格才会显示。 在此窗格中,您可以选择如何查看每个堆栈跟踪(使用跟踪选项卡)以及如何测量执行时间(使用时间参考下拉菜单)。 **(4) 跟踪窗格选项:**可以选择将方法跟踪显示为Top Down tree, Bottom Up tree, Call Chart, or Flame Char模式,具体的区别可以在下面的部分中了解有关每个跟踪窗格选项卡的更多信息。 **(5) 时序信息菜单:**确定每个方法调用的时序信息如何测量: Wall clock time:表示实际运行时间。 Thread time:表示实际运行的时间减去线程不消耗 CPU 资源的时间段的部分。 对于任何指定的方法,其线程时间始终小于或等于Wall clock time。 使用线程时间可以更好地了解指定方法消耗的线程实际 CPU 使用量。

使用 Call Chart 选项分析跟踪

“Call Chart” 是按照方法调用的顺序进行跟踪分析的图示,其中方法调用(caller)的生命周期和时间在水平轴中表示,被其调用的方法沿垂直轴显示。

比如一个方法 A() 里面包含了对 B() 和 D() 方法的调用,那么在 Call Chart 模式下 B 和 D 方法在 A 的下方显示,横向长度代表它们所执行的时长。

为了方便区分,分析工具对系统 API 的方法调用以橙色显示,对应用程序自己的方法的调用以绿色显示,并且对第三方 API(包括 Java 语言API)的方法调用显示为蓝色。 下图显示了一个示例调用图,并说明了给定方法的自我时间,子时间和总时间的概念。

方法 D self, children, and total time 的示意图

使用 Flame Char 选项分析跟踪

“Flame Char” 提供了一个反向调用图,可以聚合相同的调用堆栈。 也就是说共享相同调用者序列的方法被收集表示为 Flame Char 图表中的一个更长的条(而不是将其显示为多个较短的条)。 这使得更容易看出哪些方法消耗最多的时间。 这也意味着水平轴不再代表时间轴,而是指示每个方法执行的相对时间量。

为了帮助说明这个概念,请看下图中的调用图。

图中 B1, B2 和 B3 共享相同的调用者序列 (A→D→B),所以它们被聚合。 类似地 C1 和 C3 被聚合,因为它们共享相同的调用者序列 (A→D→B→C) , 注意这其中不包括 C2 ,因为它具有不同的呼叫者序列 (A→D→C) 。

聚合相同的调用者序列的方法用于 Flame Char ,最终效果如下图所示。当我们指定 Flame Char 中的任何方法调用进行分析时,首先出现的肯定是消耗 CPU 时间最多的被调用者,这使得很容易看出哪些方法最消耗时间,方便有针对性优化。

使用 Top Down tree 和 Bottom Up tree 分析

Top Down 自上而下显示方法调用列表,其中展开方法节点显示被其调用者,图中的每个箭头指向一个被调用者。与 Flame 图表类似,自上而下的树聚合了共享相同调用堆栈的相同方法的跟踪信息

Bottom Up 自下而上显示方法调用列表,以帮助描述每个方法调用花费的 CPU 时间(时间也表示为所选时间段内线程总时间的百分比):

  • Self:方法调用花费的时间执行自己的代码,而不是其被调用者的时间量
  • Children:方法调用花费的执行被调用者的时间,而不是自己的代码
  • Total:方法时间的总和,这表示应用程序执行方法调用的总时间量

实际操作

运行项目,应用启动后我们可以在 Android Profiler 窗口中看到应用的实时运行数据,先选中要调试的 app 进程,点击 CPU 一栏会进入到 CPU Profiler 控制面板,这是我们可以点击录制按钮开始录制,进行一些操作后,再次点击按钮停止录制,这是 CPU Profiler 会自动打开分析窗口。如下图所示:

CPU Profiler

我们想要看看方法执行的耗时情况,我们选择时序信息菜单为 Thread Time ,按照默认的 Call Chart 数据跟踪模式来看,可以很清楚的看到应用执行耗时情况。

Thread Time.png

图中展示了应用启动时的 onCreate 方法的耗时情况,其中黄色标示的事系统方法,绿色部分方法是我们的方法,也就是可优化的空间。比如上图中腾讯 x5 webview 初始化耗时很严重可以考虑做懒加载,另外一些获取渠道信息的方法 getChannel* 需要读取本地配置也是耗时操作,将这些方法做成异步处理。因为 onCreate 方法直接影响应用启动耗时,所以我们要避免其方法内的执行耗时操作。

于是按照上面定位到的问题,进行优化后,可以看到下面的效果:

优化后加速应用启动