北京市建设监理协会网站,谷歌商店下载官方正版,网站被k申诉,青海保险网站建设公司Handler postDelayed的实现原理
问题描述
Handler.postDelayed()的原理是如何保证延时执行的#xff1f; 扩展#xff1a;这样实现的好处是什么#xff1f;
题目分析
猜测一下
以我们对Handler的了解#xff0c;内部使用了Looper对消息队列进行循环获取执行#xff0…Handler postDelayed的实现原理
问题描述
Handler.postDelayed()的原理是如何保证延时执行的 扩展这样实现的好处是什么
题目分析
猜测一下
以我们对Handler的了解内部使用了Looper对消息队列进行循环获取执行所以我们估计postDelayed()是Handler内部搞了一个定时器 定时器到了delayed的时间就把消息加入到消息队列中让looper在循环获取到该消息并执行。
真的是这样吗如果不是为什么
我们来追溯一下源码
消息是怎样入队的
首先调用的是sendMessageDelayed方法 public final boolean postDelayed(NonNull Runnable r, long delayMillis) {return sendMessageDelayed(getPostMessage(r), delayMillis);}
sendMessageDelayed计算了消息执行的准确时间当前时间加上延时时间public final boolean sendMessageDelayed(NonNull Message msg, long delayMillis) {if (delayMillis 0) {delayMillis 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() delayMillis);}public boolean sendMessageAtTime(NonNull Message msg, long uptimeMillis) {MessageQueue queue mQueue;if (queue null) {RuntimeException e new RuntimeException(this sendMessageAtTime() called with no mQueue);Log.w(Looper, e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);}private boolean enqueueMessage(NonNull MessageQueue queue, NonNull Message msg,long uptimeMillis) {msg.target this;msg.workSourceUid ThreadLocalWorkSource.getUid();if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}
Message的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 dont 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.nextprev.next msg;}// We can assume mPtr ! 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}
查看源码我们发现并不是像我们推测的那样使用定时器加入队列而是简单计算了消息开始执行的时间之后就加入队列了。 MessageQueue中Message的结构就是一个简单的单向链表只保存了链表头部的引用。 我们分析一下入队过程
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 {···}
如果链表头为空或者延时时间已经到了则放到列表头唤醒阻塞队列
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.nextprev.next msg;
否则遍历链表安装when的时间顺序插入消息注意when SystemClock.uptimeMillis() delayMillis
Looper是如何出来延时消息的
我们看看Looper的loop()方法
for (;;) {Message msg queue.next(); // might blockif (msg null) {// No message indicates that the message queue is quitting.return;}...
原来是调用了MessageQueue的next方法注释说明会阻塞。
我们看下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 iterationint 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 handlerboolean 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方法中对链表头部的Message的执行时间进行了判断如果当前时间小于msg.when则计算阻塞时间 然后在循环开始的时候判断如果这个Message有延迟就调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞。 有兴趣的童鞋可以下Android源码看看native层的nativePollOnce是如何实现的作用与object.wait()类似只不过是使用了Native的方法对这个线程精确时间的唤醒。 唤醒之后loop()就能拿到对应的message了。
参考答案
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()阻塞 直到阻塞时间到或者下一次有Message进队再次唤醒
比我们的猜测好在哪里
1、如果用我们的猜测方案我们每添加一个延时消息就需要维护一个定时器如果消息多耗费性能极大
2、使用定时器到了延时时间再加入队列如果队列中任务比较多则延时的精度会大大降低精度不如Google的方案。