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

Posted 亦游

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从使用Handler致内存泄漏角度源码追踪Handler工作机制相关的知识,希望对你有一定的参考价值。

使用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都可以通过Handler可以向这个线程发Message),扔到皮带上皮带将其按一定次序整理好(将Message加入MessageQueue),之后运输机带动皮带将其一个个处理(Looper.loop()),每个煤块上都有煤块工人的标记(Message引用Handler),皮带运输机根据标记将其运到各个煤块工人对应班组的煤块堆上(Looper将Message发到相应的Handler的handleMessage()处理)。当然图上只有一个堆可以看成只有一个班组的煤块工人在扔。

Done.

以上是关于从使用Handler致内存泄漏角度源码追踪Handler工作机制的主要内容,如果未能解决你的问题,请参考以下文章

源码分析|Handler内存泄漏分析及解决

Handler系列之内存泄漏

标题 带你从源码的角度去理解Handler

它会在快速的类方法中导致内存泄漏吗

Android Handler 避免内存泄漏的用法总结

Android Handler 避免内存泄漏的用法总结