Android Profiler能够提供关于应用 CPU、内存和网络的实时数据。
一 . 启动分析
要打开 Android Profiler 窗口,请按以下步骤操作:
1. 点击工具栏中的 Android Profiler(也可以点击 View > Tool Windows > Android Profiler )。
2. 在 Android Profiler 窗口顶部选择想要分析的设备和应用进程,如下图所示。
上图中各个数字对应的含义:
① 要分析的设备。
② 要分析的应用进程。
③ 时间线缩放控件。
④ 实时更新跳转按钮。
⑤ Event时间线,包括Activity状态、用户输入Event和屏幕旋转Event。
如果显示“Advanced profiling is unavailable for the selected process”,可以在顶层工具栏中点击 Profile ‘app‘ 来运行,或者在运行配置中启用高级分析,按以下步骤操作:
1. 选择 Run > Edit Configurations。
2. 在左侧窗格中选择您的应用模块。
3. 点击 Profiling 标签,然后勾选 Enable advanced profiling。
重新构建并运行应用即可。
二. CPU Profiler
CPU Profiler 可帮助实时检查应用的 CPU 使用率和线程 Activity,并记录函数跟踪,以便优化和调试应用代码。
2.1 CPU Profiler 概览
如上图所示,CPU Profiler 的默认视图包括以下内容:
① Event 时间线: 显示应用中在其生命周期转换的 Activity,并显示用户与设备的交互,包括屏幕旋转 Event。
② CPU 时间线: 显示应用的实时 CPU 使用率(占总可用 CPU 时间的百分比)以及应用使用的总线程数。 此时间线还显示其他进程的 CPU 使用率(如系统进程或其他应用),以便可以将其与自己的应用使用率进行对比。 通过沿时间线的水平轴移动鼠标,还可以检查历史 CPU 使用率数据。
③ 线程 Activity 时间线: 列出属于应用进程的每个线程。下面说明不同的颜色对应的含义:
- 绿色: 表示线程处于活动状态或准备使用 CPU。 即,它正在“运行中”或处于“可运行”状态。
- 黄色: 表示线程处于活动状态,但它正在等待一个 I/O 操作(如磁盘或网络 I/O),然后才能完成它的工作。
- 灰色: 表示线程正在休眠且没有消耗任何 CPU 时间。 当线程需要访问尚不可用的资源时偶尔会发生这种情况。 线程进入自主休眠或内核将此线程置于休眠状态,直到所需的资源可用。
④ 记录配置: 选择分析器记录函数跟踪的方式,如下:
- Sampled: 以固定周期记录。在应用执行期间频繁捕获应用的调用堆栈。 分析器比较捕获的数据集以推导与应用代码执行有关的时间和资源使用信息。 基于“Sampled”的跟踪的问题是,如果应用在捕获调用堆栈后进入一个函数并在下一次捕获前退出该函数,则分析器不会记录该函数调用。 如果对此类生命周期很短的跟踪函数感兴趣,应使用“Instrumented”跟踪。
- Instrumented: 以函数调用时间为周期记录。在运行时设置应用以在每个函数调用的开始和结束时记录时间戳。 它收集时间戳并进行比较,以生成函数跟踪数据,包括时间信息和 CPU 使用率。 注意,与设置每个函数关联的开销会影响运行时性能,并可能会影响分析数据,对于生命周期相对较短的函数,这一点更为明显。 此外,如果应用短时间内执行大量函数,则分析器可能会迅速超出它的文件大小限制,且不能再记录更多的跟踪数据。
- Edit configurations: 允许更改上述“Sampled”和“Instrumented”记录配置的某些默认值,并将它们另存为自定义配置。
⑤ 记录按钮: 用于开始和停止记录函数跟踪。
注:分析器还会报告 Android Studio 和 Android 平台添加到您的应用进程(如 JDWP、Profile Saver、Studio:VMStats、Studio:Perfa 以及 Studio:Heartbeat)的线程 CPU 使用率。
2.2 记录和检查函数跟踪
选择 Sampled 或 Instrumented ,然后点击Record开始记录函数跟踪,点击Stop recording结束,如下图所示。
① 选择时间范围: 确定要在跟踪窗格中检查所记录时间范围的哪一部分。 当首次记录函数跟踪时,CPU Profiler 将在 CPU 时间线中自动选择完整长度。 如果想仅检查所记录时间范围一小部分的函数跟踪数据,可以点击并拖动突出显示的区域边缘以修改其长度。
② 时间戳: 用于表示所记录函数跟踪的开始和结束时间(相对于分析器从设备开始收集 CPU 使用率信息的时间)。 可以点击时间戳以自动选择完整记录。
③ 跟踪窗格: 用于显示所选的时间范围和线程的函数跟踪数据。
④ 通过调用图表、火焰图、 Top Down 树或Bottom Up 树的形式显示函数跟踪。
⑤ 确定如何测量每个函数调用的时间信息:
- Wall clock time:实际经过的时间。
- Thread time:实际经过的时间减去线程没有消耗 CPU 资源的时间。
2.2.1 使用 Call Chart 标签检查跟踪
Call Chart 标签提供函数跟踪的图形表示形式,其中,水平轴表示函数调用(或调用方)的时间,并沿垂直轴显示其被调用者。 对系统 API 的函数调用显示为橙色,对应用自有函数的调用显示为绿色,对第三方 API(包括 Java 语言 API)的函数调用显示为蓝色。 下图展示了一个调用图表示例,并描绘了给定函数的 Self time、Children time 以及总时间的概念。
提示:若要跳转到某个函数的源代码,请右键点击该函数并选择 Jump to Source。
2.2.2 使用 Flame Chart 标签检查跟踪
Flame Chart 标签提供一个倒置的调用图表,其中水平轴不再代表时间线,它表示每个函数相对的执行时间。
下面说明此概念,考虑下图中的调用图表。注意函数 D 多次调用 B(B1、B2 和 B3),其中一些对 B 的调用也调用了 C(C1 和 C3)。
由于 B1、B2 和 B3 共享相同的调用方顺序 (A → D → B),因此可将它们汇总在一起,如下所示。 同样,将 C1 和 C3 汇总在一起,因为它们也共享相同的调用方顺序 (A → D → B → C)。注意未包含 C2,因为它具有不同的调用方顺序 (A → D → C)。
汇总的函数调用用于创建火焰图,如下图所示。注意,对于火焰图中任何给定的函数调用,首先显示消耗最多 CPU 时间的被调用方。
2.2.3 使用 Top Down 和 Bottom Up 检查跟踪
Top Down 标签显示一个函数调用列表,在该列表中展开函数节点会显示函数的被调用方。 下图显示上面调用图表对应的“Top Down”图表。图表中的每个箭头都从调用方指向被调用方。
如上图所示,在“Top Down”标签中展开函数 A 的节点可显示它的被调用方,即函数 B 和 D。 然后,展开函数 D 的节点可显示它的被调用方,即函数 B 和 C 等等。
Top Down 标签提供以下信息以说明在每个函数调用上所花费的 CPU 时间:
- Self: 表示函数调用在执行自己的代码(而非被调用方的代码)上所花的时间。
- Children: 表示函数调用在执行自己的被调用方(而非自己的代码)上所花的时间。
- 总和: 函数的 Self 和 Children 时间的总和。 表示应用在执行函数调用上所花的总时间。
Bottom Up 标签显示一个函数调用列表,在该列表中展开函数节点将显示函数的调用方,如下图所示。
如上图所示,在“Bottom Up”树中打开函数 C 的节点可显示它的调用方 B 和 D。 注意,尽管 B 调用 C 两次,但在“Bottom Up”树中展开函数 C 的节点时,B 仅显示一次。 然后,展开 B 的节点显示其调用方,即函数 A 和 D。
注:当分析器到达文件大小限制时,Android Studio 将停止收集新数据(但不会停止记录)。
2.3 创建记录配置
要创建或编辑自定义配置,或检查现有的默认配置,可通过记录配置下拉菜单中选择 Edit configurations 来打开 CPU Recording Configurations 对话框,如下图所示。
可以在左侧窗格中选择现有配置来检查其设置,也可按如下方式创建一个新的记录配置:
1. 点击对话框左上角的 。
2. 为配置命名。
3. 在 Trace Technology 部分选择 Sampled 或 Instrumented。
4. 对于“Sampled”记录配置,以微秒 (μs) 为单位指定 Sampling interval。 此值表示应用调用堆栈的每个抽样之间的持续时间。
5. 对于写入连接设备的记录数据,以兆字节 (MB) 为单位指定 File size limit。 当您停止记录时,Android Studio 将解析此数据并将其显示在分析器窗口中。
注: 如果使用运行 API 级别 26 或更高版本的连接设备,则对于跟踪数据的文件大小没有限制,此值可忽略。
6. 点击 Apply 或 OK。 如果更改了其他记录配置,则也将保存这些更改。
三. Memory Profiler
Memory Profiler 是可帮助识别导致应用卡顿、冻结甚至崩溃的内存泄漏和流失。 它显示一个应用内存使用量的实时图表,可以捕获堆转储、强制执行垃圾回收以及跟踪内存分配。
3.1 Memory Profiler 概览
如上图所示,Memory Profiler 的默认视图包括以下各项:
① 强制执行垃圾回收 Event 。
② 捕获堆转储。
③ 记录内存分配情况。 此按钮仅在运行 Android 7.1 或更低版本的设备时才会显示。
④ 放大/缩小时间线。
⑤ 跳转至实时内存数据。
⑥ Event 时间线,其显示 Activity 状态、用户输入 Event 和屏幕旋转 Event。
⑦ 内存使用量时间线,包含以下内容:
- 显示每个内存类别使用多少内存的堆叠图表,如左侧的 y 轴以及顶部的彩色键所示。
- 虚线表示分配的对象数,如右侧的 y 轴所示。
- 用于表示每个垃圾回收 Event 的图标。
如上图,内存计数中的类别如下所示:
- Java:从 Java 或 Kotlin 代码分配的对象内存。
- Native:从 C 或 C++ 代码分配的对象内存。
- Graphics:图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。 (注意,这是与 CPU 共享的内存,不是 GPU 专用内存。)
- Stack: 应用中的原生堆栈和 Java 堆栈使用的内存,通常与应用运行多少线程有关。
- Code:应用用于处理代码和资源(如 dex 字节码、已优化或已编译的 dex 码、.so 库和字体)的内存。
- Other:应用使用的系统不确定如何分类的内存。
- Allocated:应用分配的 Java/Kotlin 对象数。 没有计入 C 或 C++ 中分配的对象。
注:目前,Memory Profiler 还会显示应用中的一些误报的原生内存使用量,而这些内存实际上是分析工具使用的。 对于大约 100000 个对象,最多会使报告的内存使用量增加 10MB。
3.2 查看内存分配
Memory Profiler 可显示有关对象分配的以下信息:
- 分配哪些类型的对象以及它们使用多少空间。
- 每个分配的堆叠追踪,包括在哪个线程中。
- 对象在何时被取消分配(Android 8.0+)。
如果设备运行 Android 8.0 或更高版本,可以按照下述方法查看对象分配: 只需点击并按住时间线,并拖动选择想要查看分配的区域,如下图所示:
如果设备运行 Android 7.1 或更低版本,则在 Memory Profiler 工具栏中点击 Record memory allocations 。 操作完成后,点击 Stop recording 以查看分配。如下图所示:
在选择一个时间线区域后,已分配对象的列表将显示在时间线下方,按类名称进行分组,并按其堆计数排序。
注:在 Android 7.1 及更低版本上,最多可以记录 65535 个分配。 如果记录超出此限值,则记录中仅保存最新的 65535 个分配。 在 Android 8.0 及更高版本中,没有此限制。
要检查分配记录,请按以下步骤操作:
1. 浏览列表以查找堆计数异常大且可能存在泄漏的对象。 点击 Class Name 列标题可以按字母顺序排序。 然后点击一个类名称。 此时在右侧将出现 Instance View 窗格,显示该类的每个实例。
2. 在 Instance View 窗格中,点击一个实例。 此时下方将出现 Call Stack 标签,显示该实例被分配到何处以及在哪个线程中,如下图所示。
3. 在 Call Stack 标签中,双击任意行以在编辑器中跳转到该代码。
默认情况下,左侧的分配列表按类名称排列。 在列表顶部,可以使用右侧的下拉列表在以下排列方式之间进行切换:
- Arrange by class:基于类名称对所有分配进行分组。
- Arrange by package:基于软件包名称对所有分配进行分组。
- Arrange by callstack:将所有分配分组到其对应的调用堆栈。
3.3 捕获堆转储
堆转储显示在捕获堆转储时应用中哪些对象正在使用内存。 特别是在长时间的用户会话后,堆转储会显示您认为不应再位于内存中却仍在内存中的对象,从而帮助识别内存泄漏。 在捕获堆转储后,可以查看以下信息:
- 应用已分配哪些类型的对象,以及每个类型分配多少。
- 每个对象正在使用多少内存。
- 在代码中的何处仍在引用每个对象。
- 对象所分配到的调用堆栈。 (目前,如果在记录分配时捕获堆转储,则只有在 Android 7.1 及更低版本中,堆转储才能使用调用堆栈。)
要捕获堆转储,在 Memory Profiler 工具栏中点击 Dump Java heap 。 在转储堆期间,Java 内存量可能会暂时增加, 这很正常,因为堆转储与您的应用发生在同一进程中,并需要一些内存来收集数据。
堆转储显示在内存时间线下,显示堆中的所有类类型,如下图所示。
要检查堆,请按以下步骤操作:
1. 浏览列表以查找堆计数异常大且可能存在泄漏的对象。 点击 Class Name 列标题可以按字母顺序排序。 然后点击一个类名称。 此时在右侧将出现 Instance View 窗格,显示该类的每个实例。
2. 在 Instance View 窗格中,点击一个实例,此时下方将出现 References,显示该对象的每个引用。点击实例名称旁的箭头可以查看其所有字段,然后点击一个字段名称查看其所有引用。 如果要查看某个字段的实例详情,右键点击该字段并选择 Go to Instance,如下图所示。
3. 在 References 标签中,如果发现某个引用可能在泄漏内存,则右键点击它并选择 Go to Instance。
在类列表中,可以查看以下信息:
- Heap Count:堆中的实例数。
- Shallow Size:此堆中所有实例的总大小(以字节为单位)。
- Retained Size:为此类的所有实例而保留的内存总大小(以字节为单位)。
在类列表顶部,可以使用左侧下拉列表在以下堆转储之间进行切换:
- Default heap:系统未指定堆时。
- App heap:应用在其中分配内存的主堆。
- Image heap:系统启动映像,包含启动期间预加载的类。 此处的分配保证绝不会移动或消失。
- Zygote heap:写时复制堆,其中的应用进程是从 Android 系统中派生的。
默认情况下,此列表按 Retained Size 列排序,可以点击任意列标题以更改列表的排序方式。
在 Instance View 中,每个实例都包含以下信息:
- Depth:从任意 GC root 到所选实例的最短 hop 数。
- Shallow Size:此实例的大小。
- Retained Size:此实例支配的内存大小。
3.4 将堆转储另存为 HPROF
如果要保存堆转储以供日后查看,可通过点击时间线下方工具栏中的 Export heap dump as HPROF file,将堆转储导出到一个 HPROF 文件中。 在显示的对话框中,确保使用 .hprof 后缀保存文件。然后,通过将此文件拖到一个空的编辑器窗口就可以在 Android Studio 中打开该文件。
要使用其他 HPROF 分析器(如 jhat),需要将 HPROF 文件从 Android 格式转换为 Java SE HPROF 格式。 可以使用
android_sdk/platform-tools/ 目录中的 hprof-conv 工具执行此操作。 运行该命令需要两个参数:原始 HPROF 文件和转换后 HPROF 文件的写入位置。 例如:
hprof-conv heap-original.hprof heap-converted.hprof
3.5 分析内存泄漏示例
下面举例说明如何分析内存泄漏。下图点击中间“进入内存泄漏Activity”按钮,可以进入一个产生内存泄漏的Activity,下面展示如何通过Memory Profiler找到产生内存泄漏的类并定位到产生泄漏的代码。
首先,我们可以反复进入和退出内存泄漏Activity,然后在Memory Profiler中强制垃圾回收,之后捕获堆转储。待分析结果出来后,可以点击右侧的筛选按钮,筛选我们关注的包名,如下图所示:
也可以在“Arrange by class”处选择“Arrange by package”来进行手动选择。
可以看到有3个MemoryLeakActivity对象,按理说退出它们并GC之后不应该在内存中。我们单击那一行,右侧会显示每个实例,点击其中一个实例,下面会显示其引用,如下图所示:
我们可以双击第一个引用,或者右键点击选择“Jump to Source”,就可以定位到产生内存泄漏的代码,如下:
这样一个内存泄漏的分析过程就结束了。
四. Network Profiler
Network Profiler 能够在时间线上显示实时网络 Activity,包括发送和接收的数据以及当前的连接数,便于查看应用传输数据的方式和时间,并据此对底层代码进行适当优化。
4.1 Network Profiler 概览
如上图所示,窗口顶部显示的是 Event 时间线以及无线装置功耗状态(低/高)与 WLAN 的对比①。 在时间线上,可以点击并拖动选择时间线的一部分来检查网络流量②。 下方的窗口③会显示在时间线的选定片段内收发的文件,包括文件名称、大小、类型、状态和时间。 可以点击任意列标题排序。 同时,还可以查看时间线选定片段的明细数据,显示每个文件的发送和接收时间。
点击网络连接的名称即可查看有关所发送或接收的选定文件的详细信息④。 点击各个标签可查看响应数据、标题信息和调用堆栈。
注:Network Profiler 目前只支持 HttpURLConnection 和 OkHttp 网络连接库。