源码解读Handler误用导致的内存泄漏

在Android开发中,经常会有一些因为API使用不规范而导致的内存泄漏问题。这篇文章,将结合源代码,简单分析一下Handler的不规范使用导致的内存泄漏。

首先,什么是内存泄漏?

可以简单理解为:一个对象,我们已经不需要它了,但是由于“某种原因”,垃圾回收器无法回收它,这个对象所占用的内存就相当于被“泄漏”了,无法再供应用程序其他部分使用。如果应用程序中很多这样的对象,内存很快就会被耗尽,导致OOM(内存溢出)。

“某种原因”是什么?

Java是由GC(垃圾回收器)来自动管理内存,如果一个对象从根节点开始不可达(没有引用),它就会被GC回收。反之则不会被回收。这里的根节点通常是线程。“某种原因”就是,这个不在需要的对象,仍然有某条从根节点开始的“引用链”指向它。

怎么预防内存泄漏?

只要破坏这些“引用链”,让这个对象不可达即可。一方面,我们在编写代码时,不要引用不必要的对象,减小耦合度。另一方面,对象不再需要时,让该对象的引用指向null。

下面分析一个Handler的误用引起的内存泄漏案例.

经常能够看到下面这种代码:


image1

这么写会导致内存泄漏,旁边Lint已经提示说Handler类应该为static。问题来了,这里为什么会引起内存泄漏呢?

我们知道,非静态内部类的对象有一个隐含的指向外部对象的引用,静态内部类则没有。这里匿名内部类也属于非静态内部类,所以mHandler有引用HandlerLeakActivity.this。mHandler发送了一条延迟五分钟的消息,如果这期间HandlerLeakActivity.this被finish了,系统不会回收它,因为有一条mainThread -> looper -> messageQueue -> msg -> mHandler -> HandlerLeakActivity.this的引用链。

我们来看下这条引用链是怎么形成的。

1) mHandler -> HandlerLeakActivity.this
上面已经讲过,非静态内部类隐含的指向外部类的对象。

2) messageQueue -> msg -> mHandler

    mHandler.sendMessageDelayed(msg, 5 * 60 * 1000);

我们看上面这行代码的执行过程

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

继续进入sendMessageAtTime()

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

留意这里的mQueue,再继续enqueueMessage()

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

到这里我们看到msg.target = this, 这里this就是mHandler,即建立了msg -> mHandler,而msg又被放入mQueue,所以有messageQueue -> msg -> mHandler

  1. mainThread -> looper -> messageQueue

刚才提到留意mQueue,这个mQueue怎么来的呢?追溯mQueue可以找到下面的代码

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

在Handler的构造函数中,可以看到mQueue来自mLooper,而这个mLooper即是主线程的Looper。

到这里内存泄漏的原因已经看出来了,如果知道Handler的工作机制,原因还是比较好分析的。

知道了原因之后,问题好解决了,断掉mHandler -> HandlerLeakActivity.this这个引用就可以了,只要将Handler类声明为static的。然而,把Handler声明为static之后无法在内部调用doSomething()了。所以还是要有HandlerLeakActivity.this的引用,我们可以使用一种特殊的引用,它就是WeakReference。下面的代码就是Handler通常的使用方式:

public class HandlerLeakActivity extends AppCompatActivity {

    private final Handler mHandler = new MyHandler(this);

    private static class MyHandler extends Handler {
        //HandlerLeakActivity.this的弱引用
        private final WeakReference mActivity;

        private MyHandler(HandlerLeakActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mActivity.get().doSomething();
        }
    }

    private void doSomething() {
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //发送一条延迟消息
        Message msg = new Message();
        msg.what = 1;
        mHandler.sendMessageDelayed(msg, 5 * 60 * 1000);
    }
}

关于WeakReference,它的API文档如下:

Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.

Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues.

简单来说,WeakReference不会阻止GC对它所指向的对象的回收(当这个对象没有其他强引用和软引用时)。关于StrongReference和WeakReference区别,可以参考Java: difference between strong/soft/weak/phantom reference
(可能需要梯子)。

了解了垃圾回收的基本原理,我们就可以在开发中避免很多内存泄漏的出现。在编码时,尤其要在静态域(比如Context)和非静态内部类上多留心。最后,文章有错误或者表述不当的地方,请指出。

参考:

  1. Android 内存泄漏案例和解析
  2. Android App 内存泄露之Handler
  3. Java: difference between strong/soft/weak/phantom reference

你可能感兴趣的:(源码解读Handler误用导致的内存泄漏)