05.Handler源码之消息队列的遍历

405 阅读2分钟

前几篇我们学习了Handler的创建,Looper和MessageQueue是组合关系,Handler持有Looper和MessageQueue的引用。还有消息的创建与发送,消息持有Handler的引用,通过Handler发送到MessageQueue。同时了解到MessageQueue是单向链表,以Message的分发时间排序。

这一篇我们要探究Looper是如何循环遍历MessageQueue的,还有Looper是如何把消息分发给Handler的。

先来看看Looper.loop()的主要代码:

public static void loop() {
        final Looper me = myLooper();    //获得Looper实例

        ......    //Looper空判断和判断是否正在循环

        me.mInLoop = true;    //将正在循环标志位设置为true
        final MessageQueue queue = me.mQueue;    //获得MessageQueue实例

        ......    

        for (;;) {    //死循环遍历MessageQueue,不断拿出消息发送给Handler处理
            Message msg = queue.next(); // 当没有消息返回时会阻塞
            if (msg == null) {    //返回消息为空时直接退出该循环
                // No message indicates that the message queue is quitting.
                return;
            }

            ......    

            try {
                msg.target.dispatchMessage(msg);    //消息通过持用的Handler引用进行分发
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            ......          

            msg.recycleUnchecked();    //回收该消息到全局消息池,下次可以直接通过obtain()方法获取消息,减少消息创建的开销
        }
    }

Loop.looper()方法内有一个死循环,该循环调用MessageQueue.next()方法来获得下一个待处理的消息,当没有消息返回时会阻塞等待。如果获得的消息为空,则直接结束循环。否则会通过该消息的Handler引用分发该消息并进行回收。

函数在调用实例变量和类变量时,往往会创建新的局部变量进行保存,规避函数结束之前被调用变量发生改变所带来的影响。

来看看MessageQueue.next()的具体代码:

Message next() {
        // 当消息循环已退出时返回null
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; //空闲处理程序计数器,初始值为-1,大部分时候值为0
        int nextPollTimeoutMillis = 0;    //阻塞等待时间
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();    //阻塞前将当前线程中挂起的所有Binder命令刷新到内核驱动程序
            }

            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) {
                    // 如果队头是消息屏障,则往下找到第一条异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                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;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // 子线程的Looper调用了quit(),该标志位会被置为true
                if (mQuitting) {
                    dispose();
                    return null;
                }

                //pendingIdleHandlerCount < 0条件只有第一次循环执行到这里时会成立
                //当第一次执行到这里,且消息队列为空或者还没到消息的发送时间时,会执行空闲等待程序
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    //pendingIdleHandlerCount唯一一次大于0的机会,此处进行赋值,下面的空闲等待程序代码块才有机会执行
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // 空闲处理程序计数器不大于0,结束本次循环,开始下次循环进行等待阻塞
                    mBlocked = true;
                    continue;
                }

                //获取空闲等待程序
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 执行空闲等待程序
            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);
                    }
                }
            }

            pendingIdleHandlerCount = 0;

            // 执行完空闲等待程序后,不会再进行阻塞等待,所以将阻塞时间置为0
            nextPollTimeoutMillis = 0;
        }
    }

消息队列的loop()方法内也有一个死循环,从队头开始遍历消息。如果第一次遍历时队列为空或者还没到信息的分发时间,则会获取空闲等待程序进行执行。从第二次遍历开始会直接计算阻塞时间进行阻塞等待。获取消息时,直接取队头信息,如果消息为空,则将循环等待时间置为-1(参考动画的循环次数,这里大概是一直等待,直到被唤醒)。如果遇到屏障消息,会一直往下遍历直到找到异步消息,否则将一直等待下去。如果消息不为空且为同步消息,会先判断是否可以分发,如果未能分发将计算阻塞时间进行等待,可以分发的消息会直接取出发送给Handler处理,消息队列也会进行更新。loop()方法的退出条件是调用quit()

消息的分发处理dispatchMessage(@NonNull Message msg)的源码:

public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {    //消息自带的处理程序不为空则,会直接调用自带的处理方法
            handleCallback(msg);
        } else {
            if (mCallback != null) {    //mCallback是Handler初始化时传入的消息处理接口
                if (mCallback.handleMessage(msg)) {    
                //执行接口处理程序,如果返回true,表示处理完成,不再往下执行
                    return;
                }
            }
            handleMessage(msg);    //调用Handler初始化时重写处理方法。
        }
    }


private static void handleCallback(Message message) {
        message.callback.run();
    }

综上,Loop不断遍历消息队列,循环取出消息进行处理需要通过两个嵌套的死循环实现,外层等待待处理的消息返回,发送给Handler进行处理。内层遍历消息队列,没有消息返回时进行阻塞等待,直到有消息返回才会结束循环。

Handler是d如何保证在对应的线程处理消息的?

这和Looper的创建有关,Looper在指定的线程创建,其绑定的消息队列也和Looper在同一个线程。发送消息时可能在不同的线程,但消息最终都会存放进消息队列。Looper在其线程中遍历消息,获得待处理的消息后也是在指定线程中对消息进行分发,调用Handler.dispatchMessage(@NonNull Message msg)方法。该方法运行在指定线程中,对消息进行处理。