目录
一 简介
二 使用
三 进阶用法
四 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 就会给出提示。
三 进阶用法
例子代码只能够检测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,深度定制过的类似小米不会弹出弹窗,因不同版本而异):
四 hprof分析复杂内存泄露问题
上面内存泄漏的例子比较简单,可以很明显的看出泄漏的原因,有时候我们会遇到比较复杂的内存泄漏情况,这个时候我们可能需要分析一下hprof文件。
打开Device File explorer,找到安卓中的Download文件夹:
双击hprof文件,再进行分析:
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用以查询对象的最短引用链。找到最短引用链后,定位问题,排查代码将会事半功倍。
整体流程如下:
详细原理分析可以参考:LeakCanary原理分析
八 总结
LeakCanary对于内存泄漏的检测非常有效,但也并不是所有的内存泄漏都能检测出来。
6.1 无法检测出Service中的内存泄漏问题
6.2 如果最底层的MainActivity一直未走onDestroy生命周期(它在Activity栈的最底层),无法检测出它的调用栈的内存泄漏。
所以说LeakCanary针对Activity/Fragment的内存泄漏检测非常好用,但是对于以上检测不到的情况,还得配合Android Monitor + MAT 来分析。