在Android的多线程开发中,Handler的运用是很重要的一环。本章主要介绍Handler的运行机制。
10.1 Android的消息机制概述
如果在子线程中更新UI,有可能会收到android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.报错。这个报错是在ViewRootImpl的checkThread方法中抛出的:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
如果要在子线程中更新UI,则需要借助Handler了。之所以不能在子线程对UI进行操作,是因为如果多个线程同时操作UI,会导致不能预期的结果。加锁可以解决这个问题,但是又会牵扯出其他问题,比如效率的降低以及可能导致的阻塞。所以在包括Android的许多操作系统中,操作UI都是单线程进行的。
上面说到,checkThread是在ViewRootImpl中进行的。在第8章中说到,ViewRootImpl对应的是一个Window。并且在抛出的异常中说的是“Only the original thread...”而不是“Only the main thread...”那么这个“original thread”指的是哪个线程呢?从代码中可以看出,它指的是ViewRootImpl的mThread,而这个线程其实就是创建Window的这个线程。
事实上,通过以下代码创建一个Window:
final TextView tv = new TextView(MainActivity.this);
mThread = new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
tv.setText("hello");
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv.setText("world");
}
});
mHandlerInThread = new Handler();
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
0, PixelFormat.TRANSLUCENT
);
lp.gravity = Gravity.LEFT | Gravity.TOP;
wm.addView(tv, lp);
Looper.loop();
}
});
mThread.start();
在子线程中创建一个Window,这个Window只包含一个TextView,而这个TextView的内容只能在这个线程修改。如果在主线程中修改TextView的内容,也会抛出android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.异常。
Handler是如何工作的呢?首先,创建Handler必须要在当前线程中调用Looper.prepare(),如果没有的话,那么就会抛出Can't create handler inside thread that has not called Looper.prepare()异常。
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
看一下Looper的prepare方法:
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));
}
sThreadLocal是一个ThreadLocal类型的对象,它在Loop类中是一个静态对象。sThreadLocal中维护了一个map,map的key是当前线程,value是当前线程的looper。
// ThreadLocal.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);
}
在每个线程中,只能调用一次Looper.prepare(),否则会抛出异常,也就是说每个线程只对应一个Looper。 而每个Looper中维护了一个消息队列mQueue,在Looper.loop()中有一个死循环,在循环中会调用mQueue的next方法取出消息队列中的下一个消息。而next方法是阻塞的,直到取到下一个消息才会返回。
// 略去部分
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
msg.target.dispatchMessage(msg);
}
}
// 略去部分
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
Message msg = mMessages;
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;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
}
}
当有消息之后,会调用消息对应Handler的dispatchMessage方法,进行消息的分发,最后调用到Handler的handleMessage方法或者Callback回调。 当在外部调用了Handler的post方法或者send方法之后,最终都会传递到enqueueMessage方法中
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在这里对Message对象的target成员赋值,并且调用了MessageQueue的enqueueMessage方法将消息添加到MessageQueue之中。在MessageQueue的enqueueMessage方法中,有以下一段:
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;
可以看出,在MessageQueue中,消息队列其实就是一个链表,而MessageQueue中的mMessage成员即是这个链表的头部。链表是以消息的触发时间来排序的,即是enqueueMessage中的uptimeMillis参数。当时间到达Message设定的时间,MessageQueue阻塞的next()方法就会将其返回,然后Handler将会收到消息。