码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2016-10-25 19:40:08      阅读:259      评论:0      收藏:0      [点我收藏+]

标签:nes   实例化   完整   sage   called   integer   引用   ref   排序   

使用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没释放?在此可根据源码简单分析一下:

//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 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处理
            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 <- Runtime

只要还有发出的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:
用途:携带要发送到Handler所在线程的任务信息。
直接被引用位置:MessageQueue

Handler:
用途:负责将任务信息包装成Message或直接将Message实例加入MessageQueue中(任意线程),同时也负责将Looper传递过来的Message在所在线程进行处理(目标线程)。
直接被引用位置:任何对象都可包含Handler,Handler通过引用存储在Thread上的Looper来建立绑定关系。

MessageQueue:
用途:MessageQueue引用有所有要发送的Message,持有一个Message实例引用,将每一个新加入的Message都插入单向链表中,引用方式不是通过建立一个ArrayList之类的结构来保存。
直接被引用位置:每个Looper实例化时都会初始化一个MessageQueue对象.

Looper:
用途:负责将加入MessageQueue的Message不断的交给Handler进行处理,完成消息处理。
直接被引用位置:Thread的ThreadLocal(线程局部变量),而不是作为Thread的一个成员变量。

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

Done.

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

标签:nes   实例化   完整   sage   called   integer   引用   ref   排序   

原文地址:http://blog.csdn.net/tp7309/article/details/52896643

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!