Android 源码阅读 | MessageQueue

1,164 阅读3分钟

Android 源码阅读.png

相关推荐:

类注释

/**
 * Low-level class holding the list of messages to be dispatched by a
 * {@link Looper}.  Messages are not added directly to a MessageQueue,
 * but rather through {@link Handler} objects associated with the Looper.
 * 
 * <p>You can retrieve the MessageQueue for the current thread with
 * {@link Looper#myQueue() Looper.myQueue()}.
 */
  • MessageQueue 保存着由 looper 发送的消息列表
  • Message 不是直接添加到 MessageQueue 中,而是通过与Looper关联的 Handler 对象添加的。
  • 通过 Looper.myQueue() 访问到 MessageQueue

插入消息 enqueueMessage

跟踪源码发现,在 handler 类中有这样的一个方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

这是向消息队列里面插入消息,调用的是 queue.enqueueMessage

Message mMessages;
boolean enqueueMessage(Message msg, long when) {
    //合法性校验代码省略...
    synchronized (this) {
        //quit代码省略...
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //【1】
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            //【2】
            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;
        }
        //wake代码省略...
    }
    return true;
}

省略了一些无关紧要的代码。在最开始,我们看到有一个变量:Message mMessages ,可以明确,这个就是队列的头。而在 Message 类的源码分析中,我们知道:

// sometimes we store linked lists of these things
/*package*/ Message next;

缓存池中的message对象是通过 message.next 变量进行首尾相连,一个链表的形式维持着,从缓存池取出对象后,message.next置为空,在这里,messageQueue也是通过message.next这个变量,进行首位相连接,同样的套路以链表的形式维持着messageQueue。

【1】标记1中,如果判断到当前的队列头为空,或者要插入的消息的时间等于0,或者是小于当前的队列头的消息,则,把这个消息插到最前面。咦,这里好想是把when时间小的放在最前面喔。我们继续往下看

【2】标记2中,for循环遍历整个链表队列的message,当遍历到 when < p.when 的位置,或者是 p==null 队列尾部的位置,结束遍历,然后插到相应的位置上。由此可见,队列是按照when的时间来从小到大进行排序的,when指的就是,在哪个时刻处理这个 message 信息。

移除消息 removeMessages

void removeMessages(Handler h, int what, Object object) {
    if (h == null) {
        return;
    }
    synchronized (this) {
        Message p = mMessages;
        // Remove all messages at front.
        while (p != null && p.target == h && p.what == what
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }
        // Remove all messages after front.
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && n.what == what
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

第一个while是先从队列头部进行删除,如果删除了头部,则指向接下来的元素,重复这个过程,直到有一个不匹配的元素出现,跳出while,然后从这个元素开始向后面遍历,查找符合的message并删除,直到队尾。remove有多个重载,但其逻辑跟上面差不多。

获取下一个消息 next()

Message next() {
    //省略部分代码...
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //省略部分代码...
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //省略部分代码...
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                nextPollTimeoutMillis = -1;
            }
            // 退出判断
            if (mQuitting) {
                dispose();
                return null;
            }
            //如果没有获取到message,则说明比较空闲,然后下面来回掉 IdleHandler 接口
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        //省略部分代码...
    }
}

先获取系统的时间 final long now = SystemClock.uptimeMillis(); 接着判断 Message msg = mMessages; 现在的时间是否小于链头消息的时间,now < msg.when 如果不是,则返回链头,同时把链头指向下一个。

如果最后没有获取到消息,说明此时队列为空,那么会进入一段逻辑:

for (int i = 0; i < pendingIdleHandlerCount; i++) {
    final IdleHandler idler = mPendingIdleHandlers[i];
    mPendingIdleHandlers[i] = null; // release the reference to the handler
    boolean keep = false;
    try {
        keep = idler.queueIdle();
    } catch (Throwable t) {
        Log.wtf(TAG, "IdleHandler threw exception", t);
    }
    if (!keep) {
        synchronized (this) {
            mIdleHandlers.remove(idler);
        }
    }
}

这个 IdleHandler 是什么东西?

/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.
 */
public static interface IdleHandler {
    /**
     * Called when the message queue has run out of messages and will now
     * wait for more.  Return true to keep your idle handler active, false
     * to have it removed.  This may be called if there are still messages
     * pending in the queue, but they are all scheduled to be dispatched
     * after the current time.
     */
    boolean queueIdle();
}

从注释看到,这个接口,会在没有消息的情况下(消息队列为空),进行回调,也就是在队列空闲的时候,去回调这个接口。接口返回一个Boolean的值,true代表执行一次后,不移除IdleHandler接口,false代表移除。

在Android中,我们可以处理Message,这个Message我们可以立即执行也可以delay 一定时间执行。Handler线程在执行完所有的Message消息,它会wait,进行阻塞,直到有新的Message到达。如果这样子,那么这个线程也太浪费了。MessageQueue提供了另一类消息,IdleHandler。

IdleHandler 允许您在 MessageQueue 空闲的时候,去做一些额外的事情。比如说可以这样子做: IdleHandler,页面启动优化神器,亲测过,启动速度确实提高了很多。

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        //启动加载数据
        return false;
    }
});

结束 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);
    }
}

结束队列消息的循环,可以看到,通过 safe 布尔值去执行不同的动作,如果是 safe = false 非安全,执行下面的方法,直接把队列里面所有的消息都remove掉并且回收。

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

但如果是 safe = true,执行下面的方法:

private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

当链头的消息处理时间比现在的时间还要大,说明链头的消息是在未来被处理,而队列因为是按照执行时间从小排列,也说明整个消息队列的处理应该都在未来,这种情况下,也是直接抛弃掉所有的消息。但如果链头的处理时间比现在的要小,则继续遍历寻找,直到找到 n.when>now,然后从这个位置开始,回收消息。

也就是说,在quit的时候,如果有消息未被处理,则一定会直接被抛弃(removeAllFutureMessages)。

总结

  • 消息队列是基于链表实现的
  • 消息队列是有序的,按照执行时间的先后,从小到大排序,链头的消息首先会被取出执行,先被处理掉
  • next() 获取下一个消息的方法会一直循环去读取当前队列里面是否有符合条件(now >= msg.when)的消息。在队列空闲的时候,会回调 IdleHandler 的接口
  • 延迟消息的处理,实际上是 next() 方法不断地去比较与当前时间的差值,当判断到msg.when <= now ,才执行消息。
  • quit() 结束队列循环,把所有未被处理的消息(未来执行的消息)直接扔掉。

码字不易,方便的话素质三连,或者关注我的公众号 技术酱,专注 Android 技术,不定时推送新鲜文章,如果你有好的文章想和大家分享,欢迎关注投稿!

技术酱