聊一聊 Handler(下)

196 阅读2分钟

同步屏障

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

在Message.next()方法中有这样一个逻辑,如果取出来的Msg不为空但是msg.target是空的,会不断的往后面遍历,找到第一个msg.isAsynchronous()为false的,isAsynchronous 这个是用来判断这个消息是否为同步消息,也就是说如果遇到msg != null && msg.target == null的情况,就会遍历找到第一个异步的消息进行处理,这种target为null的msg 也是handler发送的一个同步屏障,在enqueueMessage 这个消息入队的方法中有对target是否为空做判断,所以发送屏障是另外一个调的另外一个方法

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

设置同步屏障可以使得消息队列在获取消息时,如果遇到同步屏障,会忽略后面的同步消息,取出第一个异步的消息,可以使得一些消息优先执行。比如TraversalRunnable 消息就是异步消息,发送这个消息之前发送一个同步屏障Msg,可以使这个绘制的消息优先执行。

ThreadLocal

Handler 中的Looper 保存在ThreadLocal中


private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

为什么要保存在ThreadLocal中呢?个人理解是,每个线程都会有自己的Looper 去获取Msg,而ThreadLocal是一个线程内部的存储类,可以在指定的线程保存数据和获取对应线程的数据,每个线程只能从ThreadLocal中访问到自己线程对应的数据,不能访问其他线程的,也就是线程A 只能访问到自己的Looper A,不能访问到线程B的Looper B。

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

可以看到ThreadLocal保存数据的时候是先获取当前的线程,然后再根据当前线程t去获取实际保存数据的ThreadLocalMap

 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

ThreadLcoalMap 是以ThreadLocal 作为key来保存value值。

阻塞唤醒机制

在MessageQueue 遍历消息的方法中有这样本地调用的代码

nativePollOnce(ptr, nextPollTimeoutMillis);

其中nextPollTimeoutMills 是用来描述当消息队列中没有新的消息需要处理时,当前线程需要进入的睡眠等待的时间,如果值为0,则表示即使当前没有消息要处理,当前线程也不要进入睡眠等待状态。如果值为-1,则表示没有消息处理时,当前线程需要无限的处于睡眠等待状态,直到有新的消息到来或者被其他线程唤醒。当前线程会在C++层创建一个epoll 实例,并且将它的文件描述符保存在C++层的Looper类的成员变量mEpollFd 中,同时还将一个管道的读端文件描述符注册到里面,监听这个管道的IO写事件,如果这些文件描述符没有发生IO读写事件,那么当前线程就会在函数epoll_wait 中进入睡眠等待状态。

参考

《Android 系统源代码情景分析(第三版)》