Handler可能是面试Android被问的最多的把?
大部分都知道Handler、MessageQueue、message、Loop的关系,因为要面试嘛。没办法。那今天看下Handler这一块的东西核心在哪里?
Handler可以创建Message、也可以new一个Message,然后丢到Handler中去。那他是如何丢到列队中去的呢?
1、如何把Message放进列队的呢?
Handler创建的时候
mLooper = Looper.myLooper(); // 这里从Looper中拿一个MyLooper,这个就是属于这个Handler的Looper了
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; // 拿出一个列队来。
mCallback = callback;
mAsynchronous = async;
当然,Handler也可以从外面给Looper,比如Looper.getMainLooper(),直接用UI现成的消息列队。尽量不要这么干。
那,有了列队了,Handler中的消息就可以放进MessageQueue中了。
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); // 就是这里了,把Message丢到MessageQueue中去。
}
好,我们来看下MessageQueue
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) { // 这个和markInUse,看看代码就知道了。markInUse是标记,isInUse是判断,和下面的msg.markInUse();结合起来看就明白了
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) { // 如果正在结束,那没必要往里面塞消息了。
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse(); // 标记这个Message正在操作
msg.when = when; // 赋值Message的延时时间
Message p = mMessages; // Message是链表。数据结构,这个我不解释了。
boolean needWake; // 是否需要唤醒线程,在延时等情况下,会阻塞等待具体看下Next,等下也会讲到
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;
// 上面两行的意思不用解释了吧。
// 这里。找到一个时间比当前Message的延时时间大的,就退出
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next // 找到比他时间大的,就把这个放在时间较大的节点前面
// 这样,一个先进先出的列队就出来了。
// 举个例子说明下,以时间作准
// 0 0 0 0 10000 我要插入一个1000的数据
// 插入1000时间数据时:
// 0 0 0 0 10000
// ↑ 一直遍历,发现10000比他大,那就把1000放在10000的节点前面,原理就是这样的了。
// 最后变成 0 0 0 0 1000 10000的链表
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
总结:以上注释就解释了,消息的存放机制了,最终是按照延时时间排序了的。这个机制也解释了,为什么列队中有延时时间长的Message,也不会阻塞到其他的消息分发了。因为每次拿到的,都是延时时间最小的。
看看Looper中的loop()
public static void loop() {
final Looper me = myLooper();
...
for (;;) {
Message msg = queue.next(); // might block // 这里的next,会告诉你会被阻塞。
if (msg == null) { 循环还是有可能退出的。第一眼以为没有数据,就退出了。那还了得。看看MessageQueue.next后才明白。
// No message indicates that the message queue is quitting.
return;
}
...
}
什么时候阻塞呢?
在看下MessageQueue中的next()
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
// 上面的描述已经很清楚了,已经没有了,就会等待,并且阻塞住loop
mBlocked = true;
continue;
}
2、Handler是如何利用Message切换线程的?
子线程,可以切换到主线程,利用的就是Handler,这其实就是Android中的线程切换原理,但Handler是如何做到的线程切换呢?
这个就全在Looper上了。
sendMessage(...),把消息放在列队中。
自己扒源码,最终是到这里的。
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); // 消息被丢到了这个列队中,那这个列队
// 是哪里的呢?对,就是Looper中的,这个looper就是Handler持有的,如果给的是
// Looper.getMainLooper()。那消息就会被丢到UI线程中的消息列队中执行。就是这样的。
}
至于Looper是怎么定位到每个线程的,看看ThreadLocal。这个是个里面有着Map(自己实现的,散列算法),key就是当前线程,所以每次取值的时候,都会取到自己的那个Looper。