Handler 在开发中使用的非常平凡,它基于一种队列消息机制,循环处理消息或者开发者的代码任务,实现不同线程之间的通信,下面结合源码学习其如何实现。
-
主线程如何建立Handler消息机制
当我们点击launch的app图标时,系统会做什么,系统肯定会首先创建一个进程,为该app独有,接着,肯定是主线程了,也就是平常经常说的UI线程,不能执行耗时操作...,根据java的特性,肯定会执行一个类的main方法,这里,系统执行了ActivityThread的main方法。
点开ActivityThread类,main如下(代码太长,省略部分):
ActivityThread:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
首先调用了Looper的prepareMainLooper(),所以开发者就不需要在主线程调用Looper.prepare()直接使用handle,该静态方法内容如下:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//对开发者提供的prepare(),默认传true,下面会分析
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));
}
和咱调用Looper一样,主线程的Looper也是得先prepare,但传入了false值,深入prepare()源码,可以发现,该false值最终传入了Looper对象所维护的MessageQueue中的mQuitAllowed变量中,而在MessageQueue中作用是什么呢,ctrl+f,查找mQuitAllowed使用过的地方...发现仅在下面这些地方出现过:
MessageQueue:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
噢了,用来防止开发者终止主线程的消息队列的,停止Looper循环的。继续Looper的prepareMainLooper(),在Looper prepare完后,就是创建Looper对象了,并且存在了Looper静态变量中。这样主线程的Looper就算准备完毕,Looper的消息队列MessageQueue也创建处理,作为传送带的角色随时待命开机传送...
再回到ActivityThread的main方法,在 Looper.prepareMainLooper()执行完毕后,就轮到Handler处理者角色创建啦,作为一套成型的消息处理机制,Handler作为消息的消费者,地位肯定不可或缺。
再分析下main方法,在执行 sMainThreadHandler = thread.getHandler();之前,主线程创建了ActivityThread对象并调用了attach函数,这里干嘛呐,这里就涉及到应用的初始化啦,比如Application创建,Activity创建,执行应用的生命周期啊等等,挺复杂,还没转过弯...反正里面的操作也是向主线程消息队列发送消息的。
但是这里可以想下,既然这里send了Message,但Looper还没有开始启动MessageQueue循环处理啊,有用吗?这里再一步步深入Handler源码的sendMessage方法,关键代码如下:
Handle:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
MessageQueue:
boolean enqueueMessage(Message msg, long when) {
...
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;
}
...
return true;
}
可以看出,Handle的sendMessage是将Message send进了MessageQueue,enqueueMessage()方法中就是执行队列的操作,将Message加入一个队列中了,就相当于一个个的放上传送带了并且通过next引用互相联系着,通过for(;;)循环,找到队列的最后个Message,然后将最后一个的Message的next指针指向新来的msg,链表结构哈。
这样就清楚上面的疑惑了,传送带还没开动,但先把物品给放上去排好,待会跑起来了,再一个个给处理,一点不耽误。
啥时候跑起来呢,继续撸咱的main方法,好了,说曹操曹操就到,接下来就是 Looper.loop();传送带开关开了,老司机开车了,Looper.loop()源码:
Looper:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// 开始在当前线程中循环...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
为什么要循环起来呢,因为线程的生命周期限制于它的有效代码行数,循环起来,有效代码相对增多了,生命周期就延长了,这也保证了app能一直运行了,这也是主线程的Looper调用quit()会抛异常的原因,主线程quit了app还怎么玩...
开始 loop之前,要获取当前线程持有的Looper对象,有吗,没有就果断抛异常,有?拿出Looper的MessageQueue传送带来,接下来for循环开启传送带开关,运转起来了。
这里为啥用for(;;)而不是while呢,有说习惯问题,有说防止被修改字节码,也有人说比while少个判断,具体也是不清楚,还请高人指点...
接下来循环体就直截了当了,MessageQueue的next是阻塞方法,在这里等待Handle send的Message,来一个message,就放行一次,然后从message中获取Handler引用,让handle dispatchMessage,dispatchMessage也不复杂:
Handle:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
先判断有没有Runnable类型消息,有就执行run回调呗,没有就走实例化Handler时重写的handleMessage,里面就根据Message类型及参数来执行不同逻辑。
到这里就建立好了主线程的消息处理机制,应用层开发者直接通过实例化Handle sendMessage或者postRunnable就可以向主线程发送消息了。
-
主线程不能有多余5秒的耗时操作
都知道主线程中不能有超过5秒的无响应操作,否者就ANR(Application Not Responding)了,结合上面loop()源码的分析,既然主线程一直阻塞在等待send的message,那么,计时系统应该在msg.target.dispatchMessage(msg); 处理message方法的前后加的,在执行前,开始计时,当达到5秒时,跳出ANR异常,所有,初步猜想可能和Trace这个类有关,还请指点!
-
子线程创建Handler消息机制
子线程创建Handler消息机制,步骤和主线程的一样,先Looper.prepare(),来为当前线程绑定新的Looper对象,同时,Looper对象会实例化持有的MessageQueue,然后就可以通过实例化Handler发送Message了,但这时发了是处理不了的,Looper需要通过loop循环当前的MessageQueue,一个个取Message。
所有,子线程有上面操作需要注意啦,loop()之后,子线程生命周期就延长了,除非调用Looper.quit(),就结束循环了。
-
为何子线程中直接使用Toast报错
翻看Toast源码,可以看到Toast底层也是用了Handler机制发送消息,这就不难理解了,Handler消息机制需要依附于当前线程的Looper对象来运作,子线程没有自己的Looper,Handler也就使用报错,Toast也就不能用啦。
具体参看:http://blog.csdn.net/saroll57/article/details/23276275
-
为何UI操作要在主线程中
UI操作不是线程安全的,比如View的invalidate()就是非线程安全的,View的各种操作最终都会导致invalidate()函数执行,多个线程执行同一UI操作就会导致同时有多次刷新,就会出现异常情况。
子线程操作UI,可以通过获取主线程handler的引用发送message来实现,或者通过:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)