Android Handler 导致内存泄漏原因及解决方案

一、什么是内存泄漏?

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

虽然 Android 的 Dalvik 虚拟机扮演了常规的垃圾回收角色,Dalvik 的 GC 会自动把离开活动线程的对象进行回收,但是 App 的内存分配与释放的时机和地点依然不可忽视。

二、为什麽Handler会导致内存泄漏?

先来看看下面这个代码

Android Handler 导致内存泄漏原因及解决方案_第1张图片

一个极其普通的Handler定义,Android Lint却给出了警告:

This Handler class should be static or leaks might occur
这个Handler类应该被定义为静态,否则可能会导致内存泄漏。

Google的工程师Romain Guy已经在论坛中做出过解释:

I wrote that debugging code because of a couple of memory leaks I found in the Android codebase. Like you said, a Message has a reference to the Handler which, when it's inner and non-static, has a reference to the outer this (an Activity for instance.) If the Message lives in the queue for a long time, which happens fairly easily when posting a delayed message for instance, you keep a reference to the Activity and "leak" all the views and resources. It gets even worse when you obtain a Message and don't post it right away but keep it somewhere (for instance in a static structure) for later use.

大致意思是

我在 Android 代码库中发现了一些内存泄漏,为此我写了调试代码进行测试,像你说的一样,Message 会持有一个对 Handler 的引用,当这个 Handler 是非静态内部类的时候,又会持有一个对外部类的引用(比如Activity)。如果发送一条延时的 Message,由于这个 Message 会长期存在于队列中,就会导致 Handler 长期持有对 Activity 的引用,从而引起视图和资源泄漏。当你发送一条延时的 Mesaage,并且把这个 Message 保存在某些地方(例如静态结构中)备用,内存泄漏会变得更加严重。

下面我们来具体分析一下导致该问题的原因

  1. 当 Android 应用首次启动时,framework 会在应用的 UI 线程创建一个 Looper 对象。Looper 实现了一个简单的消息队列并且一个接一个的处理队列中的消息。应用的所有事件(比如 Activity 生命周期回调方法,按钮点击等等)都会被当做一个消息对象放入到 Looper 的消息队列中,然后再被逐一执行。UI 线程的 Looper 存在于整个应用的生命周期内。

  2. 当在 UI 线程中创建 Handler 对象时,它就会和 UI 线程中 Looper 的消息队列进行关联。发送到这个消息队列中的消息会持有这个 Handler 的引用,这样当 Looper 最终处理这个消息的时候 framework 就会调用 Handler 的 handleMessage(Message) 方法来处理具体的逻辑。

  3. 在 Java 中,非静态的内部类或者匿名内部类会隐式的持有其外部类的引用,而静态的内部类则不会。

举个例子会更好理解

    public class SampleActivity extends Activity {

        private final Handler mLeakyHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // ...
            }
        };

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            // Post a message and delay its execution for 10 minutes.
            mLeakyHandler.postDelayed(new Runnable() {
                @Override
                public void run() { /* ... */ }
            }, 1000 * 60 * 10);

            // Go back to the previous Activity.
            finish();
        }
    }

当 activity 被 finish 的时候,延迟发送的消息仍然会存活在 UI 线程的消息队列中,直到 10 分钟后它才被处理掉。这个消息持有 activity 的 Handler 的引用,Handler 又隐式的持有它的外部类(这里就是 SampleActivity)的引用。这个引用会一直存在直到这个消息被处理,所以垃圾回收机制就没法回收这个 activity,内存泄露就发生了。非静态的匿名类会隐式的持有外部类的引用,所以 context 会被泄露掉。需要注意的是:代码里匿名的 Runnable 子类也会导致内存泄露。

三、解决方案

在新的类文件中实现 Handler 的子类或者使用 static 修饰内部类。静态的内部类不会持有外部类的引用,所以 activity 不会被泄露。如果你要在 Handler 内调用外部 activity 类的方法的话,可以让 Handler 持有外部 activity 类的弱引用,这样也不会有泄露 activity 的风险。关于匿名类造成的泄露问题,我们可以用 static 修饰这个匿名类对象解决这个问题,因为静态的匿名类也不会持有它外部类的引用。

Romain Guy 给出了他的建议写法:

class InnerClass {
  
    private final WeakReference mTarget;
                                 
    InnerClass(OuterClass target) {
           mTarget = new WeakReference(target);
    }
                                 
    void doSomething() {
           OuterClass target = mTarget.get();
           if (target != null) {
                target.do();    
           }
     }
}

在内部类的构造方法中,创建一个对外部类的弱引用,然后在内部类的方法中通过弱引用获取外部类对象,进行非空判断后再进行操作。

以下是修改后的代码:

    public class SampleActivity extends Activity {

        /**
         * Instances of static inner classes do not hold an implicit
         * reference to their outer class.
         */
        private static class MyHandler extends Handler {
            private final WeakReference mActivity;

            public MyHandler(SampleActivity activity) {
                mActivity = new WeakReference(activity);
            }

            @Override
            public void handleMessage(Message msg) {
                SampleActivity activity = mActivity.get();
                if (activity != null) {
                    // ...
                }
            }
        }

        private final MyHandler mHandler = new MyHandler(this);

        /**
         * Instances of anonymous classes do not hold an implicit
         * reference to their outer class when they are "static".
         */
        private static final Runnable sRunnable = new Runnable() {
            @Override
            public void run() { /* ... */ }
        };

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            // Post a message and delay its execution for 10 minutes.
            mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

            // Go back to the previous Activity.
            finish();
        }
    }

静态和非静态内部类的区别是非常微妙的,但这个区别是每个 Android 开发者应该清楚的。如果要实例化一个超出 activity 生命周期的内部类对象,应该避免使用非静态的内部类。建议使用静态内部类并且在内部类中持有外部类的弱引用。

由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。因此,如果在关闭 Activity 之后,不需要对消息队列中的消息进行处理了,可以在 onDestory() 方法中加入下面这段代码:

// 移除所有消息
handler.removeCallbacksAndMessages(null);

// 移除单条消息
handler.removeMessages(what);

四、什么是 WeakReference(弱引用)?

弱引用可以使垃圾回收器回收内存,而不像强引用。如果引用被判定为只有弱引用可达,那么会被回收器回收。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

你可能感兴趣的:(Android Handler 导致内存泄漏原因及解决方案)