从使用Handler致内存泄漏角度源码追踪Handler工作机制

来源:互联网 发布:帖吧爆吧软件 编辑:程序博客网 时间:2024/05/16 23:41

使用Handler时内存泄漏分析

在Android中,处理完异步任务后常常会在主线程进行一些操作,所以我们可能会使用到Handler,下面是Handler的常见使用方法:

public class MainActivity extends AppCompatActivity {    private Handler mHanlder = new Handler() {        @Override        public void handleMessage(Message msg) {            //TODO        }    };}

但是我们这样使用时就会看到这样一句提示:

This Handler class should be static or leaks might occur

为什么会有这样的提示呢?
在Java中,匿名(非静态)内部类会隐性引用外部对象,而静态内部类则不会。
所以导致内存泄漏的原因是Activity被mHandler引用,而mHandler被其它比Activity生命周期更长的对象强引用。先上张Handler可以跨线程通信的简要说明图:

Handler工作原理图

这里写图片描述

一个线程只有一个Looper,一个Looper对应一个MessageQueue,但一个线程可对应多个Handler。
Handler可在任意线程创建,就以文章开头提到的最常见Handler创建方式为例:
mHandler = new Handler(){}是Activity的一个成员变量,Handler()无参构造函数相当于调用Handler(Looper.myLooper()),即将Handler与创建Activity所在的主线程的Looper绑定,与Handler(Looper.getMainLooper())是一样的效果,所以通过此Handler发送的消息可以会主线程进行处理,可以处理UI更新之类的消息。

源码分析

简单看了Handler工作原理图后,我们深入源码分析下为何Handler没释放?在此可根据源码简单分析一下:

//Activity中含有一个成员变量mHandler,mHandler是一个匿名内部类的实例,让我们看看Handler的构造函数中做了什么    public Handler() {        this(null, false);    }    //Handler默认构造函数最终会调到此方法    public Handler(Callback callback, boolean async) {        if (FIND_POTENTIAL_LEAKS) {            final Class<? extends Handler> 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());            }        }        //Handler中有一个mLooper        mLooper = Looper.myLooper();        if (mLooper == null) {            throw new RuntimeException(                "Can't create handler inside thread that has not called Looper.prepare()");        }        //消息队列mQueue        mQueue = mLooper.mQueue;        mCallback = callback;        mAsynchronous = async;    }

大家已经看到Handler有一个成员变量是mLooper,那么此Looper是如何实例化的呢?

    public static @Nullable Looper myLooper() {        return sThreadLocal.get();    }

通过上述代码可以看到,Looper实例原来与当前线程相关联,而创建Handler时调用 myLooper() 方法是在哪个线程?主线程。
所以说Handler实例中的成员变量mLooper与mQueue最起码在APP运行期间一直存在。那么这又与内存泄漏有什么关系呢? 毕竟是mHandler强引用mLooper,而不是mLooper强引用mHandler,为什么不能释放?
那么这就要说到我们使用Handler时必会做一事情,postMessage()
下面继续看下Handler中发送消息的源码:

    public final boolean post(Runnable r)    {       return  sendMessageDelayed(getPostMessage(r), 0);    }    private static Message getPostMessage(Runnable r) {        Message m = Message.obtain();        m.callback = r;        return m;    }    public static Message obtain(Handler h) {        Message m = obtain();        //Handler已被Message强引用!        m.target = h;        return m;    }    public final boolean sendMessageDelayed(Message msg, long delayMillis)    {        if (delayMillis < 0) {            delayMillis = 0;        }        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);    }    public final boolean sendMessageAtFrontOfQueue(Message msg) {        MessageQueue queue = mQueue;        if (queue == null) {            RuntimeException e = new RuntimeException(                this + " sendMessageAtTime() called with no mQueue");            Log.w("Looper", e.getMessage(), e);            return false;        }        //最终会调到mQueue的enqueueMessage()方法        return enqueueMessage(queue, msg, 0);    }    boolean enqueueMessage(Message msg, long when) {        //...        Message p = mMessages;        //...        //用单向链表把msg保存起来        Message prev;        for (;;) {            prev = p;            p = p.next;            //已到链表底部或此msg的执行时间小于p的执行时间则退出循环,之后便可将新msg插入到prev结点与p结点之间,            //保证MessageQueue中的Message按执行时间有序排序。其中when是(SystemClock.uptimeMillis() + delayMillis)            //而不是传入的delayMillis,这也是Handler可以保证让Message顺序执行的原因。            if (p == null || when < p.when) {                break;            }            if (needWake && p.isAsynchronous()) {                needWake = false;            }        }        msg.next = p; // invariant: p == prev.next        prev.next = msg;        //...    }

从上述代码可以看到,当我们用Handler post消息时,Message被保存到Looper.mQueue中,那么Message什么时候被销毁呢?让我们来看Looper如何对Message进行处理:

    //ActivityThread.java    public final class ActivityThread {        public static void main(String[] args) {            //...            Looper.loop();            //...        }    }    //Looper.java    public static void loop() {        //得到的是主线程的Looper        final Looper me = myLooper();        if (me == null) {            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");        }        final MessageQueue queue = me.mQueue;        //...        for (;;) {            //此处mQueue解除对msg的强引用,下面细分析            Message msg = queue.next(); // might block            if (msg == null) {                // No message indicates that the message queue is quitting.                return;            }            // This must be in a local variable, in case a UI event sets the logger            Printer logging = me.mLogging;            if (logging != null) {                logging.println(">>>>> Dispatching to " + msg.target + " " +                        msg.callback + ": " + msg.what);            }            //target即Handler, 在此处将msg交给Handler处理,所以一个MessageQueue中的每个Message都可对应不同的Handler。            msg.target.dispatchMessage(msg);            //...            msg.recycleUnchecked();        }    }//Message.java    void recycleUnchecked() {        // Mark the message as in use while it remains in the recycled object pool.        // Clear out all other details.        flags = FLAG_IN_USE;        what = 0;        arg1 = 0;        arg2 = 0;        obj = null;        replyTo = null;        sendingUid = -1;        when = 0;        //此处不在强引用Handler        target = null;        callback = null;        data = null;        synchronized (sPoolSync) {            if (sPoolSize < MAX_POOL_SIZE) {                //Message加入对象池等待复用                next = sPool;                sPool = this;                sPoolSize++;            }        }    }//MessageQueue.java    Message next() {        //...        int nextPollTimeoutMillis = 0;        for (;;) {            //不到执行时间阻塞,Message可以准时执行的原因            if (nextPollTimeoutMillis != 0) {                Binder.flushPendingCommands();            }            nativePollOnce(ptr, nextPollTimeoutMillis);            synchronized (this) {                // Try to retrieve the next message.  Return if found.                final long now = SystemClock.uptimeMillis();                Message prevMsg = null;                Message msg = mMessages;                if (msg != null && msg.target == null) {                    // Stalled by a barrier.  Find the next asynchronous message in the queue.                    do {                        prevMsg = msg;                        msg = msg.next;                    } while (msg != null && !msg.isAsynchronous());                }                if (msg != null) {                    if (now < msg.when) {                        //计算msg准时执行需要等待时间                        // Next message is not ready.  Set a timeout to wake up when it is ready.                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                    } else {                        // Got a message.                        mBlocked = false;                        //Message引用方式prevMsg->msg(prevMsg.next)->msg.next,通过此方法将msg从单向链表中移除,                        //MessageQueue对Message实例的引用已解除                        if (prevMsg != null) {                            prevMsg.next = msg.next;                        } else {                            mMessages = msg.next;                        }                        msg.next = null;                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);                        msg.markInUse();                        return msg;                    }                } else {                    // No more messages.                    nextPollTimeoutMillis = -1;                }            //...            // so go back and look again for a pending message without waiting.            nextPollTimeoutMillis = 0;        }    }

源码中重点位置已加注释,通过上述分析,我们终于可以得到一个完整的内存泄漏引用路径:
Activity <-Handler <- Message <-MessageQueue <-Looper <- MainThread

只要还有发出的Message未处理,Activity就不能释放,所以我们应该知道如何修改了:

  • 解除Activity与Handler之间的引用:
    private SafeHandler mHandler = new SafeHandler(this);    private static class SafeHandler extends Handler{        WeakReference<MainActivity> mActivityReference;        public SafeHandler(MainActivity activity){            mActivityReference = new WeakReference<MainActivity>(activity);        }        @Override        public void handleMessage(Message msg) {            MainActivity activity = mActivityReference.get();            if(activity != null){                activity.handleMessage(msg);            }        }    }    private void handleMessage(Message msg) {        //TODO    }
  • 解除Handler与Message间的引用
mHandler.removeCallbacksAndMessages(null);

Android消息处理机制

根据上述的源码分析我们也理清了Message、Handler、MessageQueue与Looper这四者之间的协作机制:

Message:
用途:携带要发送到Looper所在线程的任务信息。
直接被引用位置:MessageQueue

Handler:
用途:通过Looper绑定目标线程,负责在任意线程将Message实例加入目标线程的Looper的MessageQueue中,之后用于接收要在目标线程处理的Message信息。
直接被引用位置:任何对象都可包含Handler,Handler通过引用存储在Thread上的Looper来建立绑定关系。

MessageQueue:
用途:MessageQueue引用所有要发送的Message,持有一个对应的Handler实例引用,将每一个新加入的Message都插入单向链表中来保存。
直接被引用位置:每个Looper实例化时都会初始化一个MessageQueue对象.

Looper:
用途:负责接收Message将其加入MessageQueue,之后再派发出去交给每个Message对应的Handler进行处理。
直接被引用位置:Thread的ThreadLocal(线程局部变量)。

下面来个图比喻下(其实不太恬当,无实操>.>):
班组:Thread
煤块:Message
皮带:MessageQueue
班组煤块工人:Handler
皮带运输机:Looper
这里写图片描述
任意班组的煤块工人都可以扔煤块过来(任意Thread发Message),扔到皮带上皮带将其按一定次序整理好(MessageQueue为一Message队列),之后运输机带动皮带将其一个个处理(Looper.loop()),每个煤块上都有煤块工人的标记(Message引用Handler),皮带运输机根据标记将其运到各个煤块工人对应班组的煤块堆上(Looper将Message发到相应的Handler的handleMessage()处理),当然图上只有一个堆可以看成只有一个班组的煤块工人在扔。

Done.

2 0