Android Handler机制的工作原理

写在前面

上一次写完Binder学习笔记之后,再去看一遍Activity的启动流程,因为了解了Binder的基本原理,这次看印象会更深一点,学习效果也比以前好很多。本来打算直接来写Activity的启动流程的,但总觉得Handler也需要写一下,知道Handler和Binder的原理后,再去看Activity的启动流程,应该也没什么问题了。虽然网上已经有很多Handler相关的文章了,而且Handler机制的上层原理也并不难,还是决定写一下,因为我想构建自己的知识体系。也希望给看我博客的朋友们一个无缝衔接的阅读体验。

Handler机制涉及到的类主要有Handler、Message、Looper、MessageQueue、ThreadLocal等。虽然我们最熟悉的是Handler和Message这两个类,但是在我们开始可以使用Handler之前,Looper是为我们做了一些事情的。

本文的源码是基于android-28的

Looper

在使用Handler之前,我们必须得初始化Looper,并让Looper跑起来。

Looper.prepare();
...
Looper.loop();

执行上面两条语句之后,Looper就可以跑起来了。先来看看对应的源码:

public static void prepare() {
    prepare(true);
}

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

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

必须保证一个线程中有且只有一个Looper对象,所以在初始化Looper的时候,会检查当前线程有没有Looper对象。Looper的初始化会创建一个MessageQueue。创建完Looper后会放到ThreadLocal中去,关于ThreadLocal,后面会说到。

public static void loop() {
    // 判断当前线程有没有初始化Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    
    final MessageQueue queue = me.mQueue;

    ...

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        
        ...

        final long traceTag = me.mTraceTag;

        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }

        try {
            // target指的是Handler
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        
        ...

        msg.recycleUnchecked();
    }
}

方法比较长,所以只把最核心的代码放了出来。省略掉的代码中有一个比较有意思的:我们可以指定一个阈值比如说200,当Message的处理超过200ms时,就会输出Log。这可以在开发中帮助我们发现一些潜在的性能问题。可惜的是,设置阈值的方法是隐藏的,无法直接调用,所以这里就不放出代码了,感兴趣的朋友自己翻一下源码吧。

简化后的代码可以看出逻辑十分简单,可以说Looper在当中扮演着搬砖工的角色,从MessageQueue中取出Message,然后交给Handler去分发,再去MessageQueue中取出Message...无穷无尽,就像愚公移山一样。

看到这里,应该多多少少会觉得有点不对劲,因为这里是一个死循环,按道理来说会一直占着CPU资源的,并且消息也总有处理完的时候,难道处理完就从消息队列返回Null,然后Looper结束吗?显然不是,注意看注释might block

MessageQueue

答案就在MessageQueue里面,直接来看一下next():

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

        nativePollOnce(ptr, 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 {
                    // 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;
            }

            // 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) {
                // No idle handlers to run.  Loop and wait some more.
                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);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

代码有点长,这次不打算省略掉一些了,因为这里面还有一个小彩蛋。
方法中最重要的应该就是这一行了

nativePollOnce(ptr, nextPollTimeoutMillis);

简单来说,当nextPollTimeoutMillis == -1时,挂起当前线程,释放CPU资源,当nextPollTimeoutMillis >= 0时会延时指定的时间激活一次线程,让代码继续执行下去。这里涉及到了底层的pipe管道和epoll机制,就不再讲下去了(其实是因为讲不下去了)。这也就可以回答上面的问题了,当没有消息的时候只需要让线程挂起就行了,这样可以保证不占用CPU资源的同时保住Looper的死循环。

然后我们来看消息是如何取出来的。MessageQueue中有一个Message,Message类中又有一个Message成员next,可以看出Message是一个单链表结构。消息的顺序是根据时间先后顺序排列的。一般来说,我们要取的Message就是第一个(这里先不考虑异步消息,关于异步消息以后会讲到的,又成功给自己挖了一个坑哈哈),如果当前时间大于等于Message中指定的时间,那么将消息取出来,返回给Looper。由于此时nextPollTimeoutMillis的值为0,所以当前面的消息处理完之后,Looper就又来取消息了。

如果当前的时间小于Message中指定的时间,那么设置nextPollTimeoutMillis值以便下次唤醒。还有另外一种当前已经没有消息了,nextPollTimeoutMillis会被设置为-1,也就是挂起线程。别急,还没那么快呢,接着往下看。

紧接着的逻辑是判断当前有没有IdleHandler,没有的话就continue,该挂起就挂起,该延时就延时,有IdleHandler的话会执行它的queueIdle()方法。这个IdleHandler是干什么的呢?从名字应该也能猜出个一二来,这里就不再展开讲了。关于它的一些妙用可以看我之前写的Android 启动优化之延时加载。执行完queueIdle()方法后,会将nextPollTimeoutMillis置为0,重新看一下消息队列中有没有新的消息。

Handler

上面将取消息的流程都讲清楚了,万事俱备,就差往消息队列中添加消息了,该我们最熟悉的Handler出场了。Handler往队列中添加消息,主要有两种方式:

Handler.sendXXX();
Handler.postXXX();

第一种主要是发送Message,第二种是Runnable。无论是哪种方式,最终都会进入到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;
        } 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);
        }
    }
    return true;
}

一般情况下,我们通过Handler发送消息的时候,会通过SystemClock.uptimeMillis()获取一个开机时间,然后MessageQueue就会根据这个时间来对Message进行排序。所以enqueueMessage()方法中就分了两种情况,一种是直接可以在队头插入的。一种是排在中间,需要遍历一下,然后寻一个合适的坑插入。when == 0对应的是Handler的sendMessageAtFrontOfQueue()postAtFrontOfQueue()方法。needWake的作用是根据情况唤醒Looper线程。

上面有一点还没有讲,就是Looper从MessageQueue中取出Message后,会交由Handler进行消息的分发。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

优先级顺序是Message自带的callback,接着是Handler自带的callback,最后才是handleMessage()这个回调。

ThreadLocal

还记得Looper中有一个ThreadLocal吧,把它放到最后来讲是因为它可以单独拿出来讲,不想在上面干扰到整个流程。ThreadLocal是一个数据存储类,它最神奇的地方就是明明是同一个ThreadLocal对象,但是在不同线程中可以存储不同的对象,比如说在线程A中存储了"Hello",而在线程B中存储了"World"。它们之间互相不干扰。
在Handler机制中,由于一个Looper对应着一个线程,所以将Looper存进ThreadLocal最合适不过了。
ThreadLocal比价常用的就set()get()方法。分别来看看怎么实现的吧。

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

首先是去获取ThreadLocalMap,找得到的话直接设置值,找不到就创建一个。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

看到这里,大概也能明白了。每个线程Thread中有一个ThreadLocalMap对象。通过ThreadLocal.set()方法,实际上是去获取当前线程中的ThreadLocalMap,线程不同,获取到的ThreadLocalMap自然也不同。
再来看看这个ThreadLocalMap是什么来头。看类的注释中有这么一句话:

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.

从注释中可以知道这就是一个定制的HashMap,并且它的Entry类指定了Key只能为ThreadLocal类型的。所以直接将它看成是一个HashMap就好了。

get()方法也好理解,就是从Map中取出值而已。大概看一下就好了。

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

写在最后

虽然在开始写之前,觉得Handler机制比较简单,好像没啥必要写,但真正要写起来的时候还是得去深入了解代码的细节,然后才发现有些地方以前理解得也不够好。能理解和能写出来让别人理解,其实是不同的层次了。

你可能感兴趣的:(Android Handler机制的工作原理)