Android漫谈——Handler原理(一):死磕代码,postDelay()实现原理。

前序

疫情严重,在家闲的无聊,看了不少博客。随着看的博客越多,发现自己不会的越多,同时,忘得也越多,迷迷糊糊的也就越多,之前好多已经记住的,或者已经理解的,现在又变得记不住了,不理解了,所以这次重新翻了一遍源码,仔细读了一边,就诞生了这篇文章。另外,之前看源码,大多是跟着各大博客走的,他们分析完了,我也就觉得自己看懂了,其实远远不是这样,况且,我们一般看源码,都带有很强的目的性,所以会忽略掉很多东西,但是,等到知识储备足够了,再来看,其实很多源码,很有意思。看源码,我们不必纠结于细节,但是,特别重要的知识点,我们仔细抓一下,是有很多意外情况发生的。
而且,我很喜欢那种举一反三的文章,就是说到一个知识点,就能涉及到另一个知识点,在旁边给上参考链接,主线不变,由这条主线,引发出其他问题和知识,很有意思。
楼主想要来一个一连串的主题博客。重点强调,别人说的,和自己理解的,不是一样的。
但是楼主很反感长篇大论的文章,一个是读者看着类,二来,自己将来查阅也不方便,所以,力求简短。
话不多说,接下来我们开始吧。

正题

Handler主线

首先,阅读这个文章需要有一点基础,不会讲大家都知道的Handler的基础知识,顶多只会大概过一下,提醒一下,然后,深抓一下之前忽略的,没有分析的,并且很有含金量的代码。这篇博客的目的不仅仅是讲解某个知识点,而是把所有相关知识点都过一遍。

本次分析,主要就是Handler通过sendMessage为主线,旁敲侧击一些其他的与Message相关的知识点。
首先,所有的Handler.sendMessage,都会走到如下代码处(保留重点代码,加上自己注释,希望大家不要忽略,看一边)。

MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
		//加锁保证链表的多线程安全性。
        synchronized (this) {
        //mQuitting,暂不分析。
            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;
            }
			//markInUse,等到后面博客再分析
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //p==null即现在没有任何待处理的message
            //剩下两个when==0||when
            if (p == null || when == 0 || when < p.when) {
				//这段代码,就是把当前消息放在第一个位置。
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                //mBlocked是重点,等会会详细讲到。
                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.
                //这句代码,引申出来另一个Handler的重要知识点:异步消息和消息屏障。
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                //这里的代码,就是循环遍历,找到适合msg插入的合适位置
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    //这句代码很有意思,相信不少人跟我一样之前都略过了,其实很有意思,等会分析。
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //这就是把msg加入到链表里面去,做这种链表的题目或者代码,拿一张纸画一下就行了。
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
			//这是一个难点,我相信有很多人,都有过这个疑问,nativeWake究竟干嘛用的。其实完全不用深入到底层,知道这个是唤醒队列操作就可以了。
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
}

这段代码我相信大家都看过,下面针对几个疑问点,来一一说明。
首先,就是needWake和nativeWake(mPtr)这段代码了,先说needWake。
顾名思义,needWake,就是是否需要唤醒,唤醒谁?当然是唤醒消息队列了,为什么需要唤醒消息队列?因为消息队列被nativePollOnce阻塞住了。我们暂时不分析C层代码,记住C层代码是干什么的就行了(不然还有去分析epoll和pipe机制,太麻烦了。)

needWake = mBlocked && p.target == null && msg.isAsynchronous();

这句代码,其中p.target == null && msg.isAsynchronous();涉及到消息屏障和异步消息,以后有空再花时间来说明,大家可以自己去网上看一下,很好理解。但是接下来的内容,需要保证你对消息屏障有了解。
然后是mBlocked,这个mBlocked有两个赋值的地方,就是在next()方法里面。这个方法很长,涉及的知识点也比较多,暂时先贴出我们接下来分析的一部分。

for (;;) {
	nativePollOnce(ptr, nextPollTimeoutMillis);
	synchronized (this) {
				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;
                        //接下来的几步操作就是把msg从队列中取出来,还是那句话,画个图一下就明白了。
                        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;
                }
	}
}

nativePollOnce()
这个函数很有意思,传了两个参数ptr和nextPollTimeoutMillis,这里就是上面提到的epoll和pipe机制,
1.如果当前没有消息处理,会阻塞在这里,
2.如果消息是延时消息,那么会等到nextPollTimeoutMillis时间之后,会恢复过来。
这里会涉及到Linux的底层,楼主也不是特别懂,以后等我会了我会补充的。
这里先略过消息屏障和异步消息,以后博客再花时间总结,总结后我会在这里给出地址。现在直接看后面逻辑。
再看里面的这段代码

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

很明显,这段代码是找出了合适的msg返回回去,在找寻代码的过程中,说明msg肯定需要立刻处理,所以mBlocked置为false,当前没有阻塞。
剩下来的next()代码,选取一部分贴出来


                // 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.
                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);

这其中涉及到IdleHandler,这也是一个很有意思的代码,以后的博客专门来分析(顺便提一下,面试必问的LeakCanary就用了IdleHandler来处理)。
还有一处mBlocked = true已经在注释的地方说明了。能走到这里,肯定是没有找到msg,也就是没有msg需要处理的,所以处于阻塞状态。
好了,这两处mBlocked分析完了,总结一下:mBlocked就是用来判断,队列是否有需要处理的消息的,也就是是否处于阻塞状态的。
好,再来看看我们上面分析的代码。

needWake = mBlocked && p.target == null && msg.isAsynchronous();

即,1.如果处于阻塞状态并且当前消息(注意,是当前待插入的消息)满足消息屏障和异步消息的条件(什么消息满足呢?比如,绘制View的时候,就会在ViewRootImpl.scheduleTraversals()里面发送一个这样的屏障),则needWake为true,否则任何一项不满足都为false。
而在后面的for循环中,还有一段赋值代码

                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    //如果之前为true并且这个消息是异步的,那么置为false。
                    //能走到这里,肯定是待插入的msg在 正在遍历的p节点的后面,说明当前消息的时间点前就已经有异步消息了,
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }

这里结合上面的分析,就是如果当前处于阻塞状态,并且有消息屏障,并且待插入消息是异步消息,并且待插入消息的时间点之前也有异步消息,那么,就不唤醒。好费劲~
那么,为什么不唤醒?异步消息有什么特殊的吗?
只好再回去看看之前的next()代码了。这里我就不再贴了。
再想想,总结一下,其实你就明白了。
如果队列不是处于blocked为true状态,自然不需要唤醒(本来就是醒着的,何必再那么费事),只有处于blocked为false时候,才有唤醒的必要,所以,我们需要分析下,什么情况下,需要唤醒。
也就是说,在mBlocked为true,并且是有同步屏障之后,再加入的第一条异步消息,需要唤醒(就算时间没到也需要唤醒)。为什么呢?这就涉及到同步屏障的定义了。当有同步屏障的时候,只能处理异步消息(同步屏障,自然是把同步消息都阻塞了,异步消息放过去。),所以,这里是为了重置nativePollOnce需要等待的时间(即nextPollTimeoutMillis),因为之前的等待时间是同步消息的等待时间。
具体代码整理如下:

				...
				nativePollOnce(ptr, nextPollTimeoutMillis);
				...
                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 {
                        ...
                    }
                } else {
					...
                }
				...
                if (没有消息需要处理) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    //continue就是继续走nativePollOnce()
                    continue;
                }

到了这里,基本上就全部出来了。
我们对所有的情况总结一下,就会显得更加清晰了。

总结

每次加入一个新msg(enqueueMessage()),都会附加判断,是否需要唤醒因为pipe阻塞的队列。

  1. 如果待插入的msg需要立马处理(没有延时),并且队列处于阻塞状态(mBlocked=true),就唤醒。
  2. 如果待插入的msg不需要立马处理(有延时),又分几种情况:
    1. 如果是普通消息,或者异步消息但是前面没有屏障,不唤醒。
    2. 如果是异步消息,并且当前队列头部就是屏障,又分两种情况:
      1. 如果是第一条消息,就唤醒,目的是为了重置nativePollOnce需要等待的时间(为nextPollTimeoutMillis),而不是处理消息。
      2. 如果不是第一条消息,就不唤醒。

总结完了,这应该是目前我见过的最详细的postDelay实现原理了。核心就是重置nativePollOnce需要等待的时间(为nextPollTimeoutMillis),懂了这个,基本上所有分析你都懂了。

后续

这篇文章里面留了不少坑,需要后面补上。其实Handler设计的东西很多。比如,只能在子线程更新UI吗?View.post(Runnable)为什么经常无法生效呢?这个,都涉及到另外一个知识点,后面我会慢慢补充。

你可能感兴趣的:(源码分析)