(转载公司内部论坛本人文章2018.11.08)
一、概览
导语 : 2017年Google I/O大会上发布了Android Studio 3.0,其中一项更新是将之前内置的Android Monitor换成了Android Profiler。最新版Android Studio3.2已经移除了DDMS的入口了,感觉Google是想用Android Profiler完全取代DDMS。
在Google官网有几篇文章介绍了Android Profiler使用方法,这里我只是做了一下搬运工,结合自己的Demo得出的一些理解。如果有理解错漏的地方,欢迎指出。
个人觉得Android Profiler相比DDMS最大的优势在于它集成于Android Studio,为我们Android应用提供了 实时 检测工具,同时界面交互比较友好,能够快速简单的获取到我们想要的CPU,内存等信息。
Profiler入口:点击 ** View > Tool Windows > Android Profiler** ,或者点击Studio右下角的Profiler。
打开之后界面如下图,CPU,MEMORY(内存),NETWORK(网络),ENERGY(耗电,android studio3.2新增)四大部分。本文主要介绍 CPU 和 内存 部分。
二、CPU Profiler(CPU分析器)
CPU Profiler 可帮助我们实时检查应用的 CPU 使用率和线程活动,并进行函数跟踪,以便我们优化和调试应用代码。打开视图如下:
-
Event 时间线: 显示应用中Activity的生命周期,及用户与设备交互事件,比如按返回键,屏幕旋转等。
-
CPU 时间线: 分别显示了应用的实时 CPU 使用率,其他进程的CPU使用率,以及应用使用的线程数。
-
线程活动时间线: 列出进程的每个线程,并使用下面列出的颜色沿时间线标示它们的活动情况。
-
绿色: 表示线程处于活动状态或准备使用 CPU。 即,它正在“运行中”或处于“可运行”状态。
-
黄色: 表示线程处于活动状态,但它正在等待一个 I/O 操作(如磁盘或网络 I/O),然后才能完成它的工作。
-
灰色: 表示线程正在休眠且没有消耗任何 CPU 时间。 当线程需要访问尚不可用的资源时偶尔会发生这种情况。 线程进入自主休眠或内核将此线程置于休眠状态,直到所需的资源可用。
下面我用一个简单的Demo说明CPU Profiler的使用,先看测试代码:
public class ProfilerActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sendEmptyMessageDelayed(1, 500);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profiler);
mHandler.sendEmptyMessage(1);
}
}
代码比较简单,在Activity中有一个Handler,500ms循环一次,每次让线程sleep100ms,记录CPU使用情况,点击上面的 Record 按钮,大约3秒后Stop,会生成如下四个视图(Call Chart、Flame Chart、Top Down、Bottom Up)
1、Call Chart(调用图)
Call Chart图如上图。其实在真实项目中,生成的图比这个看起来复杂多了。但只要你弄清它们的关系,Call Chart展示的调用关系还是挺清晰的。
首先每个横条代表一个函数的运行时间,具体颜色区分如下:
-
橙色: 对系统 API 的函数调用
-
绿色 :对应用自有函数的调用
-
** 蓝色:** 对第三方 API(包括 Java 语言 API)的函数调用
Call Chart图可以简化为下面表示例:
图中看出,D函数的运行时间 Total time = Self Time + Children time (C和B的运行时间)
鼠标放在每个横条上会显示函数的运行时间,如图显示每次 handleMessage()方法大约执行了102ms,然后大概500ms循环一次。
点击右键还可以Jump to Source跳转到原代码处。
总结:Call Chart图显示了应用内所有函数的调用关系以及运行时间,方便开发者快速查看某个时间点应用内函数的调用和耗时情况。
2、Flame Chart(火焰图)
切换至Flame Chart。Flame Chart颜色没有特殊含义。看似是Call Chart的倒置,其实不完全是这样。Flame Chart会将调用顺序完全相同的函数归类成一个横条,怎么理解这个调用顺序相同呢?如下的调用图(Call Chart)
由于 B^1、B2^ 和 B^3^ 共享相同的调用顺序 (A → D → B),所以在火焰图(Flame Chart)中会合并成一个横条。同理,C^1^ 和 C^3^ 也会汇总在一起。所以Flame Chart图如下:
此时,X轴不在表示调用时间,而表示每个函数相对的运行总时间。
总结:Flame Chart更方便开发者查询哪些函数消耗的时间最多。火焰图如果看顶层的哪个函数占据的宽度比较大,就表示该函数可能存在性能问题。
3、Top Down
Top Down显示一个函数调用列表,展开后可以查看函数的被调用方。可以理解为,Flame chart 是Top down 的图形化表示形式。
4、Bottom Up
Bottom Up列出了所有执行函数的时间,点开后是它的调用方,按运行时间排序。
三、Memory Profiler(内存分析器)
Memory Profiler显示一个应用内存使用量的 实时图表 ,可以 捕获堆转储 、以及 跟踪内存分配 。
-
分别是强制垃圾回收按钮,Dump Java Heap(捕获堆转储)按钮,如果是Android7.1及以下系统,旁边会Record按钮。
-
实时显示了每个内存类别使用了多少内存,虚线表示分配的对象数,以及垃圾回收Event图标。
内存计数的类别如下:
-
Total: 应用占用内存总量
-
Java: 从 Java 或 Kotlin 代码分配的对象内存。
-
Native: 从 C 或 C++ 代码分配的对象内存。即使您的应用中不使用 C++,您也可能会看到此处使用的一些原生内存,因为 Android 框架使用原生内存代表您处理各种任务,如处理图像资源和其他图形时,即使您编写的代码采用 Java 或 Kotlin 语言。
-
Graphics: 图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。 (请注意,这是与 CPU 共享的内存,不是 GPU 专用内存。)
-
Stack: 您的应用中的原生堆栈和 Java 堆栈使用的内存。 这通常与您的应用运行多少线程有关。
-
Code: 您的应用用于处理代码和资源(如 dex 字节码、已优化或已编译的 dex 码、.so 库和字体)的内存。
-
Other: 您的应用使用的系统不确定如何分类的内存。
-
Allocated: 您的应用分配的 Java/Kotlin 对象数。 它没有计入 C 或 C++ 中分配的对象。
1.Dump Java Heap(捕获堆转储)
点击上面的Dump Java Heap 按钮,过会会倒出你应用java堆中所有的类型
图中可以看到应用中自己的Activity,右边数字栏所表示的意义如下:
-
Allocations: 该类在堆中已分配实例数
-
Native Size: 由 C 或 C++ 代码分配的实例总大小
-
Shallow Size :此堆中所有实例的总大小(以字节为单位)。
-
Retained Size : 为此类的所有实例而保留的内存总大小(以字节为单位)。
图中可以看到ProfilerActivity实例数是3,为什么会是3呢?可以看到demo代码中,我用非静态Handler实现了一个定时逻辑,非静态内部类默认持有外部类引用,所以造成了内存泄漏。ProfilerActivity启动了3次,所以就会有三个。点击右边会显示Instance View图。这就是堆中那三个ProfilerActiity,展开还可以看到实例内部的组成。
点击单个实例,下方会显示该实例的References(引用图),显示该实例的所有引用。可以看到第一个引用就是那个Handler,点击右键Jump To Source果然跳转到Hander初始化代码处。
由于我这个是demo代码,页面代码比较简单,所以很容易就能找出自己代码中出现的泄漏问题。但是实际项目中查找起来会复杂很多。这里有一个小技巧,在Heap图中可以通过切换按包名排序(Arrange by package),通过包名,我们能更方便的查找到我们所关心的类的内存占用情况。
2、跟踪内存分配
如果你的设备是Android 8.0及以上的,可以随时在内存图上拖动鼠标,选取你想要查看内存分配的时间段。如果你的设备是 Android 7.1 或更低版本,则在 Memory Profiler 工具栏中点击 Record按钮 。 记录时,Android Monitor 将跟踪您的应用中进行的所有分配。
选取时间段后会生成Live Allocation图,右边数字栏所表示的意义如下(在你所选取的时间段内):
-
Allocations: 表示你所选取时间段内,该类在堆中已分配实例数
-
Deallocations: 表示你所选取时间段内,该对象在内存中被回收的次数。
-
Total Count : 总数。
-
Shallow Size : 所有实例的总大小(以字节为单位)
点击查看Instance View图,可以看到 User对象 和 ItemsActivity 的创建时间和回收时间基本是一致的。并且在Allocation Call Stack中还可以看到User对象是在ItemsActivity的o nCreate() 方法里被创建的。
public class ItemsActivity extends AppCompatActivity {
private User mUser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profiler);
mUser = new User();
}
}
总结:Memory Profiler可以让我们很方便的查看我们应用的内存占用情况,并且可以很方便实现对单对象的内存追踪和检测内存泄漏等
以上是我对Android Profiler使用方法的一些介绍,其中的错漏,欢迎指出,大家一起探讨。
参考资料:
https://developer.android.google.cn/studio/profile/
END