你真的懂Handler.postDelayed()的原理吗?
来源:互联网 发布:本地系统 网络受限 编辑:程序博客网 时间:2024/06/08 12:27
转载自http://www.dss886.com/2016/08/17/01/
使用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()
的调用路径:
- Handler.postDelayed(Runnable r, long delayMillis)
- Handler.sendMessageDelayed(getPostMessage(r), delayMillis)
- Handler.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
- Handler.enqueueMessage(queue, msg, uptimeMillis)
- MessageQueue.enqueueMessage(msg, uptimeMillis)
最后发现Handler没有自己处理Delay,而是交给了MessageQueue处理,我们继续跟进去看看MessageQueue又做了什么:
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 { ...}
MessageQueue中组织Message的结构就是一个简单的单向链表,只保存了链表头部的引用(果然只是个Queue啊)。在enqueueMessage()
的时候把应该执行的时间(上面Hanlder调用路径的第三步延迟已经加上了现有时间,所以叫when)设置到msg里面,并没有进行处理……WTF?
继续跟进去看看Looper是怎么读取MessageQueue的,在loop()
方法内:
for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ...}
原来调用的是MessageQueue.next()
,还贴心地注释了这个方法可能会阻塞,点进去看看:
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; } ... }}
可以看到,在这个方法内,如果头部的这个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()
的时候发现,似乎刚才遗漏了什么东西:
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 { ...}...// We can assume mPtr != 0 because mQuitting is false.if (needWake) { nativeWake(mPtr);}
这个needWake变量和nativeWake()
方法似乎是唤醒线程啊?继续看看mBlocked是什么:
Message next() { for (;;) { ... if (msg != null) { ... } else { // Got a message. mBlocked = false; ... } ... } ... if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } ...}
就是这里了,在next()
方法内部,如果有阻塞(没有消息了或者只有Delay的消息),会把mBlocked这个变量标记为true,在下一个Message进队时会判断这个message的位置,如果在队首就会调用nativeWake()
方法唤醒线程!
现在整个调用流程就比较清晰了,以刚刚的问题为例:
postDelay()
一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()
阻塞,Looper阻塞;- 紧接着
post()
一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()
方法唤醒线程; MessageQueue.next()
方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;- Looper处理完这个消息再次调用
next()
方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()
阻塞; - 直到阻塞时间到或者下一次有Message进队;
这样,基本上就能保证Handler.postDelayed()
发布的消息能在相对精确的时间被传递给Looper进行处理而又不会阻塞队列了。
- 你真的懂Handler.postDelayed()的原理吗?
- AS中用handler.postdelayed的问题
- handler的sendMessage、removeMessages、post、PostDelayed等
- Android消息机制,你真的了解Handler吗?
- 邮件原理你真的造吗
- 你真的懂吗?(Android内存泄露之Handler)
- Android之Handler的postDelayed()方法的用法
- Android之Handler的postDelayed()方法的用法
- 采用Handler的postDelayed实现安卓定时器
- Handler和postDelayed方法和removeCallbacks方法的使用
- Android中三种计时器Timer、CountDownTimer、handler.postDelayed的使用
- Android Handler removeMessages引发postDelayed失效的问题
- 如何理解handler.postDelayed方法的用途与意义?
- 从源码分析Handler的postDelayed为什么可以延时?
- Android Handler.removeMessage移除所有postDelayed的问题
- 你真的懂吗
- Android 消息机制——你真的了解Handler?
- Android 消息机制——你真的了解Handler?
- oracle 12c R2 rac安装
- 【LeetCode】542. 01 Matrix
- 归并排序
- IntelliJ Idea 常用快捷键列表
- Lua知识点九
- 你真的懂Handler.postDelayed()的原理吗?
- 《算法心得-高效算法的奥秘(原书第2版)》pdf
- 学习记录
- POJ 3186 Treats for the Cows(区间DP)
- Invoking Page() in async task.
- 中英文对照 —— 饮食与美食
- maven pom.xml添加日志支持
- druid 连接池
- Unity plyGame插件技能模块分析