从源码分析Handler的postDelayed为什么可以延时?

来源:互联网 发布:mpv播放器 mac下载 编辑:程序博客网 时间:2024/09/21 09:25

昨天一个朋友去面试,回来说面试官问了他一个有意思的问题,然后被面试官各种调侃。。偷笑

什么问题呢?中国人都能看得懂的一个问句:Handler的postDelayed为什么可以延时???

握草惊恐~我只知道Handler有个延时的方法叫postDelay,为啥延时我哪知道哇!!!哈哈,相信很多朋友都是有这种感受的。今天单独开篇博客来说这个问题,也表达了我对Handler的敬重之情吧,不废话了,开车得意~~

我们由简入深,步步击破,先来看看用法:

提交一个任务到MessageQueue,Handler提供了上面的5种方式。相信大家看到方法即知意。今天的主角也正好是最后一个。一般我们都会使用Handler直接post一个延时任务:


第一个参数就是要延时执行的任务,第二个参数就是延时时间(毫秒级)。

执行成功则返回true,并将任务添加到消息队列中(MessageQueue)。

注意:返回true不代表任务执行成功,只是成功提交到任务队列。如果Looper提前在任务延时时间前退出,那么该任务将会被删除。

提交任务失败返回false,原因一般都是Looper处理消息队列退出。

上面的代码很简单,相信大家都能看懂。那么如何实现延时的呢?走,去Handler家里瞧瞧~

1、进入postDelayed方法:

可以看到postDelayed方法实际上调用了Handler的sendMessageDelayed方法,然后调用getPostMessage方法对任务进行封装处理。

getPostMessage方法中创建了Message对象,并将任务给了callback。

2、进入sendMessageDelayed方法:

在sendMessageDelayed方法中:

(1)第一步首先对delayMillis(延时时长)进行判断,如果是负值,则将delayMills置为0,从这也能看出来,如果我们传入的延时时间小于0的话,调用postDelayed和调用post方法是相同的。

(2)第二步调用了sendMessageAtTime方法,将message传入,并在当前时间加上了延时时间 delaymillis(当前时间 + 延时时间),由此可以看到,此时变成了绝对时间,即到了绝对的时间,此任务应被处理。

3、进入sendMessageAtTime方法:

在sendMessageAtTime方法中:

(1)首先将Handler的mQueue初始化给queue,然后对queue进行null的判断,如果为null,即抛出sendMessageAtTiem() called with no mQueue异常。这块也很容易理解,我们都知道Handler的运转离不开Looper的存在,在主线程中使用Handler,我们没有创建Looper是因为系统帮我们创建了,在Looper的创建同时,MessageQueue也会被相应创建,从源码我们也能看到:

所以,如果mQueue为null的话,即证明Handler没有绑定Looper,ok,异常的原因我们解释明白了。

(2)判断结束后,调用了enqueueMessage方法,将消息队列任务消息绝对的执行时间作为参数传入。

4、进入enqueueMessage方法:

在enqueueMessage方法中,调用了Looper中创建的MessageQueue实例的enqueueMessage方法,将任务和绝对时间作为参数传递。

5、进入MessageQueueenqueueMessage方法:


方法很长,在synchronized方法中的我们来看核心的代码:

(1)在if判断中:我们看到when==0或者when < p.when的情况下会执行if语句,即我们传入的绝对时间等于0或者绝对时间还没有到,那么该任务将会阻塞当前任务队列。此时我们看变量needWake被赋值为mBlocked.在next()方法内部,如果有阻塞(没有消息了或者只有Delay的消息),会把mBlocked这个变量标记为true:

所以,此时needWake为true。从字面含义很容易理解,该标志为是否需要唤醒。(need---wake)

然后看enqueueMessage方法的末尾:

可以看到needWake此时为true,将调用native层的nativeWake方法,就是将MessageQueue唤醒。

怎么理解呢?

(1)前面由于我们提交了延时任务,由于时间未到,导致了当前MessageQueue被阻塞,needWake为true。

(2)此时如果有新的任务被提交到MessageQueue,此时由于needWake为true,nativeWake方法被调用,新的任务被插入到队列头部,即延时任务的前面,MessageQueue.next方法被唤醒。

(3)此时next取到刚刚新加的任务,如果没有延时会立刻交给Looper去处理。Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表。

(4)如果延时任务还没到时间,计算一下剩余时间继续阻塞;直到阻塞时间到(即延时时间到)或者下一次有Message进队,此时else的方法将被处理,即处理当前的任务,此时整个流程就结束了。

方法链为:

postDelayed -----> sendMessageDelayed -----> sendMessageAtTime -----> enqueueMessage (此时Handler中的方法调用结束)-----> MessageQueue的enqueueMessage 


现在延时的流程是不是就比较清晰了:

  1. postDelay()一个1秒钟的MyTask任务、消息进队,MessageQueue开始阻塞,Looper阻塞,mBlocked为true,在enqueueMessage的if中将needWake = mBlocked。
  2. 然后post一个新的任务、消息进队,判断现在A时间还没到、正在阻塞,把新的任务插入消息队列的头部(MyTask任务的前面),然后此时needWake为true调用nativeWake()方法唤醒线程。
  3. MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;
  4. Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续阻塞;
  5. 直到阻塞时间到或者下一次有Message进队;
这样,基本上就能保证Handler.postDelayed()发布的消息能在相对精确的时间被传递给Looper进行处理而又不会阻塞队列了。

ok,上面我们结合源码来说明了Handler执行延时任务的原理,相信大家也有了更深的认识。本篇博客内容就这些啦,thks~

参考:http://www.tuicool.com/articles/nqeIVj

5 0
原创粉丝点击