本文以android-26的源码为基础进行分析
Looper与ThreadLocal
首先我们从Looper的源码开始
在使用Handler和Looper的时候,我们知道,Handler发送消息给Looper,加入其消息队列,Looper则不断循环去除队列前端的消息,并执行Handler中的回调代码。
但是Looper在使用前需要先执行Looper.prepare()操作,否则会报如下的错误: Can't create handler inside thread that has not called Looper.prepare(),因为此时Looper对象并没有初始化
那么我们来看一下Looper.prepare()做了什么
public final class 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));
}
}
从sThreadLocal.set(new Looper(quitAllowed)) 这一句代码我们发现, Looper.prepare()当中new了一个Looper对象,并将这个Looper对象设置进了一个叫做的东西里去,我们想要取当前线程的Looper对象的话,可以直接调用Looper.myLooper()这个静态方法。
public final class Looper {
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
来看一下sThreadLocal的定义:
public final class Looper {
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal sThreadLocal = new ThreadLocal();
}
我们发现这个sTheadLocal是Looper中的一个静态对象,类型为ThreadLocal,接下来我们需要解释一下ThreadLocal是个什么东西
ThreadLocal
ThreadLocal的一个特点就是线程间互相隔离,在每个线程取同一个ThreadLocal对象都会得到一个不同的值。例如Looper, 每个线程的都只有一个Looper,且在各个线程中调用Looper.myLooper()的方法都会返回其自己的Looper。
在上面我们看到Looper.myLooper()方法通过调用sThread.get()方法获取到了自己对应的Looper,那么我们来看一下ThreadLocal的get()的方法。
public class ThreadLocal {
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
}
ThreadLocal中的get()方法首先获取当前的thread对象,然后得到了当前Thread对象中的ThreadLocalMap对象,以当前的ThreadLocal对象为key获取到ThreadLocalMap存的value,在这个例子中这个value就是Looper啦。
暂停一下,咱们捋一捋,首先Looper通过其中的静态变量sThreadLocal.get()方法获取到当前线程的ThreadLocalMap对象threadLocals 。然后又以sThreadLocal这个对象本身为key取到了threadLocals存着的Looper对象。
看着好像有点绕,那么我们换一种说法,每个Thread都存了一个map(实际是个数组),可以以Looper中的静态对象sThreadLocal为key,在这个map中取到这个线程的Looper。由于sThreadLocal是个静态对象,所以对于任意一个线程,这个key是固定的,这样一来,通过Thread.currentThread()获取到了某个线程就等于获取到了其中的Looper对象。
这么一来是不是就清晰了很多,Looper中之所以要定义这么一个sThreadLocal的静态对象,实际上就相当于定义了一个全局存在的static final的key,只不过这个key是ThreadLocal对象而已。
/** 本文不会对ThreadLocalMap进行更深入的分析,各位可以自行阅读源码去查看其具体实现**/
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
最后我们稍微圆一圆整个流程,现在我们知道了我们获取到了一个Thread,就能以sThreadLocal为key get到其中的Looper对象。那么我们是什么这个Looper对象set到对应的Thread当中的呢。记不记得文章开头Looper.prepare()的过程,其中调用了sTheadLocal.set(T)方法,最后是怎么将Looper对象绑定到线程上的,来看一下源码:
public class ThreadLocal {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
}
首先获取到当前线程t,然后得到其中的ThreadLocalMap对象,然后将这个Looper对象放到了map中对应sThreadLocal的地方。相信这里大家都能很轻松的理解了。
最后总结一下ThreadLocal存在的意义:
- 封装了对当前所在线程中ThreadLocalMap的set和get操作,默认以自身作为key值进行数据存储
- 以泛型对外暴露,支持了所有数据类型
因此在使用ThreadLocal的时候,每一个数据都需要新建一个ThreadLocal对象为key
例如针对每一个线程都要存储一个String,需要新建一个ThreadLocal