首先有一个问题引出本篇文章:为什么更新UI线程的Handler必须在主线程中创建?
答:其实每个Handler都会关联一个消息队列,而消息队列封装在Looper中,而Looper又会关联一个线程(Looper通过ThreadLocal封装),最终每个消息队列会关联一个线程。Handler就是一个消息处理器,将消息投递给消息队列,然后再对应的线程中逐个取出消息,并且执行。因为取出消息后执行的动作在UI线程中。所以更新UI线程的Handler必须在主线程中创建。
下面从源码角度简单分析一下:
1.在Handler的构造函数中Looper的实例是通过myLooper()方法得到的:
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()");
}
//...
}
而myLooper()中Looper是通过sThreadLocal.get()获取的:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
那什么时候存储在ThreadLocal中的呢:
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.prepare()方法中存储进去的。这也那个所有的就关联上了。
因为我们知道,在主线程中创建Handler不用prepare Looper,这事因为在主线程中系统默认创建了Looper,它在不停的独爱读分发消息,因此四大组件的调度、我们的输入事件、绘制请求才能得到处理。
ActivityThread 就是我们说的主线程,而它的 main() 方法就是当主线程的入口:
public static void main(String[] args) {
/...
Process.setArgV0("<pre-initialized>");
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"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
}
所以我们可以推测出,Handler在子线程中创建Handler的时候必须手动加上prepare和loop方法,否则会抛出异常: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()");
}
//...
}
上述代码中可以看出,在Handler的构造函数中会与对Looper的判断,如果mLooper 为空,就会抛出异常。
//该写法会抛出异常!!!
new Thread(){
@Override
public void run() {
handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.e("TAG",msg.obj.toString());
return false;
}
});
}
}.start();
从上述程序中我们可以看到,当mLooper对象为空时,抛出了该异常,这是因为该线程的Looper对象还没有创建,因此sThreadLocal.get()会返回null。Handler的原理就是要与MessageQueue建立关联,并且将消息投递给MessageQueue,如果连MessageQueue都没有,那么Handler就没有存在的必要。而MessageQueue有封装在Looper中,因此创建Handler的同时必须保证Looper一定不为空。(因为sThreadLocal.get()为空即mLooper会为空,所以我们只需找到sThreadLocal.set()把mLooper穿进去,这样这里就不会为空了,就能正常运行了,而通过上述的源码发现在 Looper.prepare()的方法中sThreadLocal.set(new Looper(quitAllowed))赋值了),所以在子线程中必须手动加上Looper.prepare();
正确写法如下:
new Thread(){
@Override
public void run() {
//1.为当前线程创建Looper,并且会绑定到ThreadLocal中去
Looper.prepare();
handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.e("TAG",msg.obj.toString());
return false;
}
});
//2.启动消息循环
Looper.loop();
}
}.start();
我们加了两处:
第一是通过Looper.prepare()来创建Looper,第二是通过Looper.loop()来启动消息循环。这样该线程就有了自己的Looper,也就有了自己的消息队列,如果只创建Looper而不启动消息循环,虽然不会抛出异常,但是通过Handler来post或者sendMessage也不会有效,因为消息虽然被加到消息队列中了,但是并没有启动消息循环,也就不会从消息队列中获取消息并且执行了。
项目地址:https://github.com/fengxiaobing/MyHandler
注。由于是练习项目,该项目的代码中也有包含Thread和Runnable的优缺点的代码,不足之处还望多多指教。