写在前面
上一次写完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机制比较简单,好像没啥必要写,但真正要写起来的时候还是得去深入了解代码的细节,然后才发现有些地方以前理解得也不够好。能理解和能写出来让别人理解,其实是不同的层次了。