Handler机制之十大拷问

首先来看几个问题:
  1. Handler机制是什么.
  2. Handler、MessageQueue、Looper之间的关系及各自的职责。
  3. Message分哪几种类型。
  4. Handler消息调用链。
  5. Message是如何入队的。
  6. Message是如何出队的。
  7. MessageQueue中消息是如何阻塞的。
  8. MessageQueue中消息什么时候阻塞,什么时候被唤醒。
  9. 什么是同步消息屏障SyncBarrier。
  10. 什么是IdleHandler。
一、Handler机制是什么

Handler机制可以简单描述为是一套 “阻塞型消息队列处理机制”。
说道Handler机制的运行机制,需要从Looper讲起,而说道Looper归根结底需要从Thread讲起。

我们就先来看看Android中的主线程,其入口为ActivityThread中的main[]方法,其中开启了一个Looper,并执行了loop()方法,至此应用就开始不停的运行起来了。

我们主线程中开启了一个Looper,调用Looper.loop()
该Looper维护着一个MessageQueue,
MessageQueue中以链表的方式存储着消息队列头结点Message,
Message中引用了该消息的target接受者Handler,
Looper通过Looper.loop()在不停的从MessageQueue中取出消息,并执行message中的handler进行分发处理,
MessageQueue负责维护消息的入队和出队,
Handler则负责发送和移除消息。

上面是拿主线程举例,同样,如果在工作线程中也是这个机制,需要在工作线程中的run方法中开启Looper,而Handler需要持有这个工作线程中的Looper的引用。

二、Handler、Looper、MessageQueue、Message之间的关系及其职责

关系:
Handler持有成员变量mLooper及mQueue,在构造函数中初始赋值,mQueue指向mLooper.mQueue;
Looper持有成员变量mQueue,也在构造函数中初始赋值;
MessageQueue持有成员变量mMessages,为链表结构,指向链表头部;
Message持有成员变量target及next,target指向Handler,next指向它的下一个结点。
职责:
Handler:负责发送消息,接受消息分发处理,移除消息等
MessageQueue:负责消息队列的维护,消息入队,消息出队等
Looper:负责不停的从MessageQueue中取出消息,并分发给消息接受者去处理,以及回收消息调用。
Message:负责持有Handler和Callback,具体消息的创建及回收。
这个机制的用处:可以解决线程间的通信问题,Handler可以持有主线程的Looper,而Handler可以在工作线程发送消息,而最终在主线程得到执行。

三、Message分哪几种类型

Message根据它的功能可以分为三种类型:同步消息、异步消息、同步屏障消息
同步消息:即我们常用的普通消息
异步消息:message.setAsynchronous(true)的消息
同步屏障消息:message.target=null的消息

四、调用链

Looper.loop()
.....
Handler.sendMessage()-->MessageQueue.enqueueMessage()
Looper.loop()-->MessageQueue.next()-->message.target.dispatchMessage()
msg.callback.run()/handler.mCallback.handleMessage()/handler.handleMessage()

五、消息是如何入队的enqueueMessage()

Handler#sendMessage(Message msg)最终会调用MessageQueue#enqueueMessage(Message msg, long when)方法,在这个方法中,将消息入队。
MessageQueue中的消息其实是根据消息的触发时间顺序排列的,消息入队其实是将消息按时间顺序插入到指定的位置。

boolean enqueueMessage(Message msg, long when) {
        // enqueueMessage方法不接受同步消息屏障
        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已被使用
            msg.markInUse();
            // 这里的when,是调用SystemClock.updateMillis()获取的,它表示自开机以来的毫秒数。
            msg.when = when;       
            // p指向mMessages,而mMessages在MessageQueue中作为成员变量,一直指向链表头部
            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 {
                // 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;
                    }
                    if (needWake && p.isAsynchronous()) {
                        // 如果队列中有异步消息,则不需要唤醒队列???
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
                // 这样msg就插入在了prev与next中间。
            }
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                // 如果需要唤醒,则执行native唤醒
                nativeWake(mPtr);
            }
        }
        return true;
    }

一句话简单总结一下:消息通过MessageQueue#enqueueMessage()方法入队,消息会遍历链表,将消息按照触发时间顺序插入在指定的位置。并根据需要决定是否需要唤醒队列。

六、消息是如何出队的next()

Looper#loop()负责不停的取出消息并分发处理,其内部调用的是MessageQueue#next()方法。

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) {
            // 当next()方法return null时,looper()也将return,而结束
            return null;
        }
        // 临时空闲Handler
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;// -1:一直阻塞;0:不阻塞;n>0:最多阻塞n秒
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            // 下面这个方法有可能会产生阻塞,主要是通过native层的epoll机制,监听文件描述符的写入事件实现的。
            // -1:一直阻塞;0:不阻塞;n>0:最多阻塞n秒
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                // msg指向mMessages,mMessages指向链表头部
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    // target==null说明该msg是一个同步屏障消息,如果同步屏障消息位于头部,则遍历找到队列中的第一个异步消息。
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                // 至此:
                // 如果同步屏障消息位于头部,则需要按顺序处理队列中的异步消息,msg为队列中的第一个异步消息;
                // 如果头部消息不是同步屏障消息,则需要正常按顺序处理队列中消息即可,msg为队列中的第一个消息。
                // msg不为空,说明队列中有同步或者异步消息
                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结束了,下面就不会再执行了。
                        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;
                }
                // 执行到这里,说明此时,消息触发时间还没到或者队列中没有消息,需要阻塞。

                // 下面是Idle handles 机制,它提供了当队列空闲时的一种回调机制,可以用来处理当队列空闲时的
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                // pendingIdleHandlerCount初始值-1,第一次for循环就会将其置为0,所以在整个for循环中,下面代码只会被执行一次。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    // mIdleHandlers是用户设置的Idle handles的集合
                    // 读取mIdleHandlers的数量
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    // 居然没有idle handlers需要执行,那就继续该干嘛干嘛,下面的idle handlers执行操作也不需要继续了
                    mBlocked = true;
                    // 看这里,如果pendingIdleHandlerCount <= 0会直接continue。
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    // 初始化mPendingIdleHandlers,初始最大数量为4。
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                // 从mIdleHandlers赋值给mPendingIdleHandlers
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            // 执行idle handlers,注意这里的代码在for循环中,也只会执行一次
            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 {
                    // queueIdle()是一种回调机制,供外部处理,而其返回值keep则代表是否需要将该idler移除
                    // keep=false则该idler只执行一次,keep=true则该idler下一次还会被执行。
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            // 置为0,代表着,for循环下一次不会再执行这里。
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            // 当回调了一个idle handler,这时队列中有可能已经有了新消息,需要立即返回查看是否有新消息。
            nextPollTimeoutMillis = 0;
        }
    }

一句话简单总结:
MessageQueue#next()方法在遍历消息队列链表,找到一个需要触发的消息,否则将阻塞,直到找到下一个需要触发的消息,next方法结束。
大体过程:
1. 如果头部是同步屏障消息,则阻挡同步消息,只处理异步消息,遍历链表找出第一个异步消息,若队列中没有异步消息则一直阻塞。
2.如果头部不是同步屏障消息,则应该是同步或者异步消息,如果它的触发时间到了,则返回该消息,如果触发时间未到,则阻塞指定时间。
3.否则,队列中没有任何消息,则需要一直阻塞。
4.如上面说的,当消息队列准备进行阻塞时,说明接下来队列将要进入空闲阶段,会执行idle handler的回调。
注意:
1.next()方法里面的for循环最终只会return一个消息,找到合适的消息则立即返回,找不到则会阻塞,待唤醒后再找到合适消息的返回。
2.所谓队列空闲,就发生在队列阻塞的那段时间里,在next()方法中idle handler只会执行一次。

七、MessageQueue中消息是如何阻塞的

我们上面说Looper.loop()在不停的从MessageQueue中取出消息,那么当没有消息,或者消息触发时间没到,该怎么处理呢?
其实当没有消息,或者消息触发时间没到时,在MessageQueue的next()方法中会执行nativePollOnce(ptr, nextPollTimeoutMillis)方法,该方法可能会产生阻塞,
nextPollTimeoutMillis=-1:一直阻塞,直到被其他消息事件唤醒
nextPollTimeoutMillis=0:不会阻塞,继续运行
nextPollTimeoutMillis>0:最多阻塞nextPollTimeOutMillis毫秒后,继续执行
nativePollOnce()其具体是使用了linux的ePoll机制监听文件描述符的写入事件来实现的。

八、MessageQueue中消息什么时候阻塞,什么时候被唤醒

当队列中没有消息,或者消息没有到达触发时间,MessageQueue会在next()方法中通过nativePollOnce进行阻塞;
当有新的消息入队时,会根据当前队列的阻塞情况决定是否需要唤醒,通过nativeWake进行唤醒。

九、什么是同步屏障消息SyncBarrier

同步屏障消息,就是为了确保异步消息的优先级,同步屏障消息开启后,会阻挡住同步消息,而只能处理其后的异步消息,直到撤销该同步屏障消息,同步消息才得以继续处理。
同步屏障消息可以插入在消息队列中指定的位置postSyncBarrier(long when),也可以插入在消息队列头部postSyncBarrier(),当消息取出机制按顺序取到同步屏障消息时,则代表着“同步屏障消息开启”,将开始优先处理队列中的异步消息。如果队列中没有异步消息,则loop()方法会被Linux epoll机制阻塞。同步屏障消息的开启一直持续到该同步屏障消息被主动撤销removeSyncBarrier(int token)。

十、什么是IdleHandler

IdleHandler是当MessageQueue队列阻塞(空闲)时的一种回调机制,可以用来在队列空闲时间处理一些事情。
使用场景:
1.Activity页面在绘制过程中,如果在生命周期中做耗时的任务会导致启动时间增加,会使用户感觉到卡顿,可以在onResume中启用IdleHandler来当页面绘制完之后做一些任务。
2.ActivityThread中也使用了GcIdler,在主线程消息队列闲暇时做部分回收工作。
3.Glide中也使用RefQueueIdleHandler,在闲暇时移除一些资源的弱引用。

可以参考一下资料:

https://juejin.im/post/5d712cedf265da03ea5a9ecf
https://blog.csdn.net/start_mao/article/details/98963744
https://www.wanandroid.com/wenda/show/8723
https://www.wanandroid.com/wenda/show/8710

你可能感兴趣的:(Handler机制之十大拷问)