本文主要是用来填一下在上篇博客《Android异步消息处理机制之looper机制》中留下的坑,上篇博客的地址为:http://blog.csdn.net/qq_28269905/article/details/69389751,以下正文。
looper里还有三个比较重要的方法,一个是 myLooper(),该方法是获得Looper对象的引用,源代码为:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
由之前的prepaer源代码可以知道该方法一定是在prepaer之后调用的。那我们就来看看其中涉及到的ThreadLocal,ThreadLocal是线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据,一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。在Looper、ActivityThread以及AMS中都用到了ThreadLocal。在looper中,如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处。ThreadLocal的详细博客请参见:http://blog.csdn.net/singwhatiwanna/article/details/48350919 。在android中,ThreadLocal的官方解释是实现一个线程本地的存储,也就是说,每个线程中都有自己的局部变量。需要注意的是,每个线程都共享一个ThreadLocal对象,但是每个线程在访问这些变量的时候都能得到不同的值,在looper中是如何体现的呢?直接来看代码,在looper的prepare中有这样一段代码:
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
再来看get和set方法的代码片段:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
Set中由于value是泛型,因此在存储的时候是依据key-value的形式将当前线程和looper对象进行存储的,也验证了上述的观点:一个线程是和looper绑定,looper里就是线程的消息队列和当前线程。再来看get,就是根据当前线程来取出我们存储的对象的,当然,在Looper中取出的是looper对象。因此,为什么所有线程只需要共享一个ThreadLocal对象的理由也就清晰了,是因为ThreadLocal内部是采用key-value的形式;来存储的,根据key的不同就能存入不同的value。也能根据不同的key来取出不同的value。
紧接着就是quit方法, 在想要退出消息循环时,调用Looper.quit()注意,这个方法是要在对象上面调用,很明显,用对象的意思就是要退出具体哪个Looper,示例代码为:
mLooper.quit();为什么一定要在对象上来调用呢?直接上quit的源码:
public void quit() {
mQueue.quit(false);
}
在这里我们可以看到 mQueue变量是looper内置的一个变量,作用域仅限于Looper内部,因此,我们在使用quit的时候一定要指明哪个对象,但是用此方法也会带来不安全的地方就是在Looper终止之前,消息队列中的需要被处理的消息可能还没有被分发。因此源码中推荐我们使用的是quitSafely方法,直接上代码:
public void quitSafely() {
mQueue.quit(true);
}
既然所有的焦点都指向了MessageQueue(它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表)中的quit这个方法,那我们直接来看源代码:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
通过观察以上源码我们可以发现: 当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。
当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。
无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。
最后一个方法是getMainLooper()方法,该方法的作用是可以返回主线程的looper,即可以将handler指定为主线程,简单的应用示例为:Handler handler = new Handler(Looper.getMainLooper());这样就可以在任意位置将其指定为主线程了,那么getMainLooper()方法是如何实现的呢?直接上源代码:
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
只是简单的返回了一个sMainLooper,再来看sMainLooper是在哪里赋值的,直接上源代码:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
其中源码中对于prepareMainLooper()有这样一段注释:The main looper for your application is created by the Android environment, so you should never need to call this function yourself,意思就是说应用程序的主Looper是由android环境创建的,因此你永远都不要去调用这个函数。
至此就明白了,在android环境初始化的时候,入口函数是ActivitThread.java 的main() 函数,在main() 函数中调用了Looper的静态方法prepareMainLooper(),将 sMainLooper赋值,我们只需要调用getMainLooper()就能返回它了,也就是获得主线程的环境
总结一下,Looper的工作:
。封装了一个消息队列,
。利用prepare将Looper和调用prepare方法的线程联系起来
。利用loop函数分发消息