Handler消息机制(三)MessageQueue原理

2,001 阅读5分钟

原文链接

在Android Handler消息机制中,我在前两篇文章中已经介绍了消息机制中的消息Message和ThreadLocal原理,本篇文章会分析Handler消息机制中的另一个重要的工具,消息队列,在Handler整个消息机制中,每个具体的消息是如何被管理的,同步消息和异步消息是如何存储的?

该系列的其他文章

  1. Handler消息机制(一)Message复用原理
  2. Handler消息机制(二)ThreadLocal原理
  3. Handler消息机制(三)MessageQueue原理
  4. Handler消息机制(四)Looper原理
  5. Handler消息机制(五)Handler原理

本篇文章从以下四个部分来详细介绍MessageQueue。

  • MessageQueue是什么?
  • MessageQueue如何使用?
  • MessageQueue原理?
  • 什么是同步屏障?
  • 同步屏障用法及原理?

1. MessageQueue是什么?

MessageQueue是一个包级私有类,Android消息的管理队列,包含消息的插入和读取功能,其内部是通过单链表的数据结构来维护消息列表,MessageQueue只是负责消息的插入和读取,而单链表在插入和删除上拥有更高的效率,所以采用单链表的结构对消息的维护和管理更加高效。

2. MessageQueue如何使用?

在Android handler消息中,因为MessageQueue是一个包级私有类,我们无法直接去创建MessagQueue对象,所以MessageQueue是在同包下的Looper内部进行创建的,这里只需要知道当我们使用Handler时,每个Handler都必须有一个Looper对象,而在Looper内部会持有MessageQueue消息队列。结合源码看下:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在Looper的构造方法中会创建MessageQueue队列,同时会记录当前线程。 MessageQueue内的插入和删除消息的方法,我们同样不能直接调用,而是通过Handler提供给我们的sendMessage系列方法向MessageQueue中插入消息,而删除消息是在Looper内部轮询消息时进行的读取删除操作。

3. MessageQueue原理?

MessageQueue的插入和读取分别对应的是enqueueMessage和next方法。下面我们分析下方法内部的原理。

3.1 插入消息的enqueueMessage方法

    boolean enqueueMessage(Message msg, long when) {
        // target是handler对象,检查消息是否有线程处理
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        // msg是否已经被使用
        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;
            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 {
                // 将新消息插入到队列中,同步消息根据when来确定队列位置,如果是同步屏障,同步屏障消息target==null,且新消息是异步的,则needWake为true,则计划唤醒队列。
                // 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;
                    }
                    // 如果队头的一下个消息也是异步消息,且未到唤醒时间,则将needWake置为false,不唤醒队列
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // 是否需要唤醒队列
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

首先是对消息进行检验,消息能否被处理和消息是否正在被使用等。msg.target是指Handler对象。当处理消息的线程已经退出,则消息会被回收。 针对同步消息:将时间小的消息放在靠近队头的位置,时间长的消息靠近队尾。 从队头开始,找到新消息在队列中合适的位置,然后将新消息插入到队列中,如果队列为null或新消息的等待时间小于队头消息的等待时间,则直接将新消息放入到队列的头部。如果队头消息的时间小于新消息,则新消息将从队头消息开始依次和队列中的消息进行比较,直到找到合适的位置。

这里特别说明下:同步消息和异步消息只有isAsynchronous()可以区分,即异步标志位区分,异步标志位是通过msg的setAsynchronous(boolean async)方法进行设置,异步和同步消息均是根据when来确定在队列中的位置。

3.2 读取消息的next方法

    Message next() {
        // 只有当线程退出时,mPtr为null
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            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) {
                        // 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;
                        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;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
                ...
        }
    }

在next方法中,当发现队列中有同步屏障时,此时会优先返回队列中的异步消息,从队列中获取异步消息,并将该异步消息从队列中移除,如果同步屏障不被移除,即使异步消息被处理完毕,同步消息也不会被处理,队列会进入阻塞状态。 如果队列中没有同步屏障,则从队列中获取同步消息,并将该同步消息从队列中移除。

同步屏障下,异步消息的处理示意图:

处理异步消息示意图

4. 什么是同步屏障?

从名称上来看,就是将同步消息屏蔽的意思,当队列中包含同步屏障消息时,整个消息队列会进入优先处理异步消息的状态,同时同步消息会被屏蔽。 在说同步屏障时,需要区分清楚MessageQueue队列中的消息类型,方便理解同步屏障,MessageQueue队列中可以分为三种消息,屏障消息,同步消息和异步消息。 屏障消息: target为null的Message对象,即没有消费者的消息,该消息仅用来标记当前队列优先处理异步消息。 同步消息: target不为null的Message对象,即可以被消费的消息,且该消息的isAsynchronous方法返回false。 异步消息: target不为null的Message对象,和同步消息的区别是isAsynchronous方法返回true。 异步消息需要用户手动的将Message标记为异步,通过方法setAsynchronous(true)将消息标记为异步。 清楚了MessageQueue中的消息类型,我们再来看什么是同步屏障,就更容易理解了,同步屏障就是将同步消息屏蔽掉,只处理异步消息,这就是同步屏障的作用。

5. 同步屏障用法及原理?

5.1 同步屏障的作用我们理解了,如何使用同步屏障呢?

在MessageQueue类中有两个方法,用来添加和删除同步屏障,分别为postSyncBarrier()和removeSyncBarrier()。 通过Handler -> Looper -> MessageQueue,然后使用其方法即可,其实这两个方法的使用场景基本不可见,因为这两个方法被声明为hide方法,相当于系统自己留的后门,优先处理高优先级的任务,所以我们在调用时会出错,结合源码看,该方法只在Android的ViewRootImpl中被调用了,主要用来处理绘图操作,后续再去仔细分析ViewRootImpl的源码。

5.2 同步屏障的原理?

上面分析的插入消息我们知道,同步消息和异步消息的入队均是通过when的大小来确定消息在队列中的位置,在读取消息时,队列通过消息的msg.target==null来判断同步屏障。如果队列有同步屏障消息,则优先处理异步消息(即msg.isAsynchronous()返回true),直到异步屏障被清理,异步消息处理顺序一样是通过when大小来确定。如果不包含同步屏障,则处理同步消息,根据when大小来确定消息的处理顺序。 这里声明下:when大小是不会相同的,因为消息的入队是顺序的,所以根据入队时的时间计算出的when大小是不同的。

5.2.1 设置同步屏障postSyncBarrier方法
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // 插入同步屏障消息
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            // msg.target == null,由此判定该消息是同步屏障消息
            // msg.arg1 == token,arg1参数记录同步屏障消息的token数据
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            // token,用来删除同步屏障
            return token;
        }
    }
5.1.2 删除同步屏障removeSyncBarrier方法
   public void removeSyncBarrier(int token) {
        // 通过token或targe == null来查找同步屏障消息
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            // 查找同步屏障消息,即p.target == null 或 p.arg1 == token的消息
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            // 队列没有同步屏障消息,抛出异常
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            // 重新构建移除同步屏障的消息队列
            final boolean needWake;
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            // 同步屏障消息被回收到Message的Pool内
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

删除同步屏障通过我们插入同步屏障返回的token或者target==null来识别的。

同步屏障在使用完毕后一定要清理,否则同步消息将无法被处理。

结余 MessageQuene是维护handler消息的队列,提供了插入和读取消息的功能,可以通过同步屏障的方式,来处理高优先级的消息。