Android Handler postDelayed的原理

前言

我们经常用Handler中的postDelayed方法进行延迟操作,像这样

new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //操作内容
            }
        },100);

我们都知道Handler的机制是将消息通过sendMessage()放入到MessageQueen中 然后looper轮训器通过轮训取出MessageQueen中的消息回调目标Handler中的handlerMessage()。

可是postDelayed(),这个方法他的底层是怎么做的呢?是延迟把消息放入到MessageQueen的吗,点进去看看,发现不是

一.源码分析

1.点进去看postDelayed()中的方法。里面调用sendMessageDelayed方法,和post() 里面调用的方法一样。

   public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    

2.我们再点进去看下sendMessageDelayed()方法,

 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

里面调用了sendMessageAtTime(),这里的SystemClock.uptimeMillis()是获取系统从开机启动到现在的时间,期间不包括休眠的时间,这里获得到的时间是一个相对的时间,而不是通过获取当前的时间(绝对时间)。

而之所以使用这种方式来计算时间,而不是获得当前currenttime来计算,在于handler会受到阻塞,挂起状态,睡眠等,这些时候是不应该执行的;如果使用绝对时间的话,就会抢占资源来执行当前handler的内容,显然这是不应该出现的情况,所以要避免。

3.点进sendMessageAtTime()方法看看

    public boolean sendMessageAtTime(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);
    }

追到这里依然没有看到,他在存放的时候有什么不同,但是显然证实了消息不是延迟放进MessageQueen的,那是肿么处理的,是在轮训的时候处理的吗?,

4.我们点进Looper中看一下,主要代码,Looper中的looper调用了MessageQueen中的next方法,难道是在next()方法中处理的?

 public static void loop() {
···

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
      ···

5.我们点进MessageQueen中的next()方法

  for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);
    ···
                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;
                }

           
               
        ···
        }
}

很贴心的给出了注释解释“ Next message is not ready. Set a timeout to wake up when it is ready.”,翻译“下一条消息尚未准备好。设置一个超时,以便在准备就绪时唤醒。”

when就是uptimeMillis, for (;;) 相当于while(true),如果头部的这个Message是有延迟而且延迟时间没到的(now < msg.when),不返回message 而且会计算一下时间(保存为变量nextPollTimeoutMillis),然后再循环的时候判断如果这个Message有延迟,就调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞。nativePollOnce()的作用类似与object.wait()。得出结论是通过阻塞实现的。

6.但是如果在阻塞这段时间里有无延迟message又加入MessageQueen中又是怎么实现立即处理这个message的呢?,我们看MessageQueen中放入消息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;
    }

在这里p 是现在消息队列中的头部消息,我们看到| when < p.when 的时候它交换了放入message与原来消息队列头部P的位置,并且 needWake = mBlocked; (在next()中当消息为延迟消息的时候mBlocked=true),继续向下看 当needWake =true的时候nativeWake(mPtr)(唤起线程)

一切都解释的通了,如果当前插入的消息不是延迟message,或比当前的延迟短,这个消息就会插入头部并且唤起线程来

二.整理

我们把我们跟踪的所有信息整理下

1.消息是通过MessageQueen中的enqueueMessage()方法加入消息队列中的,并且它在放入中就进行好排序,链表头的延迟时间小,尾部延迟时间最大

2.Looper.loop()通过MessageQueue中的next()去取消息

3.next()中如果当前链表头部消息是延迟消息,则根据延迟时间进行消息队列会阻塞,不返回给Looper message,知道时间到了,返回给message

4.如果在阻塞中有新的消息插入到链表头部则唤醒线程

5.Looper将新消息交给回调给handler中的handleMessage后,继续调用MessageQueen的next()方法,如果刚刚的延迟消息还是时间未到,则计算时间继续阻塞

三总结

handler.postDelay() 的实现 是通过MessageQueue中执行时间顺序排列,消息队列阻塞,和唤醒的方式结合实现的。

如果真的是通过延迟将消息放入到MessageQueen中,那放入多个延迟消息就要维护多个定时器。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值