Handler延时处理消息的流程

前言

昨天碰到一个关于handler的问题,当我使用sendEmptyMessageDelayed这个方法,在手机熄屏的情况下并没有按时发送消息,而且中间的时间还不固定,有时候短,有时候长,但是在亮屏和充电情况下,没有该问题。
难道handler的延时本身就不准?带着疑问我看了下handler的运行机制,找到了答案!

next()和enqueueMessage()

本篇文章默认为你已经知道handler的使用发法,所以我不会对handler的基础只是做介绍了。

当我使用sendEmptyMessageDelayed的时候,源码依次的调用顺序是;
sendEmptyMessageDelayed(int what, long delayMillis) —>
sendMessageDelayed(Message msg, long delayMillis) —>
sendMessageAtTime(Message msg, long uptimeMillis) —>
enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)

其中enqueueMessage调用的MessageQueue的boolean enqueueMessage(Message msg, long when),接下来我们来看看该函数。

文件:MessageQueue.java

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) {
            //退出时,回收msg,放到消息池
            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; //这时为true
            } else {
                // 插入到队列中间,经常我们不需要唤醒事件队列,除非队列的
                // 头部有一个障碍或者消息是该队列最早的异步消息
                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;
    }

从代码可以看出该类的作用是把消息按时间顺序排序,并且控制线程的唤醒,但是我们对mBlocked和handler是怎么记时,还是不清楚,接来我们来看看Loop是怎么取消息的

我们看Loop.loop()就可以知道,取消息是这行代码:

Message msg = queue.next(); // might block

所以我们再看看next()

文件:MessageQueue.java

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
        int nextPollTimeoutMillis = 0; // 阻塞的时间
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            // 阻塞操作
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // 获取系统启动后,到现在的时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // 查找下一条异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // 如果时间未到,设置下一轮需要等待的时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 得到消息
                        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 {
                    // 没有消息
                    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) {
                    // 没有idle handlers 在运行,loop需要等待
                    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);
                    }
                }
            }

            // 重置idle handler 的数量为0,这样我们就不会再次运行
            pendingIdleHandlerCount = 0;

            // 当调用一个空闲的handler时, 一个新的消息可以被分发
            // 因此可以不需要等待,直接查询pending message
            nextPollTimeoutMillis = 0;
        }
    }

从这个方法中可以看出,handler获取时间的方式是调用SystemClock.uptimeMillis(),并用它和消息的里包含的时间进行对比。
同时next()方法内部如果有阻塞,会把mBlocked设置true,在下一个Message进队列时会判断这个message的位置,如果在队首就会调用nativeWake()方法唤醒线程。

综上所述:
1. 如果我sendEmptyMessageDelayed发送了消息A,延时为500ms,这时消息进入队列,触发了nativePollOnce,Looper阻塞,等待下一个消息,或者是Delayed时间结束,自动唤醒;
2. 在1的前提下,紧接着又sendEmptyMessage了消息B,消息进入队列,但这时A的阻塞时间还没有到,于是把B插入到A的前面,然后调用nativeWake()方法唤醒线程
3. 唤醒之后,会重新都取队列,这是B在A前面,有不需要等待,于是直接返回给Looper
4. Looper处理完该消息后,会再次调用next()方法,如果发现now大于msg.when则返回A消息,否则计算下一次该等待的时间

看到这里你可能想说,这和我的问题没什么关系啊,别急,答案就在SystemClock.uptimeMillis(),handler是通过它来获取时间的,但uptimeMillis()是不包括休眠的时间的,所以手机如果在休眠状态下(android 7.0在灭屏情况下很容易进入休眠),那时间就一直不变,至于中途又发送消息了,那是因为手机被唤醒了(android7.0会定时唤醒手机,接收消息),这时执行完delay操作,就可以发送消息了。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页