版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aliankg/article/details/70842494
Thread/Hander/Looper是Android在Java线程基础之上提供的线程通信/消息处理机制,这个众所周知,不再细说。Handler提供了两个发送延迟处理任务的api:

  1. /**
  2. * Enqueue a message into the message queue after all pending messages
  3. * before (current time + delayMillis). You will receive it in
  4. * {@link #handleMessage}, in the thread attached to this handler.
  5. *
  6. * @return Returns true if the message was successfully placed in to the
  7. * message queue. Returns false on failure, usually because the
  8. * looper processing the message queue is exiting. Note that a
  9. * result of true does not mean the message will be processed -- if
  10. * the looper is quit before the delivery time of the message
  11. * occurs then the message will be dropped.
  12. */
  13. public final boolean sendMessageDelayed(Message msg, long delayMillis)
  14.  
  15. /**
  16. * Causes the Runnable r to be added to the message queue, to be run
  17. * after the specified amount of time elapses.
  18. * The runnable will be run on the thread to which this handler
  19. * is attached.
  20. * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
  21. * Time spent in deep sleep will add an additional delay to execution.
  22. *
  23. * @param r The Runnable that will be executed.
  24. * @param delayMillis The delay (in milliseconds) until the Runnable
  25. * will be executed.
  26. *
  27. * @return Returns true if the Runnable was successfully placed in to the
  28. * message queue. Returns false on failure, usually because the
  29. * looper processing the message queue is exiting. Note that a
  30. * result of true does not mean the Runnable will be processed --
  31. * if the looper is quit before the delivery time of the message
  32. * occurs then the message will be dropped.
  33. */
  34. public final boolean postDelayed(Runnable r, long delayMillis)

问题在于,这两个delay的精度到底能有多大?如何理解?很多APP的定时处理机制都是使用这两个api递归抛延迟任务来实现的。所以有必要研究一下框架层的实现,心中有数。Android这套消息循环机制工作在最上层,距离Linux kernel的时间管理甚远。本文仍然采用跟踪分析代码的方式,基于android7.1.1。

postDelayed()实际上封装了sendMessageDelayed(),第一时间便殊途同归:

  1. public final boolean postDelayed(Runnable r, long delayMillis)
  2. {
  3. return sendMessageDelayed(getPostMessage(r), delayMillis);
  4. }
  5.  
  6. public final boolean sendMessageDelayed(Message msg, long delayMillis)
  7. {
  8. if (delayMillis < 0) {
  9. delayMillis = 0;
  10. }
  11. return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
  12. }

postDelayed()首先通过getPostMessage()将传入的Runnable对象封装成一个Message,调用sendMessageDelayed(),而sendMessageDelayed()增加了一个delay时间参数的健壮性检查,然后转化成绝对时间,调用sendMessageAtTime()。至此,再多说一句:最简单的sendMessage()和post()实际上也是sendMessageDelayed(0)的封装。所以,Handler五花八门的post/send api们本质上无差别。只是为了让使用者在简单的情况下避免手动封装Message,只需提供一个Runnable即可。Handler调用关系整理如下:

  1. post()/postDelayed()/sendMessage()->sendMessageDelayed()->sendMessageAtTime()->enqueueMessage()
  2. postAtTime()->sendMessageAtTime()->enqueueMessage()
  3. postAtFrontOfQueue()->sendMessageAtFrontOfQueue()->enqueueMessage()

最后都以enqueueMessage()告终

  1. enqueueMessage()->MessageQueue.enqueueMessage(Message msg, long when)

如前所述,这时候when已经转化成绝对系统时间。转入消息队列类MessageQueue看一下enqueueMessage()这个方法:

  1. boolean enqueueMessage(Message msg, long when) {
  2. if (msg.target == null) {
  3. throw new IllegalArgumentException("Message must have a target.");
  4. }
  5. if (msg.isInUse()) {
  6. throw new IllegalStateException(msg + " This message is already in use.");
  7. }
  8.  
  9. synchronized (this) {
  10. if (mQuitting) {
  11. IllegalStateException e = new IllegalStateException(
  12. msg.target + " sending message to a Handler on a dead thread");
  13. Log.w(TAG, e.getMessage(), e);
  14. msg.recycle();
  15. return false;
  16. }
  17.  
  18. msg.markInUse();
  19. msg.when = when;
  20. Message p = mMessages;
  21. boolean needWake;
  22. if (p == null || when == 0 || when < p.when) {
  23. // New head, wake up the event queue if blocked.
  24. msg.next = p;
  25. mMessages = msg;
  26. needWake = mBlocked;
  27. } else {
  28. // Inserted within the middle of the queue. Usually we don't have to wake
  29. // up the event queue unless there is a barrier at the head of the queue
  30. // and the message is the earliest asynchronous message in the queue.
  31. needWake = mBlocked && p.target == null && msg.isAsynchronous();
  32. Message prev;
  33. for (;;) {
  34. prev = p;
  35. p = p.next;
  36. if (p == null || when < p.when) {
  37. break;
  38. }
  39. if (needWake && p.isAsynchronous()) {
  40. needWake = false;
  41. }
  42. }
  43. msg.next = p; // invariant: p == prev.next
  44. prev.next = msg;
  45. }
  46.  
  47. // We can assume mPtr != 0 because mQuitting is false.
  48. if (needWake) {
  49. nativeWake(mPtr);
  50. }
  51. }
  52. return true;
  53. }

这个方法比较简单,采用线程安全的方式将Message插入到消息队列中,插入的新消息有三种可能成为消息队列的head:

(1)消息队列为空;

(2)参数when为0,因为此时when已经转成绝对时间,所以只有AtFrontOfQueue系列的API才会满足这个条件;

(3)当前的head Message执行时间在when之后,即消息队列中无需要在此Message之前执行的Message。

接下来就要看看消息循环(Looper)如何使用when,这是本文问题的关键。关键的方法,Looper.loop(),启动线程消息循环:

  1. /**
  2. * Run the message queue in this thread. Be sure to call
  3. * {@link #quit()} to end the loop.
  4. */
  5. public static void loop() {
  6. final Looper me = myLooper();
  7. if (me == null) {
  8. throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  9. }
  10. final MessageQueue queue = me.mQueue;
  11.  
  12. // Make sure the identity of this thread is that of the local process,
  13. // and keep track of what that identity token actually is.
  14. Binder.clearCallingIdentity();
  15. final long ident = Binder.clearCallingIdentity();
  16.  
  17. for (;;) {
  18. Message msg = queue.next(); // might block
  19. if (msg == null) {
  20. // No message indicates that the message queue is quitting.
  21. return;
  22. }
  23.  
  24. // This must be in a local variable, in case a UI event sets the logger
  25. final Printer logging = me.mLogging;
  26. if (logging != null) {
  27. logging.println(">>>>> Dispatching to " + msg.target + " " +
  28. msg.callback + ": " + msg.what);
  29. }
  30.  
  31. final long traceTag = me.mTraceTag;
  32. if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
  33. Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
  34. }
  35. try {
  36. msg.target.dispatchMessage(msg);
  37. } finally {
  38. if (traceTag != 0) {
  39. Trace.traceEnd(traceTag);
  40. }
  41. }
  42.  
  43. if (logging != null) {
  44. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
  45. }
  46.  
  47. // Make sure that during the course of dispatching the
  48. // identity of the thread wasn't corrupted.
  49. final long newIdent = Binder.clearCallingIdentity();
  50. if (ident != newIdent) {
  51. Log.wtf(TAG, "Thread identity changed from 0x"
  52. + Long.toHexString(ident) + " to 0x"
  53. + Long.toHexString(newIdent) + " while dispatching to "
  54. + msg.target.getClass().getName() + " "
  55. + msg.callback + " what=" + msg.what);
  56. }
  57.  
  58. msg.recycleUnchecked();
  59. }
  60. }

从for(;;)可以看到一次循环开始于从消息队列中去取一个消息,MessageQueue.next(),如果next()返回null,则loop()会返回,本次消息循环结束。取出消息之后,通过Handler.dispatchMessage()处理消息:

  1. msg.target.dispatchMessage(msg);

也就是说,取下一个消息的实际执行时间取决于上一个消息什么时候处理完。再看MessageQueue.next()做了什么:

  1. Message next() {
  2. // Return here if the message loop has already quit and been disposed.
  3. // This can happen if the application tries to restart a looper after quit
  4. // which is not supported.
  5. final long ptr = mPtr;
  6. if (ptr == 0) {
  7. return null;
  8. }
  9.  
  10. int pendingIdleHandlerCount = -1; // -1 only during first iteration
  11. int nextPollTimeoutMillis = 0;
  12. for (;;) {
  13. if (nextPollTimeoutMillis != 0) {
  14. Binder.flushPendingCommands();
  15. }
  16.  
  17. nativePollOnce(ptr, nextPollTimeoutMillis);
  18.  
  19. synchronized (this) {
  20. // Try to retrieve the next message. Return if found.
  21. final long now = SystemClock.uptimeMillis();
  22. Message prevMsg = null;
  23. Message msg = mMessages;
  24. if (msg != null && msg.target == null) {
  25. // Stalled by a barrier. Find the next asynchronous message in the queue.
  26. do {
  27. prevMsg = msg;
  28. msg = msg.next;
  29. } while (msg != null && !msg.isAsynchronous());
  30. }
  31. if (msg != null) {
  32. if (now < msg.when) {
  33. // Next message is not ready. Set a timeout to wake up when it is ready.
  34. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
  35. } else {
  36. // Got a message.
  37. mBlocked = false;
  38. if (prevMsg != null) {
  39. prevMsg.next = msg.next;
  40. } else {
  41. mMessages = msg.next;
  42. }
  43. msg.next = null;
  44. if (DEBUG) Log.v(TAG, "Returning message: " + msg);
  45. msg.markInUse();
  46. return msg;
  47. }
  48. } else {
  49. // No more messages.
  50. nextPollTimeoutMillis = -1;
  51. }
  52.  
  53. // Process the quit message now that all pending messages have been handled.
  54. if (mQuitting) {
  55. dispose();
  56. return null;
  57. }
  58.  
  59. // If first time idle, then get the number of idlers to run.
  60. // Idle handles only run if the queue is empty or if the first message
  61. // in the queue (possibly a barrier) is due to be handled in the future.
  62. if (pendingIdleHandlerCount < 0
  63. && (mMessages == null || now < mMessages.when)) {
  64. pendingIdleHandlerCount = mIdleHandlers.size();
  65. }
  66. if (pendingIdleHandlerCount <= 0) {
  67. // No idle handlers to run. Loop and wait some more.
  68. mBlocked = true;
  69. continue;
  70. }
  71.  
  72. if (mPendingIdleHandlers == null) {
  73. mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
  74. }
  75. mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
  76. }
  77.  
  78. // Run the idle handlers.
  79. // We only ever reach this code block during the first iteration.
  80. for (int i = 0; i < pendingIdleHandlerCount; i++) {
  81. final IdleHandler idler = mPendingIdleHandlers[i];
  82. mPendingIdleHandlers[i] = null; // release the reference to the handler
  83.  
  84. boolean keep = false;
  85. try {
  86. keep = idler.queueIdle();
  87. } catch (Throwable t) {
  88. Log.wtf(TAG, "IdleHandler threw exception", t);
  89. }
  90.  
  91. if (!keep) {
  92. synchronized (this) {
  93. mIdleHandlers.remove(idler);
  94. }
  95. }
  96. }
  97.  
  98. // Reset the idle handler count to 0 so we do not run them again.
  99. pendingIdleHandlerCount = 0;
  100.  
  101. // While calling an idle handler, a new message could have been delivered
  102. // so go back and look again for a pending message without waiting.
  103. nextPollTimeoutMillis = 0;
  104. }
  105. }

看到next()实际上也有一个for(;;),而出口只有两个:消息队列已经退出,返回null;找到了一个合适的消息,将其返回。如果没有合适的消息,或者消息队列为空,会block或者由IdleHandler处理,不在本文问题范畴,暂不展开。主要看找到合适的消息的逻辑:

  1. if (msg != null) {
  2. if (now < msg.when) {
  3. // Next message is not ready. Set a timeout to wake up when it is ready.
  4. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
  5. } else {
  6. // Got a message.
  7. mBlocked = false;
  8. if (prevMsg != null) {
  9. prevMsg.next = msg.next;
  10. } else {
  11. mMessages = msg.next;
  12. }
  13. msg.next = null;
  14. if (DEBUG) Log.v(TAG, "Returning message: " + msg);
  15. msg.markInUse();
  16. return msg;
  17. }
  18. } else {
  19. // No more messages.
  20. nextPollTimeoutMillis = -1;
  21. }

可以看到,如果在消息队列中顺序找到了一个消息msg(前文分析过,消息队列的插入是由when顺序排列,所以如果当前的消息没有到执行时间,其后的也一定不会到),当前的系统时间小于msg.when,那么会计算一个timeout,以便在到执行时间时wake up;如果当前系统时间大于或等于msg.when,那么会返回msg给Looper.loop()。所以这个逻辑只能保证在when之前消息不被处理,不能够保证一定在when时被处理。很好理解:
(1)在Loop.loop()中是顺序处理消息,如果前一个消息处理耗时较长,完成之后已经超过了when,消息不可能在when时间点被处理。

(2)即使when的时间点没有被处理其他消息所占用,线程也有可能被调度失去cpu时间片。

(3)在等待时间点when的过程中有可能入队处理时间更早的消息,会被优先处理,又增加了(1)的可能性。

所以由上述三点可知,Handler提供的指定处理时间的api诸如postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime(),只能保证在指定时间之前不被执行,不能保证在指定时间点被执行。

from:https://blog.csdn.net/zhanglianyu00/article/details/70842494

【转】从源码分析Handler的postDelayed为什么可以延时?的更多相关文章

  1. Android Handler处理机制 ( 一 )(图+源码分析)——Handler,Message,Looper,MessageQueue

    android的消息处理机制(图+源码分析)——Looper,Handler,Message 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习 google大牛们的设计思想. ...

  2. 【Android】Handler、Looper源码分析

    一.前言 源码分析使用的版本是 4.4.2_r1. Handler和Looper的入门知识以及讲解可以参考我的另外一篇博客:Android Handler机制 简单而言:Handler和Looper是 ...

  3. Handler、Looper、MessageQueue、Thread源码分析

    关于这几个之间的关系以及源码分析的文章应该挺多的了,不过既然学习了,还是觉得整理下,印象更深刻点,嗯,如果有错误的地方欢迎反馈. 转载请注明出处:http://www.cnblogs.com/John ...

  4. 7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解

    从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, res ...

  5. 源码分析Android Handler是如何实现线程间通信的

    源码分析Android Handler是如何实现线程间通信的 Handler作为Android消息通信的基础,它的使用是每一个开发者都必须掌握的.开发者从一开始就被告知必须在主线程中进行UI操作.但H ...

  6. Netty源码分析第4章(pipeline)---->第2节: handler的添加

    Netty源码分析第四章: pipeline 第二节: Handler的添加 添加handler, 我们以用户代码为例进行剖析: .childHandler(new ChannelInitialize ...

  7. Netty源码分析第4章(pipeline)---->第3节: handler的删除

    Netty源码分析第四章: pipeline 第三节: handler的删除 上一小节我们学习了添加handler的逻辑操作, 这一小节我们学习删除handler的相关逻辑 如果用户在业务逻辑中进行c ...

  8. Android源码分析笔记--Handler机制

    #Handler机制# Handler机制实际就是实现一个 异步消息循环处理器 Handler的真正意义: 异步处理 Handler机制的整体表述: 消息处理线程: 在Handler机制中,异步消息处 ...

  9. Android7.0 Phone应用源码分析(二) phone来电流程分析

    接上篇博文:Android7.0 Phone应用源码分析(一) phone拨号流程分析 今天我们再来分析下Android7.0 的phone的来电流程 1.1TelephonyFramework 当有 ...

随机推荐

  1. Chapter 5 Blood Type——15

    I hesitated, torn, but then the first bell sent me hurrying out the door — with a last glance confir ...

  2. ELK-log4j2异步输出+logstash

    1.pom.xml配置文件 <dependency> <groupId>log4j</groupId> <artifactId>log4j</ar ...

  3. springboot+mybatis+dubbo+aop日志第三篇

    AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等等. Spring AOP模块提供截取拦截应用程序的拦截器,例如,当执行方法时,可以在执行方法之前或之后添加 ...

  4. ASP.NET Core 2.1 : 十三.httpClient.GetAsync 报SSL错误的问题

    不知什么时候 ,出现了这样的一个奇怪问题,简单的httpClient.GetAsync("xxxx")居然报错了.(ASP.NET Core 系列目录) 一.问题描述 把原来的程序 ...

  5. Docker最全教程——从理论到实战(一)

    容器是应用走向云端之后必然的发展趋势,因此笔者非常乐于和大家分享我们这段时间对容器的理解.心得和实践. 本篇教程持续编写了2个星期左右,只是为了大家更好地了解.理解和消化这个技术,能够搭上这波车. 你 ...

  6. .Net语言 APP开发平台——Smobiler学习日志:如何快速实现按钮组功能

    最前面的话:Smobiler是一个在VS环境中使用.Net语言来开发APP的开发平台,也许比Xamarin更方便 一.目标样式 我们要实现上图中的效果,需要如下的操作: 1.从工具栏上的“Smobil ...

  7. Captcha服务(后续1)

    既然标题为后续,就要放一下上一篇文章使用.Net Core 2.1开发Captcha图片验证码服务 继续挖坑 时隔7个月再次继续自己在GitHub上挖的坑 https://github.com/Puz ...

  8. sql字符串包含单引号

    ad'min select  * from user where name ='ad''min'

  9. 为什么我的gridview.DataKeys.count总是为零?并提示索引超出范围

    第一个原因 你没有设置DataKeyNames属性, 第二个原因 你的DataSource是NUll值 第二个原因 DataKeyNames字段区分大小写

  10. 【Config】类库读取自己的配置文件,配置文件的扩展

    我们在项目中一般都是使用统一的项目文件配置,所有的配置和自定义的字段都写在一个web.config或者App.config文件中.一般平时我们也没有发现问题,确实这么写没有问题,但是就是如果写的多了就 ...