Handler 源码分析

一、引用关系

在Android消息循环系统里面涵盖了下面几个重要的类:Thread、ThreadLocal、Handler、Looper、MessageQueue。下面看一下它们之间大概的一个引用关系:


handler.png

简单来讲,一个线程最多持有一个Looper,这种唯一性通过ThreadLocal保证。而Looper又在创建之初实例化并持有一个MessageQueue。另外,Handler在实例化调用构造函数时,会获取到当前线程的Looper及MessageQueue。

现在来看看线程创建 Looper的过程,以及Looper的唯一性的实现过程。来看看Looper里重载的prepare 方法:

// Looper.java

private static void prepare(boolean quitAllowed) {
        // 从 ThreadLocal 获取当前线程的 Looper 对象,如果该线程已有Looper,则抛异常
        if (sThreadLocal.get() != null) {   //  1
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));  // 2
    }

可以看到,sThreadLocal先去获取当前线程的 Looper。不为空,说明已有,就抛异常,见上面注释1 。如果当前线程还没有Looper ,那么就新创建一个并保存,见注释2。那么 ThreadLocal 是怎么知道当前线程有没有 Looper 以及如何获取当前线程Looper的呢?那就好点进上面注释 1处,ThreadLocal 的 get() 方法来瞧一瞧:

// ThreadLocal.java

  public T get() {
        Thread t = Thread.currentThread(); // 3
        ThreadLocalMap map = getMap(t); 
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);  // 5
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
// 
  ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;  // 4
    }

注释3出可以看到,这里首先获取到当前线程。看注释5处,Looper是以键值对的形式保存在 ThreadLocalMap 里的。而ThreadLocalMap 就保存在当前线程(注释5)。键值对的 key 值就是保存在 Looper里的 ThreadLocal 对象(变量sThreadLocal)。所以 Threadlocal 是通过 唯一的 Key值在键值对里获取到当前线程的 Looper对象的。同样,Looper对象的保存方式也不难想象。下面是 ThreadLocal保存 Looper对象的 set() 方法:

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

至于MessageQueue这个消息队列,是在消息循环 Looper 创建的时候一起实例化的。下面看Looper的构造方法:

// Looper.java
private Looper(boolean quitAllowed) {
       // 创建消息队列
        mQueue = new MessageQueue(quitAllowed);
      // 保存当前线程
        mThread = Thread.currentThread();
    }

至于 Handler在创建时是如何获取当前线程的消息队列和消息循环的?看它的重载的构造方法就知道了:

// Handler.java

 @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper; // 保存Looper
        mQueue = looper.mQueue;  // 获取 Looper里的消息队列
        mCallback = callback;
        mAsynchronous = async;
    }

值得一提的是,目前官方不提倡在创建 Handler时隐式的传入 Looper对象。也就是不带参数的 Handler构造方法已经被标注为过时(@Deprecated)。提倡我们在创建Handler时,将当前线程的Looper作为参数,传给Handler的构造方法。

二、消息的插入及获取

消息插入:
我们来看看消息的插入过程。那么我们在调用 Handler的 post() 或sendMessage()等消息入队的方法后,消息最终是怎么插入队列的呢?就在 Handler的下面这个方法:

// Handler.java

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);   //  1 消息入队
    }

可以看到,方法返回时调用了 MessageQueue 的 enqueueMessage()方法将消息入队。那么现在来看看 该方法的实现:

// MessageQueue .java

 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) {
               
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {    // 注释 1  循环遍历链表,把消息插入合适的位置
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }
            if (needWake) {
               // 注释2
                nativeWake(mPtr);
            }
        }
        return true;
    }

上面代码根据 next 等关键词我们可以清楚地看到,消息队列里维护了一个单链表。上面代码注释 1 处用循环遍历了该链表,对比执行时间后,将要插入的的消息插入到链表合适的位置。然后注释 2处根据需要调用本地方法 nativeWake() 唤醒阻塞(插入成功且原来处于阻塞状态的话)。

消息获取:
现在来看看最重要的部分,消息获取及执行过程。先从线程的 Looper 的 loop()方法开始吧。因为是它获取消息交给线程执行的。现在看看它的方法实现的重要部分:

// Looper.java

public static void loop() {
......
 for (;;) {
            Message msg = queue.next(); // 注释 3 ,从消息队列中取消息,可能会阻塞
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
......
msg.target.dispatchMessage(msg);   // 注释 4
......
}

很简单,看注释 3 。就是在死循环里从消息队列 调用了 MessageQueue 的 next()方法。拿到消息msg 后就调用了 Message 的 target变量的dispatchMessage(msg)方法,这个target就是当前线程的Handler对象的引用,不信你可以去查~。好了,消息又回到了Handler,最终交给handleMessage(msg)方法去处理。不多说,还是看上面注释 3的next()是怎么获取消息的,最重要的方法要来了,我们找来看看这个方法:

// MessageQueue.java

@UnsupportedAppUsage
    Message next() {
        ......
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);   // 注释 7 本地方法,可实现阻塞
            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) {   //  注释 8 遇到屏障消息,后面的同步消息都不再执行
                    // 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) {     // 注释 9,取到了合适的消息,判断是否到执行时间
                    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;  // 注释 10 消息队列空了,新一轮循环将阻塞
                }
        .............
}

上面可以看到,还是使用了一个死循环遍历消息队列的链表。注释 7 处调用了一个本地方法,当 nextPollTimeoutMillis 参数为负时表示将无限期阻塞,为 0 时方法立即返回,为正数时限时阻塞。所以,我们平时经常听到的主线程消息队列无消息时会阻塞,这个阻塞就是从这里来的。
上面代码再往下看注释 8 ,msg.target == null 这个条件说明该消息是一个屏障消息,或称为同步屏障。链表中屏障消息后的同步消息将得不到执行,所以该条件判断里用了一个 do ~ while 循环跳过了所有同步消息,专挑异步消息执行。再看看注释 9 ,取到了合适的消息,判断执行时间 msg.when是否已到。到的话就拉出去执行,没到的话给 nextPollTimeoutMillis 赋一个正值(时间差),下一轮循环继续限时阻塞。
最后在注释 10 的地方,消息队列为空了,就给了 nextPollTimeoutMillis 一个负值,让它在下一轮循环时自己体会~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Handler对于Android开发者再熟悉不过了,也是面试题的常客了,所以了解Handler机制的源码就很有必要...
    i小灰阅读 949评论 0 3
  • Handler对于Android开发者再熟悉不过了,也是面试题的常客了,所以了解Handler机制的源码就很有必要...
    h2coder阅读 1,228评论 0 0
  • 前言 文章是记录自己研究源码的过程,方便日后的回顾,文章可能篇幅过长。 Handler 基本使用 源码分析 Han...
    是刘航啊阅读 3,283评论 0 2
  • 本着针对面试,不负责任的态度,写下《面试总结》系列。本系列记录面试过程中各个知识点,而不是入门系列,如果有不懂的自...
    DB_BOY阅读 4,716评论 6 9
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 12,723评论 28 53