Refrence

前言

Android的消息机制之前有一篇文章有写,里面具体讲到了Handler怎么发送和处理消息的整个过程。感兴趣的同学可以先跳转过去看看 从Handler.post(Runnable r)再一次梳理Android的消息机制(以及handler的内存泄露)

延时操作

通常要实现延时操作有这几种方法:

  • TimerTask
  • Rxjava
  • Thread
  • Handler

这里我们主要来关注最后一种方法。

使用Handler的postDelayed方法来处理延时:

new Handler().postDelayed(new Runnable() {
		@Override
		public void run() {
			//延时到了的操作
		}
	}, 1000);//毫秒

这里就很简单的实现了一个1秒的延时,且不会阻塞主线程。怎么来实现这不是要探讨的话题,关键是在Hadler的内部是怎么来处理这个延时的呢,我们来进一步看看源码。

Handler发送延时消息

来一步步看看postDelayed方法:

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

这里将Runnable封装成一个消息:

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

然后调用sendMessageDelayed

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {//判断延时时间
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

在这个方法可以看到,发送延时消息,其实就是获取开机到当前的时间总数+延时时间,然后在指定的时间来发送消息,再来到sendMessageAtTime

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);
    }

最终将消息加到消息队列,同时指定时间enqueueMessage

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);
    }

到这里为止,Handler里面的处理就完成了,接下来来到MessageQueue

MessageQueue的消息处理

插入消息

打开MessageQueue,找到enqueueMessage

boolean enqueueMessage(Message msg, long when) {
    ...省略部分代码...
    synchronized (this) {
        ...省略部分代码...

        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;//如果当前的队列是阻塞的状态,那么mBlocked就会为true
        } 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); //调用native方法,唤醒阻塞
        }
    }
    return true;
}

在这里面,首先有一个判断,将现在队列最前面的消息跟现在将要入队的消息的执行时间做对比,如果将要入队的消息执行时间比目前队列前面的消息执行时间还早,或者队列没消息,那就需要将将要入队的消息直接插入到最前面。

跟队首消息比对之后,执行时间晚于队首消息,那就进入死循环,挨个来判断队列里面的消息执行时间与将要入队消息的执行时间,目的是将将要入队的消息按照执行时间来插入到比他执行时间晚的消息之前。

这里的needWake用来判断是否需要唤醒阻塞。

取出消息

我们直接来到MessageQueue的next方法,具体为什么会调用这个方法,开头的文章有详解,这里直接来看next这个方法:

Message next() {
    ...省略部分代码...
		int pendingIdleHandlerCount = -1; // -1 only during first iteration
		int nextPollTimeoutMillis = 0;
		for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr,nextPollTimeoutMillis);//调用naive方法阻塞管道,由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 {//取出消息交给Handler处理
                        // 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;
                }
    ...省略部分代码...
    }

在这里首先会调用nativePollOnce阻塞线程,对应enqueueMessage方法里的nativeWake唤醒线程。
然后也是一个死循环,在循环里面,首先会判断消息的执行时间,如果执行时间大于等于现在的时间就取出消息

总结

其实从源码看下来,发现Handler在处理延时消息的时候,其实跟普通消息一样,只不过延时消息是在普通消息之上加上了我们设置的延时,所有的消息都是按照时间来取出处理的。