Android内存泄露监测工具之leakcanary

来源:互联网 发布:造价软件破解版 编辑:程序博客网 时间:2024/05/24 03:08

1 什么是内存泄露

Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。

简单来说就是该被回收的由于某种原因没有被回收。

2 系统分配的应用内存大小

通过ActivityManager可以获取系统为我们分配的内存大小
ActivityManager的getMemoryClass()获得内用正常情况下内存的大小
ActivityManager的getLargeMemoryClass()可以获得开启largeHeap最大的内存大小

ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); activityManager.getMemoryClass(); activityManager.getLargeMemoryClass();

需要指出的是这里获取的内存大小是JVM为进程分配的内存大小,而当我们的应用中存在多个进程的时候,该应用理论上的
应用内存 = 进程内存大小 * 进程个数
应用需要较大内存的时候也可以考虑通过多进程的方式进而获取更多的系统内存。

这样获取到的应用内存大小就是应用所能获取到的最大内存大小,当应用需要更多内存以支持其运行的时候,系统无法为其分配更多的内存,这样就造成了OOM的异常。

3 内存泄露的常见场景

  • 非静态内部类,静态实例化
/** * 自定义实现的Activity */public class MyActivity extends AppCompatActivity {    /**     * 静态成员变量     */    public static InnerClass innerClass = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_my);        innerClass = new InnerClass();    }    class InnerClass {        public void doSomeThing() {        }    }}

这里内部类InnerClass隐式的持有外部类MyActivity的引用,而在MyActivity的onCreate方法中调用了

innerClass = new InnerClass();

这样innerClass就会在MyActivity创建的时候是有了他的引用,而innerClass是静态类型的不会被垃圾回收,MyActivity在执行onDestory方法的时候由于被innerClass持有了引用而无法被回收,所以这样MyActivity就总是被innerClass持有而无法回收造成内存泄露。

  • 不正确的使用Context对象造成内存泄露
/** * 自定义单例对象 */public class Single {    private static Single instance;    private Context context;    private Object obj = new Object();    private Single(Context context) {        this.context = context;    }    /**     * 初始化获取单例对象     */    public static Single getInstance(Context context) {        if (instance == null) {            synchronized(obj) {                if (instance == null) {                    instance = new Single(context);                }            }        }        return instance;    }}

我们通过DCL创建单例对象,并且在创建的时候需要传入一个Context对象,而这时候如果我们使用Activity、Service等Context对象,由于单例对象的生命周期与进程的生命周期相同,会造成我们传入的Activity、Service对象无法被回收,这时候就需要我们传入Application对象,或者在方法中使用Application对象,上面的代码可以改成:

/** * 自定义单例对象 */public class Single {    private static Single instance;    private Context context;    private Object obj = new Object();    private Single(Context context) {        this.context = context;    }    /**     * 初始化获取单例对象     */    public static Single getInstance(Context context) {        if (instance == null) {            synchronized(obj) {                if (instance == null) {                    instance = new Single(context.getApplication());                }            }        }        return instance;    }}

这样就不会有内存泄露的问题了。

  • 使用Handler异步消息通信
    在日常开发中我们通常都是这样定义Handler对象:
/** * 定义Handler成员变量 */Handler handler = new Handler() {          @Override          public void handleMessage(Message msg) {              dosomething();          }      };  

但是这样也存在着一个隐藏的问题:在Activity中使用Handler创建匿名内部类会隐式的持有外部Activity对象的引用,当子线程使用Handler暂时无法完成异步任务时,handler对象无法销毁,同时由于隐式的持有activity对象的引用,造成activity对象以及相关的组件与资源文件同样无法销毁,造成内存泄露。
好吧,那么如何解决这个问题呢?具体可以参考:Android中使用Handler造成内存泄露的分析和解决

  • 使用了属性动画或循环动画
    在Activity中使用了属性循环动画,在onDestroy()方法中未正确停止动画

  • 使用资源文件结束之后未关闭

在使用一些资源性对象比如(Cursor,File,Stream,ContentProvider等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于Java虚拟机内,还存在于Java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。

因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该立即调用它的close()函数,将其关闭掉,然后再置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。

/** * 初始化Cursor对象 */Cursor cursor = getContentResolver().query(uri...); if (cursor.moveToNext()) {     /**     * 执行自设你的业务代码     */      doSomeThing();}

这时候我们应当在doSomeThing之后执行cursor的close方法,关闭资源对象。

/** * 初始化Cursor对象 */Cursor cursor = getContentResolver().query(uri...); if (cursor.moveToNext()) {     /**     * 执行自设你的业务代码     */      doSomeThing();}if (cursor != null) {    cursor.close();}
  • Bitmap使用不当

bitmap对象使用的内存较大,当我们不再使用Bitmap对象的时候一定要执行recycler方法,这里需要指出的是当我们在代码中执行recycler方法,Bitmap并不会被立即释放掉,其只是通知虚拟机该Bitmap可以被recycler了。

当然了现在项目中使用的一些图片库已经帮我们对图片资源做了很好的优化缓存工作,是我们省去了这些操作。

  • 一些框架使用了注册方法而未反注册

比如我们时常使用的事件总线框架-EventBus,当我们需要注册某个Activity时需要在onCreate中:

EventBus.getDefault().register(this);

然后这样之后就没有其他操作的话就会出现内存泄露的情况,因为EventBus对象会是有该Activity的引用,即使执行了改Activity的onDestory方法,由于被EventBus隐式的持有了该对象的引用,造成其无法被回收,这时候我们需要在onDestory方法中执行:

EventBus.getDefault().unregister(this);
  • 集合中的一些方法的错误使用

(1)比如List列表静态化,只是添加元素而不再使用时不清楚元素;
(2)map对象只是put,而无remove操作等等;

使用leakcanary可以快速方便的定位到内存泄漏的代码处,使用起来很方便的

4 什么是leakcanary

LeakCanary 是一个square开源的在debug版本中检测内存泄漏的java库;
其github地址:https://github.com/square/leakcanary

5 如何使用leakcanary检测内存泄露

在leakcanary的github地址中已经对如何使用做了相关的说明,这里简单介绍一下:

  • 1)在Android studio的build.gradle(Module:app)中引用leakcanary
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
  • 2)自定义Application,并在onCreate方法中执行以下代码:
public class ExampleApplication extends Application {  @Override public void onCreate() {    super.onCreate();    if (LeakCanary.isInAnalyzerProcess(this)) {      // This process is dedicated to LeakCanary for heap analysis.      // You should not init your app in this process.      return;    }    LeakCanary.install(this);    // Normal app init code...  }}
  • 3)当发现有内存泄漏时,Leakcanary就会弹出一个通知栏消息

当发现有内存泄漏时,Leakcanary就会弹出一个通知栏消息告诉你哪里存在内存泄露的情况。
这里写图片描述

点击该通知栏消息,显示内存泄露详情;
这里写图片描述
可以看到LoginPhoneActivity中存在内存泄露的情况,继续往下看,可以定位到Config.currentContext这里,下面就是具体问题具体分析了;

0 0