Android中handler消息机制中的一些细节问题

本文深入探讨了Android中Handler机制的实现原理,特别是消息的延迟发送机制及消息队列内部结构。同时,详细解释了ThreadLocal的工作原理及其在Looper中的应用,包括如何利用ThreadLocal实现每个线程拥有独立的Looper实例。
摘要由CSDN通过智能技术生成

  本文的编写是以博主前三篇博客为基础的,如果对handler详细机制有不理解的同学可以去参考下我的前三篇博客。

  1.在handler中,为什么能实现消息的延迟发送?

   是因为在enqueueMessage方法中消息是按照其发送时间进行排列的,对应的代码为:

boolean enqueueMessage(Message msg, long when) 
{
  .......
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
  ........
}
 2.ThreadLocal的get()和set()方法是如何实现的?而且ThreadLocal最神奇的地方是所有的线程都是用的同一个ThreadLocal,换句话说,就是所有的线程都能通过同一个ThreadLocal来获得不同的looper值,是如何实现的呢?在looper的prepare中有这样一段代码:
if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
  再来看get和set方法的代码片段:
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }


  public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
    Set中由于value是泛型,因此在存储的时候是依据key-value的形式将当前线程和looper对象进行存储的,也验证了上述的观点:一个线程是和looper绑定,looper里就是线程的消息队列和当前线程。
    再来看get,就是根据当前线程来取出我们存储的对象的,当然,在Looper中取出的是looper对象。
    因此,为什么所有线程只需要共享一个ThreadLocal对象的理由也就清晰了,是因为ThreadLocal内部是采用key-value的形式;来存储的,根据key的不同就能存入不同的value。也能根据不同的key来取出不同的value.

   3.ThreadLocal的好处?

   那我们就来看看其中涉及到的ThreadLocal,ThreadLocal是线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据,一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。在Looper、ActivityThread以及AMS中都用到了ThreadLocal。在looper中,如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处。

   4.在looper的构造函数new MessageQueue(quitAllowed)中,quitAllowed表示什么?与Looper.quit()方法的详细是如何呢?
    在想要退出消息循环时,调用Looper.quit()注意,这个方法是要在对象上面调用,很明显,用对象的意思就是要退出具体哪个Looper,示例代码为:

mLooper.quit();
    为什么一定要在对象上来调用呢?直接上quit的源码:
 public void quit() {
        mQueue.quit(false);
    }
    在这里我们可以看到 mQueue变量是looper内置的一个变量,作用域仅限于Looper内部,因此,我们在使用quit的时候一定要指明哪个对象,但是用此方法也会带来不安全的地方就是在Looper终止之前,消息队列中的需要被处理的消息可能还没有被分发。因此源码中推荐我们使用的是quitSafely方法,直接上代码:
 public void quitSafely() {
        mQueue.quit(true);
    }
     既然所有的焦点都指向了MessageQueue(它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表)中的quit这个方法,那我们直接来看源代码:
 void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
    通过观察以上源码我们可以发现:
    mQuitAllowed在子线程的Looper构造函数中是true,表示其是可以被quit的,从上面throw的Log也描述的很清楚了“Main thread not allowed to quit”,因此可以很自然的去认为在主线程创建looper时mQuitAllowed标记的是false, 当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。
    在这里再补充一下,查看removeAllFutureMessagesLocked和removeAllMessagesLocked的源代码可以知道,实际上到了这一步就是基本的链表操作,如何消息中的最快执行时间比当前时间晚的话,直接将所有消息停止了,直白一点,还没做的全部都不做了,相反,如果有一些消息到了执行时间的话,那就将队列中所有还未到执行时间的消息回收了,剩下的消息队列中的消息都是到了时间的消息,只需要等着looper分发出去就行了。
无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值