提及Android的消息机制,相信这是Android开发者非常熟悉,并且相当基础的一个知识点了。那这里还有什么需要讲的呢?这里,先抛出一个问题:
问题
我们在使用Handler的时候,都知道其必须要跟一个Looper绑定的。而在UI线程可直接初始化Handler来使用,但是在子线程则不行,系统会抛出一个必须调用Looper.prepare()
的异常信息。缘由在于,当初始化Handler
的时候,其会通过Looper
来获取当前的Looper,代码如下:
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()");
}
//省略
}
那么,问题来了,为什么在子线程中,通过Looper.myLooper()
方法获取的就是为空呢?如果有人回答了Looper是线程相绑定的,那它是如何做到绑定的? 如果还知道答案的话,那就可以跳过本篇文章了。
代码分析
1. Looper的myLooper方法
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
此方法只是通过从变量sThreadLocal
中取出一个值。那么它的值是哪里来的呢?
2. Looper的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));
}
可以看出的是调用了这个方法之后,会在sThreadLocal
中存在一个新建的Looper
对象。那么看看这个sThreadLocal
是什么东西呢?
3. sThreadLocal的定义
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
是一个静态的ThreadLocal的变量,并且泛型指向的是Looper
对象。另外,注释中告诉了我们,如果我们不调用prepare
方法的话,那get
方法返回的是null。所以现在就引出咱们的重头戏ThreadLocal
了。
ThreadLocal
1. 定义
先看一下官方的解释:
Implements a thread-local storage, that is, a variable for which each thread
has its own value. All threads share the same {@code 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
{@code null} values.
这段话的意思是实现了一个线程相关的存储,即每个线程都有自己独立的变量。所有的线程都共享者这一个ThreadLocal
对象,
并且当一个线程的值发生改变之后,不会影响其他的线程的值。
2. 实现
ThreadLocal的类定义使用了泛型ThreadLocal<T>
,其中T指代的是在线程中存取值的类型。(对应Android中使用的ThreadLocal, T则存放的类型为Looper)
- set方法
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
其中的values
方法如下:
Values values(Thread current) {
return current.localValues;
}
方法中,先通过Thread.currentThread
来拿到当前线程,再拿到线程的values属性,并对此values属性进行赋值,其中key为当前的ThreadLocal
对象,value则是当前要存放的值。而这个values对象,其中维持了一个一维的object数组,采用偶数为key, (索引为index)奇数为value(索引为index + 1)的数据结构。
- get方法
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
在进行取值的时候,也是现获取当前线程,然后根据当前ThreadLocal的hash值与values的mask标志位进行与操作,来获取到当前ThreadLocal在这个线程的values中的位置,并通过判断其存放的key是不是当前ThreadLocal,若是的话,则返回index+1对应的值,即是我们所存放的值;若不是的话,则需要通过values的getAfterMiss方法来进行更进一步详细的搜索。
总结
ThreadLocal通过获取当前线程中的values属性,从而实现了每个单独线程的信息绑定。这样的话,Android的消息机制中,Looper便是采用ThreadLocal作为存储结构,所以looper对象的存储只会在当前线程中,子线程若是使用消息机制的话,必须调用Looper.prepare方法来在线程中新建一个Looper的对象。