Handler 源码解析

从源码角度讲解 Handler、MessageQueue、Looper

Posted by Allen Vork on February 21, 2019

我们知道 Handler 可以发送2中消息:同步消息、异步消息。他们在处理方式上没有区别,只有当设置了同步屏障才会出现差异。

Handler 同步屏障

// MessageQueue.java

    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                // 从链表头遍历,根据时间找到节点对应的位置插进去
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

可以看出,它是将消息按照时间顺序放入链表中。最大的不同观点在于这个消息没有设置 Handler。正常情况下,通过 Handler 的 post,sendEmptyMessage,sendMessage*** 等函数都是调用的 enqueueMessage:

//Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    //...
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看出它设置了一个 Handler 到 Message 中,用于取出消息后通过这个 Handler 来处理。但是同步屏障的 Message 的 Handler 为空。

同步屏障的原理

同步屏障在 Looper 中循环获取消息的时候才会起作用,即在 MessageQueue#next 函数中发挥作用:

Message next() {
    //...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //...
        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;

            //这里就会判断 Handler 是否为空,为空的话则说明是同步屏障
            if (msg != null && msg.target == null) {
                // 从内存屏障消息的下一个消息找起,找出第一个异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //...
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        //将这个异步msg从消息链表中移除,此时内存屏障还在
                        prevMsg.next = msg.next;
                    } else { // 移除内存屏障
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    //返回异步消息
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            //...
        }

        //...
    }
}
可以看出整个过程中就是从内存屏障消息的后一个消息开始,查找异步消息。找到后将消息移除并返回。该消息执行完成后,由于同步屏障还在,继续查找同步屏障后的异步消息。如果整个链表后面没有异步消息了,则将同步屏障消息移除掉。      
可以看出,同步屏障的作用是直接忽略所有的同步消息,然后找到异步消息发送出去。

## 如何发送异步消息
```java
public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);

通常使用 handler 发送的都是异步消息,若要发送同步消息,则在构造 Handler 的时候需要将 async 参数设为 true。

为什么需要这样设计

主要是为了让一些优先级高的事情先做,譬如安卓应用框架中,为了加快相应 UI 刷新时间,将 scheduleTraversals 使用同步屏障,这样他就会得到优先执行。

参考文献