【转】从源码分析Handler的postDelayed为什么可以延时?
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aliankg/article/details/70842494
Thread/Hander/Looper是Android在Java线程基础之上提供的线程通信/消息处理机制,这个众所周知,不再细说。Handler提供了两个发送延迟处理任务的api:
- /**
- * Enqueue a message into the message queue after all pending messages
- * before (current time + delayMillis). You will receive it in
- * {@link #handleMessage}, in the thread attached to this handler.
- *
- * @return Returns true if the message was successfully placed in to the
- * message queue. Returns false on failure, usually because the
- * looper processing the message queue is exiting. Note that a
- * result of true does not mean the message will be processed -- if
- * the looper is quit before the delivery time of the message
- * occurs then the message will be dropped.
- */
- public final boolean sendMessageDelayed(Message msg, long delayMillis)
- /**
- * Causes the Runnable r to be added to the message queue, to be run
- * after the specified amount of time elapses.
- * The runnable will be run on the thread to which this handler
- * is attached.
- * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
- * Time spent in deep sleep will add an additional delay to execution.
- *
- * @param r The Runnable that will be executed.
- * @param delayMillis The delay (in milliseconds) until the Runnable
- * will be executed.
- *
- * @return Returns true if the Runnable was successfully placed in to the
- * message queue. Returns false on failure, usually because the
- * looper processing the message queue is exiting. Note that a
- * result of true does not mean the Runnable will be processed --
- * if the looper is quit before the delivery time of the message
- * occurs then the message will be dropped.
- */
- public final boolean postDelayed(Runnable r, long delayMillis)
问题在于,这两个delay的精度到底能有多大?如何理解?很多APP的定时处理机制都是使用这两个api递归抛延迟任务来实现的。所以有必要研究一下框架层的实现,心中有数。Android这套消息循环机制工作在最上层,距离Linux kernel的时间管理甚远。本文仍然采用跟踪分析代码的方式,基于android7.1.1。
postDelayed()实际上封装了sendMessageDelayed(),第一时间便殊途同归:
- public final boolean postDelayed(Runnable r, long delayMillis)
- {
- return sendMessageDelayed(getPostMessage(r), delayMillis);
- }
- public final boolean sendMessageDelayed(Message msg, long delayMillis)
- {
- if (delayMillis < 0) {
- delayMillis = 0;
- }
- return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
- }
postDelayed()首先通过getPostMessage()将传入的Runnable对象封装成一个Message,调用sendMessageDelayed(),而sendMessageDelayed()增加了一个delay时间参数的健壮性检查,然后转化成绝对时间,调用sendMessageAtTime()。至此,再多说一句:最简单的sendMessage()和post()实际上也是sendMessageDelayed(0)的封装。所以,Handler五花八门的post/send api们本质上无差别。只是为了让使用者在简单的情况下避免手动封装Message,只需提供一个Runnable即可。Handler调用关系整理如下:
- post()/postDelayed()/sendMessage()->sendMessageDelayed()->sendMessageAtTime()->enqueueMessage()
- postAtTime()->sendMessageAtTime()->enqueueMessage()
- postAtFrontOfQueue()->sendMessageAtFrontOfQueue()->enqueueMessage()
最后都以enqueueMessage()告终
- enqueueMessage()->MessageQueue.enqueueMessage(Message msg, long when)
如前所述,这时候when已经转化成绝对系统时间。转入消息队列类MessageQueue看一下enqueueMessage()这个方法:
- boolean enqueueMessage(Message msg, long when) {
- 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.markInUse();
- msg.when = when;
- 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;
- }
- // We can assume mPtr != 0 because mQuitting is false.
- if (needWake) {
- nativeWake(mPtr);
- }
- }
- return true;
- }
这个方法比较简单,采用线程安全的方式将Message插入到消息队列中,插入的新消息有三种可能成为消息队列的head:
(1)消息队列为空;
(2)参数when为0,因为此时when已经转成绝对时间,所以只有AtFrontOfQueue系列的API才会满足这个条件;
(3)当前的head Message执行时间在when之后,即消息队列中无需要在此Message之前执行的Message。
接下来就要看看消息循环(Looper)如何使用when,这是本文问题的关键。关键的方法,Looper.loop(),启动线程消息循环:
- /**
- * Run the message queue in this thread. Be sure to call
- * {@link #quit()} to end the loop.
- */
- public static void loop() {
- final Looper me = myLooper();
- if (me == null) {
- throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
- }
- final MessageQueue queue = me.mQueue;
- // Make sure the identity of this thread is that of the local process,
- // and keep track of what that identity token actually is.
- Binder.clearCallingIdentity();
- final long ident = Binder.clearCallingIdentity();
- for (;;) {
- Message msg = queue.next(); // might block
- if (msg == null) {
- // No message indicates that the message queue is quitting.
- return;
- }
- // This must be in a local variable, in case a UI event sets the logger
- final Printer logging = me.mLogging;
- if (logging != null) {
- logging.println(">>>>> Dispatching to " + msg.target + " " +
- msg.callback + ": " + msg.what);
- }
- final long traceTag = me.mTraceTag;
- if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
- Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
- }
- try {
- msg.target.dispatchMessage(msg);
- } finally {
- if (traceTag != 0) {
- Trace.traceEnd(traceTag);
- }
- }
- if (logging != null) {
- logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
- }
- // Make sure that during the course of dispatching the
- // identity of the thread wasn't corrupted.
- final long newIdent = Binder.clearCallingIdentity();
- if (ident != newIdent) {
- Log.wtf(TAG, "Thread identity changed from 0x"
- + Long.toHexString(ident) + " to 0x"
- + Long.toHexString(newIdent) + " while dispatching to "
- + msg.target.getClass().getName() + " "
- + msg.callback + " what=" + msg.what);
- }
- msg.recycleUnchecked();
- }
- }
从for(;;)可以看到一次循环开始于从消息队列中去取一个消息,MessageQueue.next(),如果next()返回null,则loop()会返回,本次消息循环结束。取出消息之后,通过Handler.dispatchMessage()处理消息:
- msg.target.dispatchMessage(msg);
也就是说,取下一个消息的实际执行时间取决于上一个消息什么时候处理完。再看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) {
- return null;
- }
- int pendingIdleHandlerCount = -1; // -1 only during first iteration
- int nextPollTimeoutMillis = 0;
- for (;;) {
- if (nextPollTimeoutMillis != 0) {
- Binder.flushPendingCommands();
- }
- nativePollOnce(ptr, nextPollTimeoutMillis);
- synchronized (this) {
- // Try to retrieve the next message. Return if found.
- 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;
- 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;
- }
- // 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);
- }
- // Run the idle handlers.
- // We only ever reach this code block during the first iteration.
- 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 {
- 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.
- 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.
- nextPollTimeoutMillis = 0;
- }
- }
看到next()实际上也有一个for(;;),而出口只有两个:消息队列已经退出,返回null;找到了一个合适的消息,将其返回。如果没有合适的消息,或者消息队列为空,会block或者由IdleHandler处理,不在本文问题范畴,暂不展开。主要看找到合适的消息的逻辑:
- 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(前文分析过,消息队列的插入是由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为什么可以延时?的更多相关文章
- Android Handler处理机制 ( 一 )(图+源码分析)——Handler,Message,Looper,MessageQueue
android的消息处理机制(图+源码分析)——Looper,Handler,Message 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习 google大牛们的设计思想. ...
- 【Android】Handler、Looper源码分析
一.前言 源码分析使用的版本是 4.4.2_r1. Handler和Looper的入门知识以及讲解可以参考我的另外一篇博客:Android Handler机制 简单而言:Handler和Looper是 ...
- Handler、Looper、MessageQueue、Thread源码分析
关于这几个之间的关系以及源码分析的文章应该挺多的了,不过既然学习了,还是觉得整理下,印象更深刻点,嗯,如果有错误的地方欢迎反馈. 转载请注明出处:http://www.cnblogs.com/John ...
- 7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解
从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, res ...
- 源码分析Android Handler是如何实现线程间通信的
源码分析Android Handler是如何实现线程间通信的 Handler作为Android消息通信的基础,它的使用是每一个开发者都必须掌握的.开发者从一开始就被告知必须在主线程中进行UI操作.但H ...
- Netty源码分析第4章(pipeline)---->第2节: handler的添加
Netty源码分析第四章: pipeline 第二节: Handler的添加 添加handler, 我们以用户代码为例进行剖析: .childHandler(new ChannelInitialize ...
- Netty源码分析第4章(pipeline)---->第3节: handler的删除
Netty源码分析第四章: pipeline 第三节: handler的删除 上一小节我们学习了添加handler的逻辑操作, 这一小节我们学习删除handler的相关逻辑 如果用户在业务逻辑中进行c ...
- Android源码分析笔记--Handler机制
#Handler机制# Handler机制实际就是实现一个 异步消息循环处理器 Handler的真正意义: 异步处理 Handler机制的整体表述: 消息处理线程: 在Handler机制中,异步消息处 ...
- Android7.0 Phone应用源码分析(二) phone来电流程分析
接上篇博文:Android7.0 Phone应用源码分析(一) phone拨号流程分析 今天我们再来分析下Android7.0 的phone的来电流程 1.1TelephonyFramework 当有 ...
随机推荐
- Chapter 5 Blood Type——15
I hesitated, torn, but then the first bell sent me hurrying out the door — with a last glance confir ...
- ELK-log4j2异步输出+logstash
1.pom.xml配置文件 <dependency> <groupId>log4j</groupId> <artifactId>log4j</ar ...
- springboot+mybatis+dubbo+aop日志第三篇
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等等. Spring AOP模块提供截取拦截应用程序的拦截器,例如,当执行方法时,可以在执行方法之前或之后添加 ...
- ASP.NET Core 2.1 : 十三.httpClient.GetAsync 报SSL错误的问题
不知什么时候 ,出现了这样的一个奇怪问题,简单的httpClient.GetAsync("xxxx")居然报错了.(ASP.NET Core 系列目录) 一.问题描述 把原来的程序 ...
- Docker最全教程——从理论到实战(一)
容器是应用走向云端之后必然的发展趋势,因此笔者非常乐于和大家分享我们这段时间对容器的理解.心得和实践. 本篇教程持续编写了2个星期左右,只是为了大家更好地了解.理解和消化这个技术,能够搭上这波车. 你 ...
- .Net语言 APP开发平台——Smobiler学习日志:如何快速实现按钮组功能
最前面的话:Smobiler是一个在VS环境中使用.Net语言来开发APP的开发平台,也许比Xamarin更方便 一.目标样式 我们要实现上图中的效果,需要如下的操作: 1.从工具栏上的“Smobil ...
- Captcha服务(后续1)
既然标题为后续,就要放一下上一篇文章使用.Net Core 2.1开发Captcha图片验证码服务 继续挖坑 时隔7个月再次继续自己在GitHub上挖的坑 https://github.com/Puz ...
- sql字符串包含单引号
ad'min select * from user where name ='ad''min'
- 为什么我的gridview.DataKeys.count总是为零?并提示索引超出范围
第一个原因 你没有设置DataKeyNames属性, 第二个原因 你的DataSource是NUll值 第二个原因 DataKeyNames字段区分大小写
- 【Config】类库读取自己的配置文件,配置文件的扩展
我们在项目中一般都是使用统一的项目文件配置,所有的配置和自定义的字段都写在一个web.config或者App.config文件中.一般平时我们也没有发现问题,确实这么写没有问题,但是就是如果写的多了就 ...