前几天写程序的时候碰到这么一个问题,当时想在子线程中Toast一段话,不废话上代码
new Thread(new Runnable() {
@Override
public void run() {
new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Toast.makeText(MainActivity.this,"Loading......",Toast.LENGTH_SHORT).show();
}
};
}
}).start();
因为之前在主线程中使用Handler的时候,初始化也没有给他传入过什么属性,就直接用了,然后报错
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
当时我就纳闷了,这Looper先前倒是听说过,但是从没研究过,去网上查了点资料后对代码进行了修改
new Thread(new Runnable() {
@Override
public void run() {
new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Looper.prepare();
Toast.makeText(MainActivity.this,"Loading......",Toast.LENGTH_SHORT).show();
Looper.loop();
}
};
}
}).start();
在添加了
Looper.prepare()和Looper.loop()
之后,就能够运行了。
出于程序员刨根问底的职业好奇心驱使,我查看了一下源码,了解了其中的运作道理。
Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same ThreadLocal object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports null values.
大致意思是,ThreadLocal实现了线程本地存储。所有线程共享同一个ThreadLocal对象,但不同线程仅能访问与其线程相关联的值,一个线程修改ThreadLocal对象对其他线程没有影响。
如下图所示,我们可以将ThreadLocal理解为一块存储区,将这一大块存储区分割为多块小的存储区,每一个线程拥有一块属于自己的存储区,那么对自己的存储区操作就不会影响其他线程。对于ThreadLocal
首先我们来看看Looper的作用:
/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
在Looper的源码中有这么一段描述,大概的意思就是如果一个线程没有Looper对象的话,那么在这个线程中运行的Hanlder就不能从消息队列中取得消息。这个是网上找的Handler运行机制图
可以看出,Handler通过Looper从消息队列(消息队列是Looper内置的)中将Message取出来。那么Handler是如何获得Looper对象的呢?
我们来看一下Handler的构造方法
public Handler() {
this(null, false);
}
无参构造回调了另一个构造方法
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;
}
可以看到其中有一句mLooper =Looper.myLooper(),我们看看它的具体实现
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
然后看看get()方法的实现
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//this是ThreadLocal对象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;//value即为Looper对象
return result;
}
}
return setInitialValue();
}
可以看到get()方法会先获取当前线程,然后获取它的ThreadLocalMap,然后用ThreadLocal
而子线程中一开始是没有Looper对象的,而要通过Looper.prepare()将其变成Looper线程,我们来看一下源码:
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));
}
然后看一下get()方法的实现:
public T get() {
Thread t = Thread.currentThread();//获取当前操作的线程
ThreadLocalMap map = getMap(t);//获取Thread中的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//通过key取得键值对的Entry对象,key为ThreadLocal,value是线程对应的Looper
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;//value就是Looper对象
return result;//返回Looper后抛出异常
}
}
return setInitialValue();
}
然后我们看一下setInitialValue()的实现:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//插入map,此时value为null
else
createMap(t, value);//map不存在就创建
return value;//此时返回null
然后回到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));
}
因为返回了null,所以执行sThreadLocal.set(newLooper(quitAllowed)):
public void set(T value) {//从传入参数也可以看出,插入的值为Looper
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
至此,由于当前线程调用了Looper.prepare(),它就获取了一个Looper对象,并存储在了它的threadLocals中(就是上面提到的那个map)
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
根据英文说明可以看出,这个方法会由Android系统调用,为主线程自动准备一个Looper对象,而上面也说到,Handler在创建的时候,会去获取当前线程的Looper对象,所以在主线程中不需要使用Looper.prepare()方法来获得Looper对象。
根据我自己的理解,Thread、Handler和Looper三者的关系是Thread中的ThreadLocal用于存储和它相关的许多对象,其中包含了它私有的Looper对象,Handler为了能在子线程中接收消息(或修改UI),就必须获得当前线程的Looper。