handler源码学习(3) — Looper
Handler是面试必问系列问题之一。本系列将从初学者的视角分析面试中常见的问题。
handler源码学习(1) — Handler
handler源码学习(2) — Message
handler源码学习(3) — Looper
handler源码学习(4) — MessageQueue
这里只讲解几个重要方法。其他方法(isCurrentThread(),myLooper()…)很简单就不讲解了。
prepare()
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
可以看到静态创建一个泛型为Looper ThreadLocal。然后通过prepare方法,将新建的looper设置进去。这里有一个考点,为什么要用ThreadLocal而不是直接创建一个局部变量Looper looper?要想解答这个问题,就要了解ThreadLocal的作用。从名字我们就可以看到ThreadLocal叫做线程本地变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,再看sThreadLocal是一个静态变量,这下明白了吗?静态变量是不会被回收的,也就是调用prepare之后,就会缓存每个线程对应的looper,这样就避免了重复创建looper。且只有当前线程可以获取到looper。
那既然是静态变量,就有另一个问题,什么时候回收掉这个looper?总不能线程都销毁了,还不回收吧?其实ThreadLocal并不存储这个looper的。存储这个Looper的是线程的成员变量threadLocals(注意是threadLocals,多了一个s),threadLocals是一个ThreadLocalMap。线程销毁时threadLocals就会被销毁。此时looper就可以被回收了。而threadLocal只不过是获取的key。也就是我们只缓存了一个通用的key。这么说可能太抽象,我们用伪代码举个例子:
private ThreadLocal key = new ThreadLocal();
class Thread{
Map<ThreadLocal,Object> map = new HashMap();
}
class ThreadLocal{
void set(Object value){
Thread thread = getCurrrentThread()
thread.map.put(this,value)
}
}
不知道看懂没,从始至终ThreadLocal是不存储任何东西的(为了学这个,把ThreadLocal的源码也看了一遍,我真是个人才)。
loop()
public static void loop() {
//就是sThreadLocal.get()
final Looper me = myLooper();
if (me == null) {
//还记得子线程不调用prepare()的报错吗?
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
···省略代码···
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// 没有消息直接return
return;
}
···省略代码···
try {
//msg.target就是handler
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
···省略代码···
//回收消息,还记得Message的时候我们讲解回收后会怎样吗?
msg.recycleUnchecked();
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
注释已经很清楚了,省略的是一些日志打印相关的。这里有几个知识点;
- 为什么子线程不调用prepare()会崩溃?
- 因为不调用prepare(),sThreadLocal.get()为null。
- loop()做了什么?
- 不断的取消息
- 没有消息时也一直取消息吗?
- 是在不断的取。只不过queue.next(); // might block。当取下一个时,可能会阻塞。具体如何我们MessageQueue分析。
总结
解决问题
- prepare()为什么使用ThreadLocal来缓存创建的Looper?
- 为什么子线程不调用prepare()会崩溃?
- loop()做了什么?没有消息时也一直取消息吗?
留下疑问
MessageQueue.next如何阻塞?
推荐阅读
-
android的消息处理机制(图文+源码分析)—Looper/Handler/Message
-
PHP getID3类的使用方法学习笔记【附getID3源码下载】
-
SQLite3源码学习之test_vfs的共享内存机制讲解
-
SQLite3源码学习之test_vfs框架实例讲解
-
HTML5开发学习(3):本地存储之Web Sql Database(附源码)
-
Android消息机制(3)- Handler和Looper
-
Android消息通信机制Handler详解,Handler,Looper,MessageQueue,源码解析,讲解这几个类怎么配合工作的
-
Android消息机制三剑客之Handler、Looper、Message源码分析(一)
-
handler源码分析之Looper
-
Android消息机制原理,仿写Handler Looper源码解析跨线程通信原理--之仿写模拟Handler(四)