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
4dependencies {
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
3public class ExampleApplication extends Application {
}
在AndroidManifest.xml文件中去配置这个application1
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
7public class ExampleApplication extends Application {
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
LeakCanary的配置就已经完成了,现在我们到MainActivity中,去实现一个内存泄露的代码,新创建一个方法startAsyncTask
1 | void startAsyncTask() { |
这个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
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() {
public void onClick(View v) {
startAsyncTask();
}
});
}
我们来测试一下:
点击Start按钮,然后旋转屏幕,等待一会我们会看到有notification出现MainActivity has leaked
,点击进去查看详细信息。
LeakCanary的简单使用就介绍到这里,更多用法请查看官网 FAQ:https://github.com/square/leakcanary/wiki/FAQ。