handler源码学习(4) — MessageQueue

5 篇文章 0 订阅
4 篇文章 0 订阅

Handler是面试必问系列问题之一。本系列将从初学者的视角分析面试中常见的问题。

handler源码学习(1) — Handler
handler源码学习(2) — Message
handler源码学习(3) — Looper
handler源码学习(4) — MessageQueue

本篇学习MessageQueue,主要解决以下几个问题

  • 如何入队列
  • 如何判断队列是否包含某个消息
  • 如何移除消息
  • 如何取消息
  • 如何实现同步屏障

入队列

还记第一篇文章讲到无论是sendMessage还是postMessage最后都调用enqueueMessage方法,而enqueueMessage调用的就是MessageQueue的enqueueMessage(Message msg, long when)。源码如下

    boolean enqueueMessage(Message msg, long when) {
        //注释1.注意这里,如果msg.target==null,会直接抛出异常。后续讲同步屏障要用到
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //注释2.如果正在使用,直接抛出异常
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        
        //注意这里加锁了
        synchronized (this) {
            //如果正在放弃,直接return false,同时回收掉msg
            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;
            }

            //注释3.标记为正在使用
            msg.markInUse();
            msg.when = when;
            //mMessages就是队头消息
            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.
                //英文注释已经很清楚了,我直接翻译
                //添加一个消息到队列中间,通常不需要唤醒事件队列,除非有屏障并且这个消息是最早的异步消息
                
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //这里for循环就是遍历消息,拿到触发时间晚于入队列消息的消息和他的上一个消息
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

注释已经很清楚了。这里总结下:消息入队列会按触发时间进行入队列。这里留下三个疑问:

  • 注释1处,msg.target == null会抛出异常,什么时候会msg.target == null?
    • 目前看到的是在发送屏障消息时target为null
  • 注释2处,msg.isInUse()会抛出异常,什么时候会msg.isInUse()?,什么时候会清除标记?
    • 我目前看到的是在入队列的时候标记为正在使用,在obtain()的时候会被标记为不是正在使用。即使recycleUnchecked之后还是正在被使用状态。
  • 哪些场景需要唤醒事件队列?

如何判断队列是否包含某个消息

其实这个重点是关注结论,我只给出一个方法的源码

    boolean hasMessages(Handler h, int what, Object object) {
        if (h == null) {
            return false;
        }

        synchronized (this) {
            Message p = mMessages;
            while (p != null) {
                if (p.target == h && p.what == what && (object == null || p.obj == object)) {
                    return true;
                }
                p = p.next;
            }
            return false;
        }
    }

可以看到对比的并不是对象,而是target,what和object相同。

从队列移除消息

    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;
            }
        }
    }

    void removeMessages(Handler h, Runnable r, Object object) {
        if (h == null || r == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;

            // Remove all messages at front.
            while (p != null && p.target == h && p.callback == r
                   && (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.callback == r
                        && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }

    void removeCallbacksAndMessages(Handler h, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;

            // Remove all messages at front.
            while (p != null && p.target == h
                    && (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 && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }

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

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

其实这块就是遍历调用调用recycleUnchecked()。唯一的考点是,为什么在移除消息的时候有些方法会先移除前面的消息,然后再移除后面的消息。答案是mMessages = n;为了拿到队头。其实一个while循环也可以。只是要讲会比较复杂。举个例子


消息队列 = {0,1,0,2}
第一个消息 = 0
此时移除消息0
两次while循环
先拿到 0符合,移除,再拿到1,不符合,队头就是1,然后执行第二个while循环,此时就不涉及到队头变化了

但如果是一个while循环
拿到0,符合,移除队头是1,又拿到1,不符合,又拿到0,又符合,此时就要判断是否要更换队头了

取消息

    Message next() {
        // 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;
                //msg.target == null
                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.
                        //下个消息还没到触发时间,记录下触发时间。最大值为Integer.MAX_VALUE
                        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.
                    //如果消息为空,将下次唤醒时间设置为-1
                    nextPollTimeoutMillis = -1;
                }

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

                //以下是关于idleHandler(闲时策略)相关的,后续分享。
                ······
            }

            ······
        }
    }

以上便是取消息的源码。简单解释下流程。如果消息不为null且没有target,则开始屏障逻辑。还有我们之前提到的Looper.loop()不会阻塞,但是该方法会循环调用MessageQueue.next()。这个方法会阻塞,注意nativePollOnce(ptr, nextPollTimeoutMillis);这个方法,他的作用是在唤起一次,并记录下次触发的时间。如果没有消息,执行完一次之后,将会在这里挂起,有点类似Object.wait。那什么时候唤醒呢?其实在入队列的时候有这样一个方法nativeWake(mPtr),它的作用就是唤醒有点类似obejct.notify。经常面试被问到为什么Looper.loop()不会卡死,其实答案就在这里。因为没有消息时,会挂起。在有消息时,会在下次消息到达触发事件才触发。

同步屏障

    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++;
            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;
            }
            return token;
        }
    }

其实很简单,就是Message.obtain()创建了一个msg,并且这个消息是没有target的,然后按触发时间插入消息队列。在next中,会优先处理这类消息。注意这个方法是private。要想使用只能用反射。

总结

  • 如何入队列
    • 按触发时间入队列
  • 如何判断队列是否包含某个消息
    • 并不是调用equals,而是比较message的what,when,object
  • 如何移除消息
    • 移除指定message时,先移除前面的,确定消息队列头,然后移除后面的。
  • 如何取消息
    • 不断的for循环取下一个消息,如果是屏障消息,则优先屏障消息。当没有消息时触发闲时策略,当没到下个消息触发时间时,会nativePollOnce,记录下下个触发的时间,阻塞。
  • 如何实现同步屏障
    • 通过postSyncBarrier发送屏障消息。该方法是私有方法需要通过反射调用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值