为什么Handler容易引起内存泄露

首先还是先了解一下什么叫内存泄露

####内存泄漏

Java 使用有向图机制,通过GC自动检查内存中的对象(什么时候检查交给虚拟机决定),如果GC发现一个或一组对象为不可达状态,则将该对象从内存中回收。

也就是说,一个对象不被任何引用所指向,则该对象会被在GC发现的时候被回收;

还有一种情况是,如果一组对象中只包含互相的应用,而没有来自他们外部的应用,仍然属于不可达的范围,同样会被GC回收。

可以总结为两个词:

  • 不可达
  • 无根

就会被GC回收。

内存泄漏会发生什么?

Android中经常出现的一种现象:内存占用越来越大,App越用越卡,只有在强制关闭程序后才会有好转的迹象,这个就属于内存泄漏。而导致内存越来越大的原因有很多,其中最主要的原因之一,就是内存泄漏

Handler 是怎么把内存泄漏的?

Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        iv_test.setImageBitmap(mBitmap);
    }
}

这是一段很简单的 handler 。但使用内部类(包括匿名类)来创建Handler 的时候,Handler对象会*隐式*地持有一个外部类对象的引用(一般是一个 Activity)。

而Handler的主要使用场景是处理一个耗时的任务,等待任务处理完成之后,通过Handler 的消息机制通知到 Activity,然后完成更新界面的工作。

但是,移动端的交互方式决定了何时关闭Activity是由用户决定的,如果在耗时任务完成之前,Activity就被关闭了。正常情况下,由于用户关闭了Activity,所以Activity申请的相关资源内存,应该要被释放掉。

但是,之前的 【学习笔记】Looper 与 Handler 的关系 学习到了一点,Handler在发送消息的时候,会把Message 排队到 一个 消息队列里面 (MessageQueue),等到任务完成后,再从消息队列里面取出来,通知handler,这里的关系是 引用持有关系可以表现为 MessageQueue -> Message -> Handler -> Activity,但任务没有完成的情况下,Activity理应被回收的业务逻辑下,由于Activity的引用被持有,导致GC不会去进行回收操作,这个就形成了内存泄漏。

内存泄漏有什么危害

导致虚拟机占用内存过高,导致OOM,程序崩溃。对于Android来说,目前这种Android手机越用越卡的第一印象很大程度上与应用的质量有很大的关系,应用没有很好地解决性能问题(内存泄漏只是其中一种情况)。用户打开一个Activity,关闭的时候并没有把之前向系统申请的内存回收掉,反复操作几次之后,就会出现内存超过系统限制的问题而被强制关闭,用户体验就不好了。

Handler导致内存泄漏的解决办法

  • 通过程序逻辑进行保护
  1. 在Activity被关闭的时候,进行耗时任务的停止工作,任务被停止了,Activity也就没有被引用了。自然也就回收了。
  2. 在Activity被关闭的时候,在消息队列里面把Message消息(包含 Handler引用)remove掉,使用 handler.removeCallback() 方法,同样达到去除引用的效果。

总的来说就是 在Activity关闭的时候做好 耗时任务的处理,不管是停止任务,还是removeCallback ,这个习惯总是好的。

  • 针对使用内部类的解决办法

静态类不持有外部类的对象,所以Activity可以被随时回收。

static class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        iv_test.setImageBitmap(mBitmap);
    }
}

由于Handler 不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以使用一个Activity的弱引用(WeakReference)方式解决。

static class MyHandler extends Handler {
    WeakReference<Activity > mActivityReference;

    MyHandler(Activity activity) {
        mActivityReference= new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            iv_test.setImageBitmap(mBitmap);
        }
    }
}

到这里就完成。

延伸

什么叫 弱引用