前言
一直以来对Handler的理解都迷迷糊糊的,今天好好总结下。想了半天不知道该从哪里开始写起,那就从最常用的用法开始吧!
代码解析
我们都知道以下Handler的用法会报错:RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
new Thread(new Runnable() {
@Override
public void run() {
handler = new Handler();
//do someThing...
handler.sendEmptyMessage(0);
}
}).start();
我们找到源码中抛出异常的地方,源码如下:
/**
* ...
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
public Handler() {
this(null, false);
}
public Handler(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()");
}
......
}
很明显异常的产生是因为在Handler构造方法中,条件的判断Looper对象为null所导致,并且根据默认构造方法的注释可知:当使用Handler切换线程时,那么这个线程必须要有一个looper,否则Handler无法接收到消息。既然Looper如此重要,那么什么是Looper呢?来看下Looper类的注释:
/**
* Class used to run a message loop for a thread. Threads by default do
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
* ......
*/
大意是:线程在默认情况下没有与它们关联的消息循环,Looper类就是为线程运行消息循环的类。可以使用Looper.prepare()方法为线程创建一个消息循环,使用Looper.loop()方法开始处理消息。这里又牵扯出两个概念:消息(Message)和消息队列(MessageQueue)。Message是 一个实现了Parcelable接口可以被发送给Handler的数据载体,MessageQueue是一个内部由单链表实现的用于管理Message的列表。
知道了相关的概念,那么再来看正确的Handler的写法:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler = new Handler();
//do someThing...
handler.sendEmptyMessage(0);
Looper.loop(); } }).start();
由此看来执行Looper.prepare()方法后,Looper.myLooper()方法可以获取到Looper对象,再来看看相关的源码:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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对象,并存储到ThreadLocal中,ThreadLocal网络上有人翻译为:线程局部变量,很贴切,它为使用该变量的线程提供独立的变量副本,能解决线程并发访问变量的问题。并且,根据这里的条件判断说明,一个线程中只能有一个Looper实例,因此Looper.prepare()方法在一个线程中也只能调用一次。第二部分源码是Looper.prepare()方法中创建的Looper的Looper构造器,可以看到在构造器中实例化了一个消息队列(MessageQueue),也就是说线程中的MessageQueue也是唯一的。这样Looper.prepare()执行后便创建了Looper对象,Looper.myLooper()方法也就是获取当前线程的Looper对象,而Looper中的Looper.getMainLooper()也就是获取主线程的Looper对象(实际上还是通过Looper.myLooper()方法获取的),这样对于上述的错误的解决过程我们也就能理解了。接下来我们就可以愉快的使用Handler切换线程,处理消息了。
上面多次提到了消息循环,那么怎么开启消息循环,消息又是怎么循环呢?由Looper的注释可知通过Looper.loop()方法可以开始处理消息,我们看Looper.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;
......
for (;;) {
Message msg = queue.next(); // might block
......
msg.target.dispatchMessage(msg);
......
}
}
可以看到loop方法中先判断了当前线程是否存在Looper对象,接下来开启一个死循环,循环从消息队列(MessageQueue)中取出消息,交给Handler去处理。这里是Handler中最终消息处理的方法源码:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里的callback实际上就是我们通过Handler.post传递的Runable对象,当callback为null时最终调用我们熟悉的handlerMessage(Message msg)方法,完成消息的处理,至此我们也就明白了消息处理的整个过程。最后因为这里的Looper是我们手动创建并开启消息循环的,再线程任务结束了,别忘了调用Looper.myLooper.quit()方法退出消息循环。
总结
Handler常用来更新UI和处理消息,通过Looper.prepare()方法创建一个Looper实例,并创建一个消息队列(MessageQueue),用来对消息的管理,通过Looper.loop()方法循环从消息队列中取出消息交给Handler去处理,如果没有消息则阻塞,在当前线程中只能存在唯一的Looper和消息队列的实例。
其实Handler的用处还有很多,比如使用Handler构成每隔一定时间的无限循环,用来更新UI,形成动画效果:
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//do someThing...
handler.sendEmptyMessageDelayed(1, 1000);
}
};
handler.sendEmptyMessageDelayed(1, 1000);
//exit
handler.removeMessages(1);
疑问
这里还有一个疑问,View类的post方法和Handler的post有什么区别呢?为什么在View类的post方法中可以获取到View的宽高呢?
先说简单的Handler的post方法,通过追踪源码可知,Handler的post方法其实就是就是发送了一条消息并将消息加入消息队列而已。而View的post方法的源码如下:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
这里对View的AttachInfo进行了非空判断,什么是AttachInfo呢?简单来说就是当前View在父容器中的各种信息的集合。然后这里最终还是调用了Handler的post方法,由此看来View的post方法还是利用Handler发送消息,只不过这里的消息是发送给主线程。至于为什么能在View类的post方法中可以获取到View的宽高,简单来说就是通过View的post发送的消息,等主线程的Looper处理的时候,View已经执行了测量(measure)和布局(layout),所以也就能获取到View的宽高了。
参考资料:《Android开发艺术探究》