详解Handler中消息队列的入队逻辑

一、源码分析

具体分析请见代码注释:java

/** * 消息队列是以执行时间为序的优先级队列 * * @param msg * @param when * @return */
boolean enqueueMessage(Message msg, long when) {

    //入队消息没有绑定Handler
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //入队消息已经在使用中
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    //获取同步锁
    synchronized (this) {
        //入队消息正在被取消发送
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            //回收入队消息
            msg.recycle();
            return false;
        }
        //标记入队消息为正在使用中
        msg.markInUse();
        //入队消息的执行时间
        msg.when = when;
        //获取消息队列的队首消息
        Message p = mMessages;
        //是否须要唤醒线程
        boolean needWake;
        //若是队列首部为null,也就是队列为空
        //或若是入队消息的执行时间为0,也就是入队消息须要立刻执行
        //或若是入队消息的执行时间小于,也就是早于队首消息的执行时间
        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.

            // 若是线程已经阻塞
            // 且若是队列的队首消息的Handler为空,也就是队首消息是同步屏障消息
            // 且若是入队消息是异步的,也就是能够经过同步屏障
            // 则须要唤醒
            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;
        }

        // 咱们能够假设mPtr != 0,由于m是假的。
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            //若是须要唤醒,则进行唤醒
            nativeWake(mPtr);
        }
    }
    return true;
}
复制代码

二、消息是分哪些状况入队的?如何入队?

咱们剖除入队规则、同步锁、同步屏障消息、异步消息、唤醒规则等逻辑,将入队的逻辑代码抽出,获得:markdown

public class Message {
    public Object obj;
    public long when;
    public Message next;
}
复制代码
public class MessageQueue {
    public Message mMessages;

    public void enqueueMessage(Message msg, long when) {
        msg.when = when;
        Message p = mMessages;
        if (p == null || when == 0 || when < p.when) {
            //往空队列和队列头插入消息
            msg.next = p;
            mMessages = msg;
        } else {
            Message prev;
            for (; ; ) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    //往队列尾和队列中插入消息
                    break;
                }
            }
            msg.next = p;
            prev.next = msg;
        }
    }
}
复制代码

2.一、往空队列插入消息

2.二、在队列头插入消息

2.三、在队列尾插入消息

2.四、在队列中插入消息

三、消息入队时,什么状况下须要主动唤醒线程?

3.一、队列中没有任何消息,且线程阻塞

此时新消息入队后便主动唤醒线程,不管新消息是同步消息、异步消息。less

3.二、队首的消息执行时间未到,且线程阻塞

若是在阻塞时长未耗尽时,就新加入早于队首消息处理时间的消息,须要主动唤醒线程。 一、若是入队消息的执行时间为0,也就是入队消息须要立刻执行。 二、若是入队消息的执行时间小于队首消息的执行时间,也就是入队消息要早于队首消息执行。异步

3.三、队首消息是同步屏障消息,而且队列中不含有异步消息,且线程阻塞

若是新加入的消息仍然是晚于队首同步障碍器处理时间,那么此次新消息的发布在next()层面上是毫无心义的,咱们也不须要唤醒线程。 只有在新加入早于队首同步障碍器处理时间的同步消息时,或者,新加入异步消息时(不论处理时间),才会主动唤醒被next()阻塞的线程。async

3.四、队首消息是同步屏障消息,队列中含有异步消息但执行时间未到,切线程阻塞

由于队首同步障碍器的缘故,不管新加入什么同步消息都不会主动唤醒线程。 即便加入的是异步消息也须要其处理时间早于设定好唤醒时执行的异步消息,才会主动唤醒。源码分析

欢迎关注Android技术堆栈,专一于Android技术学习的公众号,致力于提升Android开发者们的专业技能! 学习

Android技术堆栈