Handler 系列三:Looper
Looper
prepare()

- Looper 采用静态变量 sThreadLocal:ThreadLocal 管理着与线程相关的 Looper 实例,sThreadLocal:ThreadLocal 并不真正持有实际存放的 Looper 对象,而是通过 Thread 的成员变量 ThreadLocalMap 去读写。也就是说,Looper 对象实际上存放在与其关联的线程 Thread 对象的成员变量 ThreadLocalMap - Entry 中。
- 当重复调用 Looper.prepare() 方法时,如果 sThreadLocal:ThreadLocal.get() 不为空,则会抛异常。
- 由此保证了一个线程 Thread 有且只有一个与之关联的 Looper 对象。
ThreadLocal
- 线程内部的数据存储类(ThreadLocal),将数据与线程相关联,实现在指定线程中存储数据且不被其他线程访问到这部分数据。这些特定于线程的数据,实际上是保存在线程对象
Thread中,当线程终止后这些数据就会被当作垃圾回收。
- 也就是说在不同线程中去访问同一个ThreadLocal对象会有不同的结果;并且对于ThreadLocal对象所做的操作仅限于各自线程的内部。
- 详见:链接
- 使用:通过使用 ThreadLocal ,来存储和获取与当前线程所关联的 Looper 对象。从而实现唯一性。
构造函数

- 构造 Looper 时,会同时构造一个与之关联的 MessageQueue() 对象并获取当前的线程对象 Thread。
loop()
- 在 Looper 关联的线程中,开启消息循环。一定要调用 Looper.quit()/quitSafely(… 来结束循环。
- 创建 Looper 并执行 loop() 循环的线程,会因为 loop() 的轮询使得线程一直处于可运行状态,更有可能因为 Looper 对象作为 GC Root 导致引用链没有断开,Looper - MessageQueue - Message - Handler - Activity ,从而持有超出生命周期的实例对象而引发内存泄漏。
- 因此在任务结束的时候,需要手动调用 quit()/quitSafely() 来退出。
- 在一个无限循环中,不断重复以下操作:
- ① 调用 MessageQueue.next() 获取下一个消息:该方法会阻塞;如果返回 null,则退出循环。
- ② 当有返回消息且不为空时,调用 Message.target.dispatchMessage() 方法分发消息。
- ③ 把分发后的消息对象 Message,回收到消息池中以便复用。
- 进入下一个循环。
quit()/quitSafely()

- 退出消息循环,终止 loop()。Looper 被退出后,任何向队列发布消息的尝试都将失败。
- quit():直接移除消息队列中所有消息,使得 MessageQueue.next() 因为没有消息而返回null,致使Looper.loop() 退出循环。
- quitSafely():只会剔除执行时刻 when 晚于当前调用时刻的 Message 消息。这样可以保证 quitSafely() 调用的时刻,满足执行时间条件的 Message 会继续保留在队列中被执行并在所有可执行的消息都执行完毕之后才退出 loop() 的轮询。
- 由上可知,方法内部是通过调用消息队列 MessageQueue.quit() 方法来实现的。
Looper.myLooper()

- 返回与当前线程关联的 Looper 对象。 如果调用线程未与 Looper 关联,则返回 null。
小结-Looper
Looper 如何实现线程唯一?Looper 实例保存在哪里?
- 【线程独有】通过 ThreadLocal 进行管理 Looper 实例,Looper 对象实际上存放在与其关联的线程 Thread 对象的成员变量 ThreadLocalMap - Entry 中,从而实现线程独有(保证每个线程对应一个独立的 Looper 实例对象)。
- ① 每个线程 Thread 都有且只有一个自己的 ThreadLocalMap,ThreadLocalMap 用于管理一个线程对象中的多个 ThreadLocal,其保存的元素是 Entry。
- ② ThreadLocalMap.Entry,key 以弱引用的方式保存 ThreadLocal 实例本身,value 以强引用的方式保存数据对象-比如 Looper 实例。
- 【线程唯一】当调用 Looper.prepare() 方法时,该方法会先判断当前线程是否已有 Looper 对象?如果有,则直接抛异常。进而保证线程独有的 Looper 实例有且只有一个。
- ThreadLocal:详见
Looper 异同:主线程和子线程
- 相同点
- 都是通过 Looper.prepare() 方法间接调用 Looper 的构造函数来创建的实例对象。
- 都是通过 ThreadLocal 进行管理 Looper 实例,Looper 对象实际上存放在与其关联的线程 Thread 对象的成员变量 ThreadLocalMap - Entry 中,从而实现线程独有,线程唯一。
- 不同点
- 主线程的 Looper 不需要也不可以 quit;而子线程的 Looper 需要也可以 quit。
- 主线程的 Looper 实例:为了方便每个线程都能获得主线程的 Looper 实例,Looper 类通过一个数据类型为 Looper 的私有静态变量 sMainLooper:Looper 缓存了主线程的 Looper 实例。
- 子线程的 Looper 实例:Looper 类通过一个数据类型为 ThreadLocal 的静态变量 sThreadLocal:ThreadLocal进行管理,该对象并不真正持有实际存放的 Looper 对象,而是通过获取当前线程 Thread 的成员变量 ThreadLocalMap 去读写。
Looper 的无限循环在做什么?
- Looper.loop():开启了一个无限循环。
- 在循环中调用 MessageQueue.next() 去读取消息队列中需要被执行的消息对象 Message,然后 ① 将该消息对象交给其成员变量 Message.target.dispatchMessage(Message) 进行处理,② 最后会将该消息对象 Message 进行回收,回收到消息池中以便复用。然后进入下一个循环。
- 这里需要注意的是,由于 loop() 方法是一个死循环,直到 Looper.quit()/quitSafely() 退出循环之前,该方法后面的代码时无法得到执行的。要避免在 Looper.loop() 调用后面写代码。
Looper 的无限循环在什么时候会退出?
- 当与之关联的消息队列 MessageQueue.next() 返回 null,才会跳出死循环。
- 实现原理:当 Looper.quit()/quitSafely() 方法被调用时,Looper 会调用 MessageQueue.quit() 方法来通知消息队列退出;当消息队列被标记为退出状态时,它的 next() 方法就会返回 null;而在 Looper.loop() 方法的循环中,当 MessageQueue.next() 方法返回 null 时,会直接退出循环。从而终止了 Looper.loop() 的死循环。
- 因此可以知道,在子线程中如果手动为子线程创建并开启了消息循环,那么在所有的消息处理完成以后应该主动调用 Looper.quit()/quitSafely() 方法来终止消息循环,否则这个子线程会一直处于阻塞等待的状态。
Looper.quit()/quitSafely()
- quit():直接移除消息队列中所有消息,使得 MessageQueue.next() 因为没有消息而返回null,致使Looper.loop() 退出循环。
- quitSafely():只会剔除执行时刻 when 晚于当前调用时刻的 Message 消息。这样可以保证 quitSafely() 调用的时刻,满足执行时间条件的 Message 会继续保留在队列中等待被Looper调度执行,并在所有可执行的消息都执行完毕之后才去退出 loop() 的轮询。
- 方法内部是通过调用消息队列 MessageQueue.quit() 方法来实现的。
- 为什么说该方法会终止 loop() 的消息循环?链接。
小插曲
- 1、一个线程有且只有一个 Looper 对象,一个 Looper 有且只有一个 MessageQueue 对象。
- 2、一个 Handler 有且只有一个 Looper 对象与之关联;反之,一个 Looper 可以与多个 Handler 对象相关联。同理,一个线程会有多个与之关联的 Handler 对象,而一个 Handler 对象只会与一个线程相关联。
- 3、MessageQueue,持有一个数据类型为 Message 的成员变量,用于存储消息队列中的多个消息;Message 以队列的形式存储所有消息,通过单向链表的方式 next 指针指向下一个消息。
- 问:一个 App 拥有几个 Looper 实例?几个 MessageQueue 实例?几个 Message 实例?几个 Handler 实例?
- 有几个线程启动了消息循环,就有几个 Looper 实例;MessageQueue 实例同理(因为一个 Looper 对应一个 MessageQueue)。至少有一个 Looper 实例和一个 MessageQueue 实例,对应主线程 ActivityThread。
- Message 会通过池化的方式来实现内存复用,最多缓存50个消息对象 Message。
- Handler 实例的个数,看使用。