Handler.postDelayed()工作原理

转载:https://blog.csdn.net/qingtiantianqing/article/details/72783952

原文:

使用handler发送消息时有两种方式,post(Runnable r)post(Runnable r, long delayMillis)都是将指定Runnable(包装成PostMessage)加入到MessageQueue中,然后Looper不断从MessageQueue中读取Message进行处理。

然而我在使用的时候就一直有一个疑问,类似Looper这种「轮询」的工作方式,如果在每次读取时判断时间,是无论如何都会有误差的。但是在测试中发现Delay的误差并没有大于我使用System.out.println(System.currentTimeMillis())所产生的误差,几乎可以忽略不计,那么Android是怎么做到的呢?

Handler.postDelayed()的调用路径

一步一步跟一下Handler.postDelayed()的调用路径:

  1. Handler.postDelayed(Runnable r, long delayMillis)
  2. Handler.sendMessageDelayed(getPostMessage(r), delayMillis)
  3. Handler.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
  4. Handler.enqueueMessage(queue, msg, uptimeMillis)
  5. MessageQueue.enqueueMessage(msg, uptimeMillis)

最后发现Handler没有自己处理Delay,而是交给了MessageQueue处理,我们继续跟进去看看MessageQueue又做了什么:

 
  1. msg.markInUse();

  2. msg.when = when;

  3. Message p = mMessages;

  4. boolean needWake;

  5. if (p == null || when == 0 || when < p.when) {

  6. // New head, wake up the event queue if blocked.

  7. msg.next = p;

  8. mMessages = msg;

  9. needWake = mBlocked;

  10. } else {

  11. ...

  12. }

MessageQueue中组织Message的结构就是一个简单的单向链表,只保存了链表头部的引用(果然只是个Queue啊)。在enqueueMessage()的时候把应该执行的时间(上面Hanlder调用路径的第三步延迟已经加上了现有时间,所以叫when)设置到msg里面,并没有进行处理……WTF?

继续跟进去看看Looper是怎么读取MessageQueue的,在loop()方法内:

 
  1. for (;;) {

  2. Message msg = queue.next(); // might block

  3. if (msg == null) {

  4. // No message indicates that the message queue is quitting.

  5. return;

  6. }

  7. ...

  8. }

原来调用的是MessageQueue.next(),还贴心地注释了这个方法可能会阻塞,点进去看看:

 
  1. for (;;) {

  2. if (nextPollTimeoutMillis != 0) {

  3. Binder.flushPendingCommands();

  4. }

  5.  
  6. nativePollOnce(ptr, nextPollTimeoutMillis);

  7.  
  8. synchronized (this) {

  9. // Try to retrieve the next message. Return if found.

  10. final long now = SystemClock.uptimeMillis();

  11. Message prevMsg = null;

  12. Message msg = mMessages;

  13. if (msg != null && msg.target == null) {

  14. // Stalled by a barrier. Find the next asynchronous message in the queue.

  15. do {

  16. prevMsg = msg;

  17. msg = msg.next;

  18. } while (msg != null && !msg.isAsynchronous());

  19. }

  20. if (msg != null) {

  21. if (now < msg.when) {

  22. // Next message is not ready. Set a timeout to wake up when it is ready.

  23. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

  24. } else {

  25. // Got a message.

  26. mBlocked = false;

  27. if (prevMsg != null) {

  28. prevMsg.next = msg.next;

  29. } else {

  30. mMessages = msg.next;

  31. }

  32. msg.next = null;

  33. if (DEBUG) Log.v(TAG, "Returning message: " + msg);

  34. msg.markInUse();

  35. return msg;

  36. }

  37. } else {

  38. // No more messages.

  39. nextPollTimeoutMillis = -1;

  40. }

  41. ...

  42. }

  43. }

可以看到,在这个方法内,如果头部的这个Message是有延迟而且延迟时间没到的(now < msg.when),会计算一下时间(保存为变量nextPollTimeoutMillis),然后在循环开始的时候判断如果这个Message有延迟,就调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞。nativePollOnce()的作用类似与object.wait(),只不过是使用了Native的方法对这个线程精确时间的唤醒。

精确延时的问题到这里就算是基本解决了,不过我又产生了一个新的疑问:如果Message会阻塞MessageQueue的话,那么先postDelay10秒一个Runnable A,消息队列会一直阻塞,然后我再post一个Runnable B,B岂不是会等A执行完了再执行?正常使用时显然不是这样的,那么问题出在哪呢?

再来一步一步顺一下Looper、Handler、MessageQueue的调用执行逻辑,重新看到MessageQueue.enqueueMessage()的时候发现,似乎刚才遗漏了什么东西:

 
  1. msg.markInUse();

  2. msg.when = when;

  3. Message p = mMessages;

  4. boolean needWake;

  5. if (p == null || when == 0 || when < p.when) {

  6. // New head, wake up the event queue if blocked.

  7. msg.next = p;

  8. mMessages = msg;

  9. needWake = mBlocked;

  10. } else {

  11. ...

  12. }

  13. ...

  14. // We can assume mPtr != 0 because mQuitting is false.

  15. if (needWake) {

  16. nativeWake(mPtr);

  17. }

这个needWake变量和nativeWake()方法似乎是唤醒线程啊?继续看看mBlocked是什么:

 
  1. Message next() {

  2. for (;;) {

  3. ...

  4. if (msg != null) {

  5. ...

  6. } else {

  7. // Got a message.

  8. mBlocked = false;

  9. ...

  10. }

  11. ...

  12. }

  13. ...

  14. if (pendingIdleHandlerCount <= 0) {

  15. // No idle handlers to run. Loop and wait some more.

  16. mBlocked = true;

  17. continue;

  18. }

  19. ...

  20. }

就是这里了,在next()方法内部,如果有阻塞(没有消息了或者只有Delay的消息),会把mBlocked这个变量标记为true,在下一个Message进队时会判断这个message的位置,如果在队首就会调用nativeWake()方法唤醒线程!

现在整个调用流程就比较清晰了,以刚刚的问题为例:

  1. postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;
  2. 紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;
  3. MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;
  4. Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;
  5. 直到阻塞时间到或者下一次有Message进队;

这样,基本上就能保证Handler.postDelayed()发布的消息能在相对精确的时间被传递给Looper进行处理而又不会阻塞队列了。

 

 

另外,这里在阅读原文的基础上添加一点思考内容:

 

MessageQueue会根据post delay的时间排序放入到链表中,链表头的时间小,尾部时间最大。因此能保证时间Delay最长的不会block住时间短的。当每次post message的时候会进入到MessageQueue的next()方法,会根据其delay时间和链表头的比较,如果更短则,放入链表头,并且看时间是否有delay,如果有,则block,等待时间到来唤醒执行,否则将唤醒立即执行。

 

所以handler.postDelay并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以MessageQueue的时间顺序排列和唤醒的方式结合实现的。使用后者的方式,我认为是集中式的统一管理了所有message,而如果像前者的话,有多少个delay message,则需要起多少个定时器。前者由于有了排序,而且保存的每个message的执行时间,因此只需一个定时器按顺序next即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值