这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
概述
平时我们执行耗时的操作,比如网络请求,IO操作等,需要在子线程中运行,不然会阻塞主线程。 而执行完网络请求等耗时操作后通常需要更新UI,如果在子线程中更新UI,那么程序会崩溃。
因为Android的UI是线程不安全的。
解决的方案是只需把更新UI的操作切换到主线程即可,这时就轮到Handler出场了,相信大家都对Handler的用法很熟悉了。当我们在子线程向服务端拉取数据后,主线程是不知道的,这时Handler在子线程发送一个消息到主线程告诉主线程:我已经请求数据完毕,现在你要更新UI了。然后handlerMessage方法接收到消息即可处理数据更新UI。
这一切都是那么的自然。
Android中的消息机制,由四个角色承担。分别是Handler,Looper(消息循环),MessageQueue(消息队列),Thread。
看到下图这四个角色的关联,先有一个大概的认识
那接下来就从源码的角度一起来学习消息机制吧
先从handler对象的创建开始,接着再分析handler怎么发送消息。
我们通常在主线程中创建handler,看看他的构造方法。
public Handler() {
this(null, false);
}
可以看到这个无参数的构造方法,内部使用this关键字调用含有两个参数的构造方法。那就找到两个参数的构造方法,源码如下:
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
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调用myLooper方法获取到Looper对象, 如果mLooper == null的话,会抛出
Can't create handler inside thread that has not called Looper.prepare()
的异常。大概的意思就是无法在没有调用Looper.prepare()的线程中创建handler。 我在刚开始学习Handler的时候经常会遇到这个错误。不急,等下在分析到底为什么,现在我们只需要知道如果Looper.myLooper()没有获取到Looper对象的话就会报这个错。 到了这里,Handler和Looper就建立起了关联。接着往下看完最后几行代码
mQueue = mLooper.mQueue;
从Looper对象中取出MessageQueue对象并赋值。MessageQueue就是消息队列,那么他里面存储着很多消息吗? 到了这一步,Handler通过Looper与MessageQueue也建立起了关联。
我们跟踪Looper的myLooper方法进去,解决为什么会抛出Can’t create handler inside thread that has not called Looper.prepare()异常。
myLooper方法源码如下:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
只有一行代码,从线程中取出Looper对象,那么我们有理由相信,这个ThreadLocal是通过set方法把Looper对象设置进去的。
想一想ThreadLocal在哪里把Looper对象设置进去了呢。回到刚才想要解决的问题:Can’t create handler inside thread that has not called Looper.prepare() 。那会不会是Looper的prepare方法呢?
public static void prepare() {
prepare(true);
}
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));
}
找到了线索,ThreadLocal确实是在Looper的prepare方法里把Looper对象设置进去的,而且从第一行的判断可以知道,一个线程只有一个Looper对象。 到了这里,Looper与ThreadLocal建立起了关联。可以看下Looper的构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
创建了一个MessageQueue对象。
好,结合我们的分析可以知道,如果Looper没有调用prepare方法,ThreadLocal的get方法就会返回空,那么Looper.myLooper()也会返回空,所以就抛出了
Can't create handler inside thread that has not called Looper.prepare()的异常。
那么问题又来了,我们写程序时好像没有手动调用Looper.prepare()吧,也不会抛出异常。前面提到,我们通常都是在主线程,也就是UI线程中创建handler的。而在主线程中,系统已经为我们创建了一个Looper对象,所以不会抛出异常了。。。而那些会抛出异常报错的情况,是在子线程中创建的handler,但是又没有调用Looper.prepare()去创建Looper对象。 继续前进。那就来看看,主线程在什么时候创建了Looper对象吧。