Android 内存泄漏工具 LeakCanary 使用介绍

1、内存泄露的概念

简单来说是:垃圾回收器无法回收原本应该被回收的对象,这个对象就引发了内存泄露。

具体来讲:在Java中对象都有一定的生命周期,当这些对象完成了它们的职责,它们原本应该被垃圾回收器回收,这样便可以将这些对象所占用的内存释放出来。当一个对象应该被回收的时候,程序中可能有一系列的引用嵌套,最终仍然保持了对该对象的引用,这个时候就引发了内存泄漏,当过多的内存泄露发生,程序就会出现Out Of Memory(OOM) Error,也就是内存溢出错误。

举例说明:
Android 四大组件之一的Activity生命周期中,它的onDestory方法被调用后,这个Activity和它所包含的View以及跟这个View所关联的BitMap或者其它被引用的对象都应该被垃圾回收器回收。如果Activity中有一个线程正在后台长时间运行,且线程保持了对这个Activity的引用,我们都知道,通常情况下旋转屏幕,系统会重新创建这个Activity,从新创建之前的那个Activity应该被垃圾回收,但是由于线程没有执行完,线程的生命周期没有结束,它还引用着之前那个Activity的实例,这个时候之前的那个Activity实例和它所包含的所有对象的内存都无法释放,我们说它发生了内存泄露。如果Activity中引用了一些比较耗内存的BitMap,多旋转几次屏幕就会出现Force Close(Out Of Memory Error).

2、内存泄露危害

  • 导致用户手机可用内存变少
  • 程序出现卡顿
  • 导致应用莫名退出(当内存不够用时,Android系统可能会杀掉该应用)
  • 影响用户体验,用户流失

3、内存泄露检测工具

  • MAT工具 (功能强大,界面友好;但是操作复杂,学习成本高,不适合入门级开发者)
  • YourKit工具 (商业软件)
  • LeakCanary工具 (功能强大,使用简单)

4、LeakCanary介绍

LeakCanary简介

LeakCanary是一个内存泄露检测工具。它能十分方便的检测出项目中的内存泄露,同时提供非常友好的通知提示,LOG信息详细。最主要的是它可以保存内存映像文件。官网:https://github.com/square/leakcanary

LeakCanary使用示例

参考LeakCanary官方示例 https://github.com/square/leakcanary/tree/master/leakcanary-sample 介绍如下:

为了集成LeakCanary,首先我们需要在应用的 build.gradle 中,添加依赖:

1
2
3
4
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
}

我们在dependencies下面添加了两个依赖:debugCompile、releaseCompile。它们有什么区别了?主要是为了在Debug版本和Release版本上面实现不同的行为,比如在Debug的版本上,我们可以提示LeakCanary的通知然后查看LeakCanary的Log信息,在Release版本上其实我们是不希望用户看到的,为了不修改代码,所以使用了这种方式。

LeakCanary依赖添加后,我们添加一个Application类

1
2
3
public class ExampleApplication extends Application {

}

在AndroidManifest.xml文件中去配置这个application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.leakcanary" >

<application
android:name=".ExampleApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

我们在application标签中,创建一个name,指向刚刚创建的ExampleApplication. android:name=".ExampleApplication"这样Application对象就配置好了。然后我们回到ExampleApplication类,重写onCreate方法,安装LeakCanary。

1
2
3
4
5
6
7
public class ExampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}

LeakCanary的配置就已经完成了,现在我们到MainActivity中,去实现一个内存泄露的代码,新创建一个方法startAsyncTask

1
2
3
4
5
6
7
8
9
10
11
12
void startAsyncTask() {
// 这个AsyncTask是一个匿名内部类,因此他隐式的持有一个外部类
// 的对象,也就是MainActivity。如果MainActivity在AsyncTask
// 执行完成前就销毁了,这个activity实例就发生了泄露。
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(20000); // 休眠20秒
return null;
}
}.execute();
}

这个AsyncTask是一个匿名内部类,因此他隐式的持有一个外部类的对象,也就是MainActivity。如果MainActivity在AsyncTask执行完成前就销毁了,这个activity实例就发生了泄露。

我们现在来测试一下这个startAsyncTask方法,我们在MainActivity的界面中添加一个Button:

1
2
3
4
5
6
7
8
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"
android:id="@+id/async_task"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true"
android:layout_marginTop="64dp" />

我们在MainActivity中获取这个Button,给这个Button设置一个Click事件,在这个Click事件中去调用startAsyncTask方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

View button = findViewById(R.id.async_task);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAsyncTask();
}
});
}

我们来测试一下:

点击Start按钮,然后旋转屏幕,等待一会我们会看到有notification出现MainActivity has leaked,点击进去查看详细信息。

image

LeakCanary的简单使用就介绍到这里,更多用法请查看官网 FAQ:https://github.com/square/leakcanary/wiki/FAQ。