目录

一 简介

二 使用

三 进阶用法

四 hprof分析复杂内存泄露问题

五 使用小结

六 使用踩坑

6.1 权限

6.2  NullPointerException

七 原理简单介绍

7.1 触发检测

7.2 判断是否存在内存泄漏

7.3 分析内存泄漏

八 总结


参考源码地址:​​https://github.com/LucasXu01/Autils​

一 简介

使用MAT来分析内存问题,有一些门槛,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比才能找到问题原因。 为了能够简单迅速的发现内存泄漏,Square公司基于MAT开源了LeakCanary ;

github地址:​​https://github.com/square/leakcanary​

官方文档:​​https://square.github.io/leakcanary/​


二 使用

在app build.gradle 中加入引用:

dependencies {
//内存泄露
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'

}

在 Application 中:

public class MyApplication extends Application {
@Override public void onCreate() {
super.onCreate();

/** 基础使用 */
if (LeakCanary.isInAnalyzerProcess(this)) {//1
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);


}
}

如果当前的进程是用来给LeakCanary 进行堆分析的则return,否则会执行LeakCanary的install方法。这样我们就可以使用LeakCanary了,如果检测到某个Activity 有内存泄露,LeakCanary 就会给出提示。

Android:性能优化工具之内存泄露-LeakCanary_内存泄露

 


三 进阶用法

例子代码只能够检测Activity的内存泄漏,当然还存在其他类的内存泄漏,这时我们就需要使用RefWatcher来进行监控。

改写Application,如下所示:

public class MyApplication extends Application {
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher= setupLeakCanary();
}

private RefWatcher setupLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
}
return LeakCanary.install(this);
}

public static RefWatcher getRefWatcher(Context context) {
MyApplication leakApplication = (MyApplication) context.getApplicationContext();
return leakApplication.refWatcher;
}
}

install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。

最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。

泄露所在的活动:

/**
* 内存泄露
* @author lucas
* created at 2019/9/22 2:53 PM
*/
public class LeakActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak_test);

LeakTestManager manager = LeakTestManager.getInstance(LeakActivity.this);
}

外部引用:

public class LeakTestManager {

private static LeakTestManager manager;
private Context context;

private LeakTestManager(Context context) {
this.context = context;
}

/**
* 如果传入的context是activity/service的上下文,会导致内存泄漏
* 原因是我们的manger是一个static的静态对象,这个对象的生命周期和整个app的生命周期一样长
* 当activity销毁的时候,我们的这个manger仍然持有者这个activity的context,就会导致activity对象无法被释放回收,就导致了内存泄漏
*/

public static LeakTestManager getInstance(Context context) {
if (manager == null) {
manager = new LeakTestManager(context);
}
return manager;
}

//正确写法
public static LeakTestManager getInstanceSafe(Context context) {
if (manager == null) {
manager = new LeakTestManager(context.getApplicationContext());
}
return manager;
}

}

LeakActivity存在内存泄漏,原因就是非静态内部类LeakTestManager持有外部类LeakActivity的引用,LeakTestmanger是一个static的静态对象,这个对象的生命周期和整个app的生命周期一样长,当activity销毁的时候,我们的这个manger仍然持有者这个activity的context,就会导致activity对象无法被释放回收,就导致了内存泄漏。

***LeakCanary会自动监控Activity执行onDestroy方法之后是否发生内存泄露,当前此例onDestroy加是多余的,这里只是为了方便举例,如果想要监控Fragment,则必须在Fragment中添加如上的onDestroy方法!

运行程序,打开泄露的活动界面,需要稍等片刻,打开LeakCanary内存泄漏信息就会展示出来(原生安卓会直接Notification,深度定制过的类似小米不会弹出弹窗,因不同版本而异):

Android:性能优化工具之内存泄露-LeakCanary_性能优化_02

 

Android:性能优化工具之内存泄露-LeakCanary_内存泄露_03


四 hprof分析复杂内存泄露问题

上面内存泄漏的例子比较简单,可以很明显的看出泄漏的原因,有时候我们会遇到比较复杂的内存泄漏情况,这个时候我们可能需要分析一下hprof文件。
打开Device File explorer,找到安卓中的Download文件夹:

Android:性能优化工具之内存泄露-LeakCanary_内存泄露_04

双击hprof文件,再进行分析:

Android:性能优化工具之内存泄露-LeakCanary_安卓_05

hprof文件里信息很多,其中常用字段的含义:

名称

描述

Class name

类名

Total Count

该类的实例总数

Heap Count

所选择的堆中该类的实例的数量

Sizeof

单个实例所占空间大小(如果每个实例所占空间大小不一样则显示0)

Shallow Size

堆里所有实例大小总和(Heap Count * Sizeof)

Retained Size

该类所有实例所支配的内存大小

Instance

具体的实例

Reference Tree

所选实例的引用,以及指向该引用的引用。

Depth

GC根节点到所选实例的最短路径的深度

Shallow Size

所选实例的大小

Dominating Size

所选实例所支配的内存大小


五 使用小结

如果只关注activity的内存泄漏,那么在Application中onCreate加入LeakCanary.install(this);就OK了,

如果还关注fragment的泄漏情况,那么Application加上RefWatcher,然后在对应fragment页面中onDestroy中加入:

RefWatcher refWatcher = MyApplication.getRefWatcher(this);
refWatcher.watch(this);


六 使用踩坑

在刚开始使用LeakCanary的时候,遇到了几个问题导致有内存泄漏发生时LeakCanary不发生通知,这里和大家分享一下。

6.1 权限

你的应用需要有写SD权限,因为LeakCanary需要生成hprof文件,保存在SD卡里面,因此你的应用要先申请权限。但在本文中的1.6.3版本中,你只需要在manifest清单中添加下面两条权限即可,需要时LeakCanary会自动申请零时零时权限,不需要自己再在代码中手动控制了。

<!– SDCard中创建与删除文件权限 –>
<uses-permission android:name=“android.permission.MOUNT_UNMOUNT_FILESYSTEMS”/>

<!– 向SDCard写入数据权限 –>
<uses-permission android:name=“android.permission.WRITE_EXTERNAL_STORAGE”/>

6.2  NullPointerException

java.lang.NullPointerException: Attempt to invoke virtual method’boolean java.lang.String.equals(java.lang.Object)’ on a null object reference

atcom.squareup.leakcanary.HeapAnalyzer.findLeakingReference(HeapAnalyzer.java:160)

….

如果遇到这个问题,是LeakCanary的版本过低了,不适合Android6.0及以上的机型,我看网上大部分引用的还是基于1.3的版本,升级到高版本的就没问题了。


七 原理简单介绍

7.1 触发检测

每次当Activity/Fragment执行完onDestroy生命周期,LeakCanary就会获取到这个Activity/Fragment,然后初始化RefWatcher对它进行分析,查看是否存在内存泄漏。

7.2 判断是否存在内存泄漏

首先尝试着从ReferenceQueue队列中获取待分析对象(软引用和弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用或弱引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用或弱引用加入到与之关联的引用队列中),如果不为空,那么说明正在被系统回收,如果直接就返回DONE,说明已经被系统回收了,如果没有被系统回收,可能存在内存泄漏,手动触发系统GC,然后再尝试移除待分析对象,如果还存在,说明存在内存泄漏。

没有引用了,就去ReferenceQueue中找,没有就是泄露了。

7.3 分析内存泄漏

确定有内存泄漏后,调用heapDumper.dumpHeap()生成.hprof文件目录。HAHA 是一个由 square 开源的 Android 堆分析库,分析 hprof 文件生成Snapshot对象。Snapshot用以查询对象的最短引用链。找到最短引用链后,定位问题,排查代码将会事半功倍。

Android:性能优化工具之内存泄露-LeakCanary_安卓_06

整体流程如下:

Android:性能优化工具之内存泄露-LeakCanary_性能优化_07

详细原理分析可以参考:LeakCanary原理分析


八 总结

LeakCanary对于内存泄漏的检测非常有效,但也并不是所有的内存泄漏都能检测出来。

6.1 无法检测出Service中的内存泄漏问题

6.2 如果最底层的MainActivity一直未走onDestroy生命周期(它在Activity栈的最底层),无法检测出它的调用栈的内存泄漏。

所以说LeakCanary针对Activity/Fragment的内存泄漏检测非常好用,但是对于以上检测不到的情况,还得配合Android Monitor + MAT 来分析。

 参考:


性能优化工具(九)-LeakCanary : https://www.jianshu.com/p/70b8c87ea877

Android内存优化(六)LeakCanary使用详解:http://liuwangshu.cn/application/performance/ram-6-leakcanary.html

「Leakcanary 源码分析」看这一篇就够了:https://www.jianshu.com/p/9cc0db9f7c52