Android 消息机制概述
Android 的消息机制主要是指 Handler 的运行机制。
Handler类的引入
Android 规定访问 UI 只能在主线程中进行,如果在子线程访问UI,那么程序就会抛出异常。
ViewRootImpl 对UI操作做了验证,这个验证工作是由 ViewRootImpl 的 checkThread 方法来完成的。
void checkThread() {
Thread current = Thread.currentThread();
if (mThread != current) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views."
+ " Expected: " + mThread.getName()
+ " Calling: " + current.getName());
}
}
由于这一点的限制,导致必须在主线程中访问 UI,但是 Android 又建议不要在主线程中执行耗时任务,否则会导致程序无法响应即 ANR。系统提供 Handler 主要原因就是为了解决在子线程无法访问UI的矛盾。
为什么不允许在子线程中访问 UI?
Android 的 UI 控件非线程安全,多线程并发访问可能会导致 UI 控件处于不可预期的状态。
为什么不对 UI 控件的访问加上锁机制?
首先加上锁机制会让 UI 访问的逻辑变得复杂;其次锁机制会降低 UI 访问的效率,因为锁机制会阻塞某些线程的执行。
最简单且高效的方法就是采用单线程模型来处理 UI 操作,Handler 就是线程间通信的桥梁。
Handler 的执行流程
- Handler: 消息发送者与处理器,绑定特定 Looper,负责发送 Message/Runnable 并处理回调。
- Message: 消息载体,存储数据、回调与目标 Handler,可从消息池复用。
- MessageQueue: 消息队列,单链表结构,按时间排序,线程安全的入队/出队操作。
- Looper: 消息循环器,每个线程唯一(ThreadLocal 隔离),死循环轮询消息并分发。
Handler初始化
Handler 创建时,会自动获取当前线程的 Looper,并绑定其内部的 MessageQueue,构建起消息循环系统。
如果当前线程没有 Looper(比如普通子线程),直接创建 Handler 会抛出 Can't create handler inside thread that has not called Looper.prepare() 异常。
解决方案:要么为当前线程调用 Looper.prepare() 创建 Looper,要么直接在已有 Looper 的线程(如主线程)中创建 Handler。
消息发送与入队
Handler 提供了两种方式发送任务,本质上最终都会走同一套流程:
- post(Runnable):将 Runnable 任务投递到 Handler 内部的 Looper 中处理,底层会把 Runnable 包装成 Message,最终调用
send()系列方法。 - sendMessage(Message):直接发送消息对象。
无论是 post 还是 sendMessage,最终都会调用 MessageQueue.enqueueMessage(),将消息按时间顺序插入到 MessageQueue 中排队。
Looper 轮询与消息分发
绑定 Handler 的 Looper 会在 loop() 方法中启动一个无限循环,持续调用 MessageQueue.next(),从队列中取出消息。
当 Looper 发现队列中有新消息到来时,就会取出消息,并调用消息中 target(也就是发送它的 Handler)的 dispatchMessage() 方法。
最终会执行:
- 要么执行 Message 里的 Runnable 回调;
- 要么回调 Handler 的
handleMessage()方法。
注意:最终业务代码运行的线程 = 绑定的 Looper 原本所在的线程,和在哪条线程创建 Handler、在哪条线程发消息,全部无关。
Handler 相关方法
构造方法(创建Handler)
决定 Handler 绑定哪个线程的 Looper。
new Handler():绑定当前线程的 Looper,如果当前线程没有 Looper 会报错。new Handler(Looper.getMainLooper()):绑定主线程 Looper。new Handler(Looper.myLooper()):绑定当前线程 Looper,和空参构造效果一致。
发送 Message 消息(send 系列)
用于传递带标识、数据的消息
sendMessage(Message, msg):立即发送消息sendEmptyMessage(int what):发送空消息(仅传标识)sendMessageDelayed(msg, 延时毫秒):延时发送消息sendMessageAtTime(msg, 时间戳):定时发送消息(指定时间执行)
发送 Runnable 任务(post 系列)
直接切换线程执行代码
post(Runnable r):立即执行任务postDelayed(Runnable r, 延时毫秒):延时执行任务postAtTime(Runnable r, 时间戳):定时执行任务
移除消息/回调(防内存泄露、取消任务)
removeMessages(int what):移除指定标识的消息removeCallbacks(Runnable r):移除指定的 Runnable 任务removeCallbacksAndMessages(null):移除所有消息和任务
消息处理/回调方法(重写用)
handleMessage(Message msg):重写此方法,接收并处理消息dispatchMessage(Message msg):系统内部分发消息(优先级:post 回调 > 接口回调 > handleMessage)
Looper 配套方法(配合 Handler 使用)
Looper.getMainLooper():获取主线程 LooperLooper.myLooper():获取当前线程 LooperLooper.prepare():子线程创建 Looper(手动初始化)Looper.loop():开启消息循环
Android 消息机制分析
四大核心组件
Message(消息载体)
线程间通信的数据单元。
Message 的核心属性
/**
* 消息标识码
* 用户定义的消息代码,以便接收者识别此消息的内容。
* 每个 Handler 都有自己的命名空间,因此不必担心与其他 Handler 冲突
* 如果未指定,此值为 0,使用非 0 的值来表示自定义消息代码
*/
public int what;
/**
* 低成本的整数存储
* 如果只需要存储几个整型值,arg1,arg2是setData()的低成本替代方案
* (无需创建 Bundle 对象,开销极小)
*/
public int arg1;
public int arg2;
/**
* 传输任意对象
* 在跨进程通信(IPC,如 Messenger)时,
* 此字段只能包含系统框架定义的Parcelable对象
*/
public Object obj;
/**
* 当 arg1/arg2/obj 不够用时,通过 setData(Bundle) 传递复杂数据。
* 它会涉及 Bundle 的克隆或序列化,开销相对较大。
*/
/*package*/ Bundle data;
/**
* 执行的时间戳
* 基于 SystemClock.uptimeMillis()
* MessageQueue 是按照 when 的先后顺序对消息进行排序的单链表。
*/
public long when;
/**
* 发送该消息的 Handler 引用
* 标记这个消息是由哪个 Handler 发出的,
* 同时也决定了最终由哪个 Handler 来处理。
*/
/*package*/ Handler target;
/**
* 如果是通过 handler.post(Runnable) 发送的消息,
* Runnable 会赋值给 callback
* 如果此字段不为空,
* 消息处理时会直接运行这个 Runnable,而不会走 handleMessage
*/
/*package*/ Runnable callback;
/**
* 指针,指向下一个 Message
* 在 MessageQueue 中,它指向队列中的下一条消息
* 在 sPool(对象池)中,它指向池子里的下一个可用空闲对象
*/
/*package*/ Message next;
arg1和arg2是为了避免开发者仅仅为了传几个数字就去new Bundle()或创建对象,这体现了极简的内存优化思想。Message本质上是一个单链表的节点,这全靠next指针维持。
什么是享元模式?为什么Message需要它?
在 Android 系统中,消息的产生是极其频繁的(比如屏幕每秒刷新 60 次,底层就会发送 60 个 VSYNC 消息;你滑动一下屏幕,会产生无数个 Touch 消息)。 如果每次发消息都 new Message(),处理完就变成垃圾。那么系统内存会瞬间暴涨,触发 Java 的垃圾回收(GC)。频繁 GC 会导致所有线程暂停(Stop The World),进而导致画面严重卡顿(掉帧)。
享元模式 (Flyweight) 的思想: 享(共享)元(对象)。既然消息对象结构都一样,只是每次装载的数据不同,那能不能建一个池子? 用完的消息不销毁它,而是把里面的数据擦除,放回池子里。下次谁要发消息,直接从池子里捞一个干净的空壳拿去用。 这就是对象池复用机制。
Message 对象池
Message 类内部自己实现了一个单链表结构的对象池。
对象池的定义
public static final Object sPoolSync = new Object(); // 锁对象,保证多线程池操作安全
private static Message sPool; // 对象池的链表头指针
private static int sPoolSize = 0; // 当前池子里有几个空闲消息
private static final int MAX_POOL_SIZE = 50; // 重点!池子的最大容量是 50
obtain() —— 从池子里“捞”消息
public static Message obtain() {
synchronized (sPoolSync) { // 加锁,防止多个线程同时来捞消息
if (sPool != null) {
Message m = sPool; // 拿到池子最顶上的那个(链表头节点)
sPool = m.next; // 池子的头指针向后移一位
m.next = null; // 把拿出来的这个消息和池子断开连接
m.flags = 0; // 清除正在使用的标记
sPoolSize--; // 池子里的存货减一
return m; // 把复用的消息返回给你!
}
}
return new Message(); // 只有当池子是空的(比如刚启动时),才真的去 new 一个新对象
}
recycleUnchecked() —— 处理完后“洗干净”扔回池子
在 Looper.loop() 中,msg.target.dispatchMessage(msg) 执行完毕后,Looper 会自动调用这个方法回收消息。
void recycleUnchecked() {
// 1. 洗干净:把所有数据清空,防止内存泄漏(比如 obj 还持有大图的引用)
flags = FLAG_IN_USE; // 标记它已经被回收到池子里了
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
// 2. 扔回池子:把它插到池子链表的头部
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) { // 如果池子还没满(<50)
next = sPool; // 它的下一个指向当前的头节点
sPool = this; // 它自己成为新的头节点
sPoolSize++; // 存货加一
}
// 如果池子满了(>=50),啥也不干,它就会被虚拟机当成垃圾 GC 掉
}
}
MessageQueue(消息队列)
不是真正的队列!!!虽然名字叫 Queue,但它的底层数据结构其实是一个按时间排序的单向链表。
为什么使用单链表?
真正的队列是 FIFO(先进先出),只能在尾部插入,头部取出。 但 Android 的消息是有延迟时间 (delay) 的。比如你发了一个延迟 5 秒的消息,又发了一个即时消息。即时消息必须排在延迟消息前面。 为了实现按时间优先级插队,单链表是最优选择:只需要打断一根指针 next,就能轻松在中间插入新节点。
消息的三种身份
在 MessageQueue 的单链表中,节点分为三种:
- 普通消息 (Sync Message)
- 特征:
target != null且isAsynchronous() == false。 - 地位:普通私家车。平时通过
handler.sendMessage()发送的都是此类,老老实实按时间排队。
- 特征:
- 异步消息 (Async Message)
- 特征:
target != null且isAsynchronous() == true。 - 地位:救护车/特权车。无视同步屏障,优先放行。可以通过
msg.setAsynchronous(true)将消息设为异步发送异步消息,在没有同步屏障的情况下,异步消息在执行顺序上与普通同步消息完全一致。
- 特征:
- 同步屏障 (Sync Barrier)
- 特征:
target == null。 - 地位:交警路障。本质是一个没有 Handler 收件人的空头消息,专门用于拦截它后面的所有普通消息。
- 特征:
为什么要这么设计?
为了 UI 刷新的极致流畅。
问题:Android 屏幕刷新率通常为 60Hz/120Hz,每隔 16.7/8.3ms 底层会发出 VSYNC 信号触发 UI 绘制。如果主线程 MessageQueue 中堆积了大量普通耗时消息(比如你的数据计算、日志打印、网络回调解析等),如果屏幕刷新的指令排在这些任务后面,会导致严重的掉帧和卡顿。
解决方案:当需要刷新屏幕时,系统(Choreographer)会先往消息队列队头丢一个同步屏障,然后发送一个异步消息(VSync 信号),此时,即使队列里有再多普通任务,Looper 也会优先处理这个异步的刷新指令。刷新完成后,系统再移除屏障,让普通任务继续执行。
核心机制
MessageQueue 最核心的代码就两个方法:装进去(enqueueMessage)和取出来(next)。
生产者入口:enqueueMessage(Message msg, long when)
核心逻辑:寻找合适的位置,把消息插进链表。
enqueueMessage
boolean enqueueMessage(Message msg, long when) {
/**
* target 校验
* 所有进入队列的消息必须绑定一个 Handler(即target)
* 唯一例外:系统内部有一种特殊消息叫同步屏障(Sync Barrier),它的 target 是空的,
* 但它不是通过这个方法进入队列的,而是通过 postSyncBarrier。
*/
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
/**
* 根据模式选择执行函数
*/
if (mUseConcurrent) {
// 走无锁高并发通道
return enqueueMessageConcurrent(msg, when);
} else {
// 走传统加锁单链表通道
return enqueueMessageLegacy(msg, when);
}
}
enqueueMessageLegacy
private boolean enqueueMessageLegacy(Message msg, long when) {
synchronized (this) { // 悲观锁:锁住整个 MessageQueue,防止多线程同时修改链表
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
} // 异常抛出:不能发送正在使用的消息
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG_L, e.getMessage(), e);
msg.recycle();
return false;
} // 异常处理:如果线程快死了(Looper退出),拒绝接收并回收消息
msg.markInUse(); // 标记:这个消息正在处理中了
msg.when = when; // 记录执行时间
incAndTraceMessageCount(msg, when);
Message p = mMessages; // p 指向当前链表的头节点
boolean needWake;
// 场景A:队列是空的,或者这个新消息的时间比头节点还要早
if (p == null || when == 0 || when < p.when) {
// 头插法
msg.next = p;
mMessages = msg;
needWake = mBlocked; // 如果阻塞则准备唤醒
if (p == null) {
mLast = mMessages;
}
} else {
// 判断是否需要唤醒:
// 通常插在后面不需要唤醒,除非遇到了“同步屏障”(p.target==null)
// 并且你插入的是队头第一个“异步消息”(msg.isAsynchronous())
needWake = mBlocked && p.target == null && msg.isAsynchronous();
if (Flags.messageQueueTailTracking()) {
// 如果开启了尾部追踪功能 (Tail Tracking)
if (when >= mLast.when) {
// 直接和 mLast (尾节点) 的时间比。
// 如果比尾部还晚,直接 O(1) 挂在尾巴上!
needWake = needWake && mAsyncMessageCount == 0;
msg.next = null;
mLast.next = msg;
mLast = msg;
} else {
// 遍历插入到队列中间
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
if (p == null) {
mLast = msg;
}
msg.next = p;
prev.next = msg;
}
} else {
// 老版本的遍历插入,没有 O(1) 尾部优化
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
/*
* 如果正在执行此代码块,则表示我们构建的版本没有尾部跟踪功能 -
* 具体来说:Flags.messageQueueTailTracking() == false。这是在构建时确定的,
* 因此该标志在运行时不会改变。
*
* 由于我们不想在代码中添加额外的检查,因此仅在可能使用 mLast 时才检查尾部跟踪。
* 否则,我们会继续将 mLast 更新为列表的尾部。
*
* 然而,在这种情况下,我们没有正确维护 mLast。由于我们从不使用它,这没问题。
* 但是,我们有泄漏引用的风险。因此,在这种情况下将 mLast 设置为 null,以避免任何
* 消息泄漏。其他地方永远不会使用该值,因此我们不会出现空指针解引用的问题。
*/
mLast = null;
}
}
if (msg.isAsynchronous()) {
mAsyncMessageCount++;
}
if (needWake) {
nativeWake(mPtr);
} // 最终调用 JNI 唤醒 epoll
}
return true;
}
设计思想:以空间换时间
仅仅增加了一个 mLast 指针,就完美解决了“大量堆积的长延迟消息导致队列插入极慢”的性能瓶颈。
唤醒机制
mBlocked变量:当MessageQueue.next()里没有消息,或者最早的消息时间还没到时,底层会调用nativePollOnce()让主线程进入休眠(阻塞态),此时mBlocked = true。nativeWake(mPtr):如果主线程休眠了,子线程发消息时,必须调用这个 JNI 方法,通过 Linux 的epoll机制向底层写入一个特殊的 I/O 事件,把主线程“踹醒”。
大原则:唤醒线程是一个极其昂贵的操作(涉及内核态到用户态的上下文切换,极度消耗 CPU)。因此 Google 的设计原则是:非必要,绝不唤醒。
什么时候需要唤醒?(needWake = true)
- 场景一:插在对头(通常需要唤醒)
队列是空的,或者你发送的是一个“即时消息(when==0)”,或者你发送的消息时间比当前队头的第一条消息还要早。
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked; // 核心逻辑:只要线程在睡觉,就必须叫醒!
}
- 场景二:插在中间或队尾(通常不需要唤醒)
发送的消息时间比队头消息晚,排在了别人后面。
} else {
// Message is to be inserted at tail or middle of queue.
// Usually we don't have to wake up the event queue...
// 默认情况,插在后面不需要唤醒
- 场景三:唯独一种特例 —— 【同步屏障下的首个异步消息必须唤醒】
队头是“同步屏障”(拦截所有普通消息),你插入了一个“异步消息”,且它是队列中唯一的异步消息(mAsyncMessageCount == 0),此时必须唤醒,因为之前的休眠可能是因为没有异步消息可做而进入的。
// unless there is a barrier at the head of the queue and
// the message is the earliest asynchronous message in the queue.
// 【判断条件1】:线程在睡觉 + 队头是个同步屏障(target==null) + 新来的是异步消息
needWake = mBlocked && p.target == null && msg.isAsynchronous();
// 然后在遍历链表寻找插入位置时...
for (;;) {
// ... (寻找插入位置逻辑)
// 【判断条件2】:如果在这个新消息前面,已经有其他的异步消息了
if (needWake && p.isAsynchronous()) {
needWake = false; // 取消唤醒!
}
}
enqueueMessageConcurrent
这是 Android 15 之后引入的**:基于 CAS 的无锁并发消息队列(Concurrent MessageQueue)**。
在传统模式(Legacy)下,MessageQueue 是用 synchronized(this) 保护的。 现在的 App 大量使用 RxJava、Kotlin 协程,可能有几十个后台子线程同时干活,干完活后都要通过 handler.post() 把结果发给主线程更新 UI。 几十个子线程同时竞争同一把 synchronized 锁,会导致大量的线程被迫挂起(陷入内核态排队)、唤醒,产生极其严重的上下文切换开销(Context Switch),造成主线程微卡顿。
新架构的解决方案:彻底干掉 synchronized,采用无锁化(Lock-Free)设计!
在并发模式下,MessageQueue 的结构发生了裂变,分为两个区域:
[发送方:其他子线程] (高并发乱入)
↓
【Treiber Stack (无锁压栈区)】 <-- 靠 CAS 保证线程安全,大家随便扔消息
↓
(Looper 批量转移)
↓
【Priority Queue (主线程私有队列)】 <-- 只有主线程能访问,按时间排序
↓
[接收方:主线程 Looper 执行]
核心流程:
- 合法性校验:检查消息是否正在使用,线程是否已经死掉。
- 生成绝对序列号:通过原子操作(Atomic)给消息发一个唯一的排队号(Seq),保证哪怕时间完全一样,也有先来后到。
- 包装成节点:把普通的
Message包装成支持无锁链表的MessageNode。 - 【终极分流】判断是谁在发消息:
- 同线程发送(VIP 通道):如果是主线程给自己发消息,根本不存在并发冲突!直接绕过栈,塞进本地优先队列。
- 跨线程发送(地狱难度):进入
while(true)死循环,利用 CAS 技术尝试把消息压入栈顶。如果发现有人抢先了,就重新读取栈顶,再试一次,直到成功。
- 判断唤醒:在压栈的同时,读取栈底记录的 Looper 状态(醒着?死睡?定时睡?),决定是否需要调用
nativeWake唤醒底层。
源码分析:
private boolean enqueueMessageConcurrent(Message msg, long when) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
return enqueueMessageUnchecked(msg, when);
}
private boolean enqueueMessageUnchecked(@NonNull Message msg, long when) {
// 无锁判断线程是否存活(取代了以前 synchronized 里的 mQuitting)
if ((boolean) sQuitting.getVolatile(this)) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG_C, e.getMessage(), e);
msg.recycleUnchecked();
return false;
}
// 生成严格自增的序列号 seq
// 用 getAndAdd 原子操作,就算 100 个线程同时执行,拿到的号码也绝对不会重复!
long seq = when != 0 ? ((long) sNextInsertSeq.getAndAdd(this, 1L) + 1L)
: ((long) sNextFrontInsertSeq.getAndAdd(this, -1L) - 1L);
/* TODO: 在Message中添加一个MessageNode成员,这样我们就可以避免这种内存分配 */
// 把普通 Message 包装成支持并发栈操作的 MessageNode
MessageNode node = new MessageNode(msg, seq);
msg.when = when;
msg.markInUse();
incAndTraceMessageCount(msg, when);
if (DEBUG) {
Log.d(TAG_C, "Insert message what: " + msg.what + " when: " + msg.when + " seq: "
+ node.mInsertSeq + " barrier: " + node.isBarrier() + " async: "
+ node.isAsync() + " now: " + SystemClock.uptimeMillis());
}
final Looper myLooper = Looper.myLooper();
// 当前所在的线程,是不是就是这个 Handler 要送达的线程?
if (myLooper != null && myLooper.getQueue() == this) {
node.removeFromStack();
// 如果是,根本不存在多线程抢夺!直接插进私有的优先队列!
insertIntoPriorityQueue(node);
/*
* 即使我们是当前线程,我们仍然需要这样做,否则 next() 可能会无限期休眠。
*/
if (!mMessageDirectlyQueued) {
mMessageDirectlyQueued = true;
nativeWake(mPtr);
}
return true; // 直接成功,避开下面复杂的死循环!
}
while (true) { // 【无锁设计的标志:自旋重试】
// 抓取当前的栈顶状态(old)
StackNode old = (StackNode) sState.getVolatile(this);
boolean wakeNeeded;
boolean inactive;
// 让新节点的 next 指向这个旧的栈顶(压栈准备动作)
node.mNext = old;
// 极其复杂的判断:判断主线程现在到底处于什么状态?
switch (old.getNodeType()) {
// 线程活跃状态
case STACK_NODE_ACTIVE:
/*
* The worker thread is currently active and will process any elements added to
* the stack before parking again.
*/
// 线程醒着在干活呢!不需要叫醒它!
node.mBottomOfStack = (StateNode) old;
inactive = false;
node.mWokeUp = true;
wakeNeeded = false;
break;
// 无限期睡眠状态
case STACK_NODE_PARKED:
// 线程正在死睡(队列没消息了)。必须叫醒它!
node.mBottomOfStack = (StateNode) old;
inactive = true;
node.mWokeUp = true;
wakeNeeded = true;
break;
// 定时睡眠状态
case STACK_NODE_TIMEDPARK:
// 线程正在睡觉,但设置了“闹钟”(例如原本队列里有个 5 秒后的任务,线程计划 5 秒后自动醒来)
// 如果新消息比原计划的消息更早执行(>= 逻辑在底层时间戳比较中通常意味着新消息更紧急),
// 则 wakeNeeded = true,提前叫醒线程;否则让它按原计划醒来即可。
node.mBottomOfStack = (StateNode) old;
inactive = true;
wakeNeeded = mStackStateTimedPark.mWhenToWake >= node.getWhen();
node.mWokeUp = wakeNeeded;
break;
// 堆栈顶端是另一条消息
default:
// 栈顶是个别的子线程刚塞进来的消息,继承它底部的状态...
MessageNode oldMessage = (MessageNode) old;
// 通过 mBottomOfStack 找到这串消息序列的最底层,
// 看看线程最初是因为什么而进入当前状态的(是醒着、死睡还是定时睡)。
// 判断 inactive:如果最底层是 PARKED 或 TIMEDPARK,说明线程还在睡觉。
// 判断 wakeNeeded:这是一种“接力唤醒”优化。如果底层是定时睡眠,且新消息更紧急,
// 且之前的消息还没触发唤醒(!oldMessage.mWokeUp),那么当前这条消息就承担起唤醒线程的责任。
node.mBottomOfStack = oldMessage.mBottomOfStack;
int bottomType = node.mBottomOfStack.getNodeType();
inactive = bottomType >= STACK_NODE_PARKED;
wakeNeeded = (bottomType == STACK_NODE_TIMEDPARK
&& mStackStateTimedPark.mWhenToWake >= node.getWhen()
&& !oldMessage.mWokeUp);
node.mWokeUp = oldMessage.mWokeUp || wakeNeeded;
break;
}
// 核心机制:CAS 原子提交
// sState.compareAndSet(this, old, node):
// 尝试原子性地将 MessageQueue 的状态从 old(之前的堆栈顶端)更新为 node(当前新消息节点)。
// 它取代了传统的 synchronized(this),无锁竞争
// 如果有多个线程同时发送消息,只有一个线程能成功执行 compareAndSet。
if (sState.compareAndSet(this, old, node)) {
// 如果返回 true,说明我成功把消息压入栈顶了!没有人跟我抢!
if (inactive) {
if (wakeNeeded) {
nativeWake(mPtr); // 按需唤醒
} else {
mMessageCounts.incrementQueued();
}
}
return true;
}
// 自旋重试
// 如果返回 false(说明在计算期间有其他线程抢先插入了消息),
// 代码会继续留在 while(true) 循环中重新获取最新的 old 状态并再次尝试,直到成功。
}
}
设计思想:乐观锁思想与 CAS (Compare-And-Swap,比较并交换)
悲观锁(synchronized)认为一定会有冲突,所以先锁门再干活。乐观锁认为冲突很少,干完活再去对比(CAS),一旦发现别人动过了,大不了重来(自旋)。CAS 是 CPU 支持的原子操作指令,是无锁并发编程的核心,用于实现多线程同步。
消费者出口:Message next()
与生产者对应,也有两个出口
Message next() {
if (mUseConcurrent) {
return nextConcurrent();
} else {
return nextLegacy();
}
}
nextLegacy()
private Message nextLegacy() {
// 如果消息循环已经退出并被释放,则返回此处。
// 如果应用程序在退出后尝试重启循环器(这是不支持的操作),就可能发生这种情况。
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // 初始化为 -1,标记这是第一次循环
int nextPollTimeoutMillis = 0; // 0:不阻塞立刻返回;-1:永久阻塞;>0:阻塞对应毫秒数
for (;;) {
if (nextPollTimeoutMillis != 0) {
// 如果即将进入休眠,强制把 Binder 线程池里积压的 IPC 命令发出去。
// 因为主线程都要睡觉了,别把跨进程的活儿给耽误了。
Binder.flushPendingCommands();
}
// 这是一个 JNI 调用,底层通过 Linux 的 epoll 机制阻塞当前线程。
// 如果 nextPollTimeoutMillis = 0,不休眠。
// 如果 = -1,死睡,直到被 nativeWake 唤醒。
// 如果 > 0,睡指定的毫秒数后自己醒来。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 尝试检索下一条消息。若找到则返回。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 【核心拦截逻辑】:如果队头是同步屏障 (target == null)
if (msg != null && msg.target == null) {
// 开启搜救模式:顺着链表往下摸,直到找到第一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 场景 A:找到了消息,但它的执行时间还没到。
// 算一下还有多久才到,把这个差值赋给 nextPollTimeoutMillis。
// 等下一轮循环进入 nativePollOnce 时,就会睡这么久。
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 场景 B:成功获取消息
mBlocked = false; // 标记当前不用阻塞
// 从单链表中移除该 msg
if (prevMsg != null) {
prevMsg.next = msg.next;
if (prevMsg.next == null) {
mLast = prevMsg;
}
} else {
mMessages = msg.next;
if (msg.next == null) {
mLast = null;
}
}
msg.next = null;
if (DEBUG) Log.v(TAG_L, "Returning message: " + msg);
msg.markInUse();
if (msg.isAsynchronous()) {
mAsyncMessageCount--;
}
decAndTraceMessageCount();
if (TRACE) {
Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
}
return msg; // 【唯一出口】:把消息 return 给 Looper 的 dispatchMessage 去执行!
}
} else {
// 场景 C:没消息了(队列是空的,或者全被屏障挡住了且没异步消息)
nextPollTimeoutMillis = -1; // 标记 -1,下一轮循环直接死睡
}
// 既然所有待处理的消息都已处理完毕,现在就处理退出消息。
if (mQuitting) {
dispose();
return null;
}
// 如果是首次空闲,则获取要运行的空闲处理器数量。
// 空闲处理器仅在队列为空或队列中的第一条消息(可能是屏障)要在未来处理时才会运行。
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 没有空闲的处理程序可运行,继续循环等待。
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
} // 退出 synchronized 锁,不阻塞发消息的线程
// 运行空闲处理程序。
// 我们只会在第一次迭代期间进入这个代码块。
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // 释放对处理程序的引用
boolean keep = false;
try {
keep = idler.queueIdle(); // 执行回调
} catch (Throwable t) {
Log.wtf(TAG_L, "IdleHandler threw exception", t);
}
// 如果 queueIdle 返回 false,系统就把它从名单里删了,下次闲着也不调用。
// 如果返回 true,下次闲着还会再调用。
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 将空闲处理程序计数重置为0,保证一次 next() 循环最多只跑一轮 IdleHandler
pendingIdleHandlerCount = 0;
// 在调用空闲处理程序时,可能已传递了新消息
// 因此返回并再次查找待处理消息,无需等待
nextPollTimeoutMillis = 0;
}
}
nextLegacy 采用单链表 + 全局悲观锁的架构,其核心是一个包含四大阶段的 for(;;) 自旋死循环:
- 底层休眠阶段 (阻塞)
- 检查
nextPollTimeoutMillis。若需休眠,首先调用Binder.flushPendingCommands()清理跨进程挂起命令。 - 调用
nativePollOnce(ptr, nextPollTimeoutMillis),利用 Linuxepoll机制挂起当前线程,释放 CPU 资源。
- 检查
- 加锁检索阶段 (O(N) 复杂度)
- 线程唤醒后,进入
synchronized(this)全局大锁区块。 - 读取队头:获取链表头节点
mMessages。 - 屏障拦截:若队头是同步屏障(
target == null),则触发线性遍历(do-while),向后寻找第一个异步消息(isAsynchronous() == true)。
- 线程唤醒后,进入
- 出队与延时计算阶段
- 未到执行时间:计算消息
when与当前时间的差值,赋值给nextPollTimeoutMillis,准备进入下一轮循环休眠。 - 达到执行时间:更新链表指针(
prevMsg.next = msg.next),维护尾部追踪(mLast),将消息标记为IN_USE并return,退出循环。 - 无消息:将休眠时间设为
-1(无限期挂起)。
- 未到执行时间:计算消息
- IdleHandler 调度阶段 (锁释放)
- 当队列为空或队头消息未到执行时间时,判断是否需要执行空闲任务。
- 将系统挂起的
IdleHandler收集到数组中,退出synchronized锁。 - 在无锁状态下遍历执行
idler.queueIdle(),避免主线程执行空闲任务时阻塞子线程发送新消息。执行完毕后,强制将下一次休眠时间重置为 0,重新检查队列。
nextConcurrent()
private Message nextConcurrent() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
mNextPollTimeoutMillis = 0;
int pendingIdleHandlerCount = -1; // -1 only during first iteration
while (true) {
// 如果有延迟任务,Binder 刷新挂起指令(性能优化)
if (mNextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
mMessageDirectlyQueued = false;
// 进入底层epoll阻塞
nativePollOnce(ptr, mNextPollTimeoutMillis);
// 核心获取逻辑:调用 nextMessage()
Message msg = nextMessage(false, false);
if (msg != null) {
msg.markInUse();
decAndTraceMessageCount();
return msg; // 成功获取消息,返回给 Looper.loop()
}
// 老版本是 if (mQuitting) (必须在锁里读)
// 新版本改用了 Volatile 变量,无锁保证内存可见性
if ((boolean) sQuitting.getVolatile(this)) {
return null;
}
// 逻辑与Legacy相似,但在并发环境下通过
// synchronized (mIdleHandlersLock) 保证了对监听器列表访问的线程安全。
synchronized (mIdleHandlersLock) {
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& isIdle()) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 运行 IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG_C, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (mIdleHandlersLock) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
mNextPollTimeoutMillis = 0;
}
}
// nextMessage部分源码
private Message nextMessage(boolean peek, boolean returnEarliest) {
// 方法主体为一个自旋循环
while (true) {
// ...
/**
* 清空并转移堆栈
* 在并发模式下,外部线程投递的消息首先会进入一个无锁堆栈(sState)
* nextMessage 首先通过原子操作 swap 将堆栈清空,并把当前线程状态标记为 ACTIVE(活跃)。
* 调用 drainStack 将刚拿到的这一批消息,按照它们的时间戳(when)
* 分别插入到两个内部的优先级队列中:mPriorityQueue(普通消息)和 mAsyncPriorityQueue(异步消息)。
*/
StackNode oldTop;
oldTop = swapAndSetStackStateActive();
drainStack(oldTop);
// ...
/**
* 双优先队列检索
* 优先级判定逻辑:
* 1.存在同步屏障(Barrier):
* 如果发现普通队列队头是屏障(msgNode.isBarrier(),即 target == null)。
* 忽略普通消息:此时它完全不考虑 msgNode
* 只取异步:如果异步队列里有消息且时间已到(now >= asyncMsgNode.getWhen()),
* 则将其设为 found(即待返回的消息)。
* 2.不存在同步屏障:
* 调用 pickEarliestNode(msgNode, asyncMsgNode),
* 全局比对:从两个队列的队头中,选出 when 最小(即最紧急)的那一个
* 判定时机:如果选出的消息时间到了,标记为 found;
* 如果时间没到,将其标记为 next(用于计算下次休眠时间)
*/
Iterator<MessageNode> queueIter = mPriorityQueue.iterator();
MessageNode msgNode = iterateNext(queueIter); // 普通队列对头
Iterator<MessageNode> asyncQueueIter = mAsyncPriorityQueue.iterator();
MessageNode asyncMsgNode = iterateNext(asyncQueueIter); // 异步队列对头
MessageNode found = null;
MessageNode next = null;
long now = SystemClock.uptimeMillis();
if (msgNode != null && msgNode.isBarrier()) {
// 存在同步屏障,忽略普通消息,强制评估异步消息
if (asyncMsgNode != null && (returnEarliest || now >= asyncMsgNode.getWhen())) {
found = asyncMsgNode; // 异步消息达到执行时间
} else {
next = asyncMsgNode; // 异步消息未到时间,作为下一次唤醒的参照
}
} else {
// 无同步屏障,按绝对时间选取最早的节点
MessageNode earliest;
earliest = pickEarliestNode(msgNode, asyncMsgNode);
if (earliest != null) {
if (returnEarliest || now >= earliest.getWhen()) {
found = earliest;
} else {
next = earliest;
}
}
}
// ...
/**
* 若存在可执行的 found 节点,维持 ACTIVE 状态。
* 若当前无可执行消息,依据 next 节点计算出 mNextPollTimeoutMillis,
* 并预设下一步的栈状态为 Parked(死睡)或 TimedPark(定时休眠)。
*/
StateNode nextOp = sStackStateActive;
if (found == null) {
if (next == null) {
// 无消息,准备无限期休眠
mNextPollTimeoutMillis = -1;
nextOp = sStackStateParked;
// ...
} else {
long nextMessageWhen = next.getWhen();
if (nextMessageWhen > now) {
mNextPollTimeoutMillis = (int) Math.min(nextMessageWhen - now,
Integer.MAX_VALUE);
} else {
mNextPollTimeoutMillis = 0;
}
mStackStateTimedPark.mWhenToWake = now + mNextPollTimeoutMillis;
nextOp = mStackStateTimedPark; // 准备定时休眠
// ...
}
}
/**
* 尝试将全局状态从 ACTIVE 变更为上面预设的 nextOp。
* 失败:说明在第 1 步到第 5 步执行期间,其他子线程又向无锁栈中压入了新消息,
* 导致状态被更改。此时触发 while(true) 外层循环重新执行数据转移。
* 成功:状态更新完毕。调用 removeFromPriorityQueue 将节点从本地私有队列中正式摘除,
* 并返回 Message 对象供底层的 dispatchMessage 执行。
*/
if (sState.compareAndSet(this, sStackStateActive, nextOp)) {
mMessageCounts.clearCounts();
if (found != null) {
if (!peek && !removeFromPriorityQueue(found)) {
// 防御性编程:防止消息被其他线程并发 remove 导致的不一致
continue;
}
// ...
return found.mMessage;
}
return null;
}
}
}
nextConcurrent 采用 Treiber Stack + 线程私有双队列 + CAS 状态机 的架构,彻底移除了 MessageQueue 级别的全局锁。
- 无锁休眠阶段
- 同样依赖
nativePollOnce进行底层休眠,但整个过程完全脱离synchronized(this)锁的控制。
- 同样依赖
- 批量数据转移阶段 (
drainStack)- 唤醒后,调用内部核心方法
nextMessage()。 - 利用原子操作(CAS),将全局共享的无锁栈(Treiber Stack)状态标记为
ACTIVE。 - 将子线程高频压入栈内的
MessageNode批量弹出(Drain),并依据消息的async属性,分别归类插入到主线程私有的两个优先级队列:mPriorityQueue(普通队列)和mAsyncPriorityQueue(异步队列)。
- 唤醒后,调用内部核心方法
- 双队列决议阶段 (O(1) 复杂度)
- 分别获取两个优先队列的头节点(最早的消息)。
- 屏障决议:若普通队列头节点为同步屏障,直接忽略普通队列,仅评估异步队列头节点。此设计消除了老版本中遍历单链表寻找异步消息的 O(N) 性能损耗。
- 常规决议:无屏障时,对比两个队列头节点的时间戳,选取最小者作为目标节点。
- CAS 状态机提交阶段
- 若找到可执行消息:通过
removeFromPriorityQueue将其从本地私有队列摘除并return。 - 若无立即可执行消息:计算出休眠时长
mNextPollTimeoutMillis,预设下一步状态为PARKED或TIMEDPARK。 - 通过
compareAndSet原子操作提交新状态。若遭遇并发冲突(有新消息刚好入栈),则重新自旋处理。
- 若找到可执行消息:通过
- 细粒度锁 IdleHandler 阶段
- 采用锁分离(Lock Striping)思想。
- 仅在操作
IdleHandler列表时,使用专门的极小范围锁synchronized (mIdleHandlersLock)。 - 拷贝列表后立即释放锁,在外部遍历执行
queueIdle(),实现极致的锁粒度控制。
空闲回调:IdleHandler
IdleHandler 是定义在 MessageQueue 内部的一个单方法接口。 它提供了一种在主线程(或任何拥有 Looper 的线程)处于空闲状态时,执行低优先级任务的回调机制。
// MessageQueue.java 内部接口
public static interface IdleHandler {
/**
* 当消息队列没有消息,或者队列中的消息尚未到执行时间,
* 线程即将进入阻塞(挂起)状态前,系统会回调此方法。
*
* @return 返回 true 表示该 IdleHandler 继续保留,下次空闲时再次执行;
* 返回 false 表示执行完毕后将其从注册列表中移除。
*/
boolean queueIdle();
}
使用场景:
- App 冷启动优化 (延迟初始化):在
Application.onCreate或Activity.onCreate中执行过多的第三方 SDK 初始化或数据预加载,会严重阻塞首帧 UI 的渲染时间。可以将非首屏强依赖的任务放入IdleHandler。这能确保主线程优先完成所有的测量、布局和绘制指令。当首帧成功渲染,主线程真正闲置准备休眠的那一刻,再触发这些底层的初始化逻辑。
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// 只有当主线程处理完 UI 绘制,队列为空时才执行
initCrashReporter();
initPushService();
return false; // 执行一次即销毁
}
});
- 预加载与预实例化:对于极其复杂的弹窗或后续 Activity,可以在主线程空闲时,提前利用
LayoutInflater将 XML 转化为 View 对象保存在内存中,或者提前实例化复杂的跨进程组件(如 WebView)。当用户真实触发操作时,直接从内存读取,大幅缩短响应延迟。 - 主动触发 GC:在内存敏感型应用中,如果在执行完内存密集型任务后立刻调用
System.gc(),可能会引发主线程卡顿。将 GC 操作放入IdleHandler,可以在保证 UI 流畅度的前提下,寻找 CPU 的闲置窗口回收内存。
注意:queueIdle()本质上依然是在主线程执行的代码,只是利用了主线程的“空闲时间”,但如果在queueId1e()里面的逻辑太耗时(比如疯狂读写本地大文件耗时10秒),一旦在这10秒内用户点击了屏幕,因为主线程被ldleHandler占用了,依然会抛出ANR。所以它只适合做轻量级的延迟任务。
Looper(消息循环器,“发动机”)
Looper 的引入
在标准的 Java 线程模型中,Thread 的 run() 方法执行完毕后,线程的生命周期结束并被系统回收。而Android 的主线程(UI 线程)需要持续存活,不断响应用户的触摸事件、系统的生命周期回调以及跨线程的异步结果。
解决方案:Looper(消息循环器)通过引入死循环(Event Loop)机制,将普通线程转化为事件驱动型线程。只要循环不退出,线程就不会终止,从而具备了持续处理异步事件的能力。
Looper 是连接 Thread 和 MessageQueue 的桥梁。其核心架构关系如下:
- 一个
Thread只能拥有一个Looper。 - 一个
Looper只能拥有一个MessageQueue。 - 一个
Looper可以绑定多个Handler。
核心工作流程
- 初始化(
Looper.prepare()):创建 Looper 实例,创建关联的 MessageQueue,将 Looper 存入当前线程的 ThreadLocal(确保线程唯一)。 - 启动循环(
Looper.loop()):获取当前线程的 Looper 和对应的 MessageQueue,进入无限循环(for(;;)),调用queue.next()阻塞取消息,将消息分发给目标 Handler(msg.target.dispatchMessage(msg)),消息处理完成后,调用 msg.recycleUnchecked() 回收复用。 - 退出循环(
quit()/quitSafely()):quit()立即退出,移除所有未处理的消息(可能丢失);quitSafely()安全退出,处理完所有延迟消息后退出。主线程 Looper 禁止手动调用quit(),会导致应用崩溃。
源码分析
实例创建与 ThreadLocal 隔离:prepare()
// Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true); // 默认允许退出
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
// 防重入校验。一个线程调用两次 prepare 会直接抛出异常。
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); // 实例化私有队列
mThread = Thread.currentThread(); // 绑定当前线程
}
循环引擎:loop()与loopOnce()
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// ...
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return; // loopOnce 返回 false,意味着队列返回 null,循环终止
}
}
}
private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // 阻塞式获取消息
/**
* 调用 MessageQueue.next(),由于底层采用 epoll 机制,
* 若队列为空或未到执行时间,线程会在此处挂起休眠。
* 当开发者调用 Looper.quit() 或 quitSafely() 时,next() 方法内部会感知并返回 null。
* 此时 loopOnce 返回 false,彻底打破外层死循环。
*/
if (msg == null) {
return false; // 队列正在退出 (quit)
}
// 性能优化的关键 API
// 这是 Android APM(应用性能监控)体系中最核心的卡顿监控 Hook 点
// 系统允许开发者通过 Looper.getMainLooper().setMessageLogging(Printer)
// 注入自定义的日志打印器。获取到此处的系统时间戳,即可标记该 Message 执行的起点。
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
}
// ...
try {
// 定向分发至 Handler 的 handleMessage 中执行
// msg.target 即为发送该消息的 Handler 实例。Looper 并不关心具体的业务逻辑,
// 它只负责精确地将控制权移交给目标的 dispatchMessage() 方法。
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
throw exception;
} finally {
// ...
}
// ...
// 通过记录此处的时间戳并与前置时间戳相减,即可得出当前 Message 执行的精确耗时。
// 若耗时超过阈值(如主线程超过 16.6ms),则判定为发生卡顿/掉帧。
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// 享元模式
// 消息执行完毕后,调用 recycleUnchecked() 清空内部的业务数据(防止内存泄漏),
// 并将其头插法归还至全局最大容量为 50 的 Message 单链表缓存池中,
// 供后续 Message.obtain() 极速复用。
msg.recycleUnchecked(); // 消息放入对象池复用
return true; // 返回 true,维持下一轮死循环
}
循环终止机制:quit() 与 quitSafely()
调用 Looper.quit() 实际上是调用底层的 mQueue.quit(boolean safe)。
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
void quit(boolean safe) {
/**
* 主线程保护
* 在 Looper.prepareMainLooper() 初始化主线程时,
* 系统会强制传入 quitAllowed = false。
* Android 主线程的生命周期与应用进程(App Process)绑定。
* 若允许主线程的 Looper 退出,将导致主线程执行完 loop() 后直接终止,
* 使得整个 App 失去响应 UI 事件的能力(应用实际上处于僵尸状态)。
* 因此,系统在底层做出了硬性拦截。
*/
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
if (mUseConcurrent) {
// 现代并发模型分支 (Concurrent 架构)
synchronized (mIdleHandlersLock) {
if (sQuitting.compareAndSet(this, false, true)) {
if (safe) {
removeAllFutureMessages();
} else {
removeAllMessages();
}
nativeWake(mPtr);
}
}
} else {
// 传统模型分支 (Legacy 架构)
synchronized (this) {
if (mQuitting) {
return; // 防重入检查
}
mQuitting = true; // 标记退出状态
if (safe) {
removeAllFutureMessagesLocked(); // 安全退出
} else {
removeAllMessagesLocked(); // 强制退出
}
// 如果调用 quit() 时,主线程因为没有消息正处于 nativePollOnce 的休眠阻塞状态,
// 直接修改 mQuitting 或 sQuitting 的值是无法让主线程知道的。
// 必须调用 JNI 方法 nativeWake,向底层的 epoll 实例(通常是一个 eventfd)写入数据。
// 这会立刻打破主线程的休眠状态。
// 主线程被唤醒后,会在 next() 方法中执行下一次检索。
// 此时它会读取到 mQuitting == true,随即 next() 方法返回 null。
// 外层的 Looper.loop() 接收到 null 后,跳出 for(;;) 循环,线程正式终止。
nativeWake(mPtr); // 唤醒底层
}
}
}
清理策略 (****safe 参数解析):
safe == true(quitSafely):调用removeAllFutureMessagesLocked()。遍历单链表,保留所有when <= now(已到达执行时间但因主线程繁忙还在排队)的消息,仅清空when > now(未来才需要执行的延迟消息)。safe == false(quit):调用removeAllMessagesLocked()。采取暴力清空策略,直接断开单链表的所有引用,无论消息是否已到期,全部回收到对象池。
Handler(消息发送者&处理者)
在 Android 的事件驱动与多线程模型中,MessageQueue 负责存储,Looper 负责轮询,但开发者并不直接操作这两个底层组件。Handler 作为整个消息机制的业务代理人,同时扮演了“生产者”(发送消息入队)和“消费者代理”(接收 Looper 派发并执行)的双重角色,彻底屏蔽了跨线程通信的底层复杂性。
源码分析
Handler 的源码主要分为三个核心部分:初始化、发送路由、派发路由。
初始化机制(构造函数)
// 现代 Android 开发标准写法(Android 11 废弃了无参构造)
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue; // 直接获取目标 Looper 的队列
mCallback = callback;
mAsynchronous = async; // 决定通过此 Handler 发送的消息是否默认为异步消息
}
注意:Handler 的执行线程完全取决于传入的 Looper,与其在哪一个线程被实例化毫无关系。
生产者逻辑 (发送消息)
无论是调用 post(Runnable) 还是 sendMessage(Message),最终都会汇聚于 enqueueMessage 方法。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
// 将当前 Handler 实例的引用赋值给 Message 的 target 属性。
// 这是 Looper 取出消息后,能够精准回调对应 Handler 的物理基础。
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true); // 如果 Handler 开启了 async,全局标记异步消息
}
return queue.enqueueMessage(msg, uptimeMillis);
}
消费者代理逻辑 (消息派发)
当 Looper.loop() 取出消息并调用 msg.target.dispatchMessage(msg) 时,代码回到 Handler 内部执行。此方法定义了严格的回调执行优先级。
public void dispatchMessage(@NonNull Message msg) {
// 优先级 1:Message 自带的 Callback (即 handler.post(Runnable) 传入的 Runnable)
if (msg.callback != null) {
handleCallback(msg);
} else {
// 优先级 2:Handler 初始化时传入的全局 Callback 接口
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return; // 如果 Callback 返回 true,则拦截消息,不再向下传递
}
}
// 优先级 3:Handler 子类重写的 handleMessage 方法
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
ThreadLocal
什么是线程隔离?
在并发编程中,处理多线程共享变量通常采用悲观锁(Synchronized/ReentrantLock)或乐观锁(CAS)。加锁会导致线程排队、上下文切换,极大地消耗系统性能。且有些场景下(如 Looper),我们并不需要多线程“共享”同一个变量,而是希望每个线程拥有该变量的独立副本。
线程隔离是一种并发编程的设计模式。它通过将数据状态的作用域限制在单个线程内部,使每个线程拥有该变量的独立副本,从而彻底消除多线程环境下的数据共享与竞态条件(Race Condition),实现无锁并发(Lock-free Concurrency)。在 Java 和 Android 底层,实现线程隔离最核心的工具就是 ThreadLocal
| 传统同步模式 (Synchronized/Lock) | 线程隔离模式 (ThreadLocal) | |
|---|---|---|
| 核心策略 | 串行化访问共享资源 | 空间换时间,资源副本化 |
| 并发控制 | 悲观锁/乐观锁阻塞排队 | 无锁设计,完全并行 |
| 性能损耗 | 线程挂起与唤醒的上下文切换开销 | 堆内存对象的额外分配(内存开销增大) |
| 适用场景 | 必须保证全局状态一致性的场景 (如库存扣减) | 线程内部状态流转、隐式传参 (如 Looper、数据库连接) |
ThreadLocal 如何实现线程隔离?
ThreadLocal 并不是一个线程,是一个线程局部变量的存储容器,能够保证变量在每个线程内部是完全独立的。
注意:ThreadLocal 本身不存储任何业务数据,它仅仅是一个全局哈希键(Hash Key)和访问代理。数据真实存储在每个 Thread 实例的内部。
每个线程维护着一个私有的哈希表(ThreadLocalMap)。当我们调用 ThreadLocal.set() 时,实际上是把当前 ThreadLocal 实例作为 Key,传入的值作为 Value,存进了当前执行代码的那个线程的私有哈希表中。
源码分析
数据的写入:set(T value)
public void set(T value) {
// 获取当前正在执行代码的线程
Thread t = Thread.currentThread();
// 提取该线程内部的 ThreadLocalMap 成员变量
ThreadLocalMap map = getMap(t);
if (map != null) {
// 如果表存在,以上下文的 ThreadLocal 实例 (this) 为 Key 存入数据
map.set(this, value);
} else {
// 如果不存在,为该线程懒加载初始化一个 Map
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
数据的获取:T get()
从源码可以看到,ThreadLocal 并不直接存储数据,数据是存在 Thread 对象里的。
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();
}
ThreadLocalMap 的底层设计
哈希算法与冲突解决
Java 中的 HashMap 遇到哈希冲突时,采用的是“数组 + 链表/红黑树”的拉链法。 而 ThreadLocalMap 遇到冲突时,采用的是开放寻址法(线性探测法,Linear Probing)
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// threadLocalHashCode 是 ThreadLocal 特有的哈希算法
// (斐波那契散列法,能极大程度打散数据)
int i = key.threadLocalHashCode & (len-1);
// 线性探测
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 场景A:找到相同的 Key,更新值
if (e.refersTo(key)) { // Android 特有优化:比 get() == key 更快且更安全
e.value = value;
return;
}
// 场景B:遇到脏数据(Key 已经被GC回收了),执行替换并清理脏数据
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i);
return;
}
}
// 场景C:找到了空槽位,直接插入新 Entry
tab[i] = new Entry(key, value);
int sz = ++size;
// 插入后,触发一次试探性清理,启发式(对数级别)的局部清理
// 如果没清理掉脏数据且超过阈值(通常是长度的 2/3),则扩容(翻倍)
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
扩容机制
private void rehash() {
// 先进行一次全量级别 O(N) 的清理
// 遍历整个数组,把所有 Key 为 null 的废弃节点彻底清理掉。
expungeStaleEntries();
// 防抖动设计
// 清理后,只有当 size >= threshold * 0.75 时才扩容。
// 这是为了防止因大量“陈旧项”被清理后,
// 数组其实还很空,却盲目扩容导致空间浪费。
if (size >= threshold - threshold / 4)
resize();
}
/**
* 空间翻倍
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2; // 容量翻倍
Entry[] newTab = new Entry[newLen]; // 创建新容器
int count = 0;
// 数据迁移与哈希重排
for (Entry e : oldTab) {
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
// 极端情况防御:虽然刚调用过全量清理,但在极短并发窗口内,
// Key 又被 GC 了,这里顺手把 Value 置空帮助垃圾回收。
e.value = null; // Help the GC
} else {
// 计算在新数组中的全新索引 (Hash 码 & 掩码)
int h = k.threadLocalHashCode & (newLen - 1);
// 处理哈希冲突:如果新位置已经被别人占了,
// 继续采用【线性探测法】往后找空位
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
【与 HashMap 的核心差异点】
HashMap在扩容时,采用的是高低位拆分(利用hash & oldCap)将链表或红黑树的数据拆成两部分。ThreadLocalMap则由于底层是线性探测,链表结构不存在。它必须逐个重新计算 Hash 并重新进行线性探测碰撞。因为数组长度变了,原来因为 Hash 冲突而堆积在一起的元素,在新的长数组中极大概率会被打散,从而大幅降低检索的时间复杂度。
内存泄漏分析
ThreadLocal 是 Java 内存泄漏的高发区,其根本原因在于其内部数据结构 Entry 的引用类型设计。
弱引用设计
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
// Key 为弱引用,Value 为强引用
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
弱引用的意义:若 Key 为强引用,只要线程存活,ThreadLocal 实例将永远无法被垃圾回收。设计为弱引用后,当外部业务代码解除对 ThreadLocal 的强引用时,下次 GC 即可回收该 Key,避免 ThreadLocal 对象本身的泄漏。
内存泄露的本质
被动清理机制:ThreadLocalMap 的清理是被动的,只有在调用 get()、set()、remove() 方法时,会自动触发 expungeStaleEntry() 方法,探测并清除数组中 Key 为 null 的脏数据。
当 Key(ThreadLocal 实例)被 GC 回收后,ThreadLocalMap 中将出现 Key 为 null 的废弃节点(Stale Entry)。若此时当前线程(尤其是线程池中的核心线程)长期存活,将形成以下无法被 GC 打断的强引用链:
Thread 实例 ➔ ThreadLocalMap ➔ Entry[] ➔ Entry 实例 ➔ Value 业务对象
结果:Value 对象永远无法被回收,导致内存泄漏。
规范:在使用完 ThreadLocal 后,必须显式调用 remove() 方法。
主线程的 Looper 启动流程
启动入口:ActivityThread.main()
当 App 进程被 Zygote 进程 fork 出来后,会首先执行 ActivityThread.main() 方法,这是 Android 应用进程的入口点。
// 简化后的 ActivityThread.main 逻辑
public static void main(String[] args) {
// 初始化主线程 Looper
Looper.prepareMainLooper();
// 创建 ActivityThread 对象并与系统服务绑定
ActivityThread thread = new ActivityThread();
// 绑定AMS,注册Binder通信,接收系统四大组件调度指令
thread.attach(false, startSeq);
// 获取主线程 Handler(内部持有刚刚创建的 Looper)
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 启动Looper死循环,主线程进入消息驱动
Looper.loop();
// 如果 loop 退出,抛出异常(主线程不允许退出)
throw new RuntimeException("Main thread loop unexpectedly exited");
}
主线程专属初始化:Looper.prepareMainLooper()
普通的子线程初始化 Looper 使用的是 Looper.prepare(),而主线程使用的是专属 API prepareMainLooper()。
public static void prepareMainLooper() {
// 调用普通的 prepare(false),false 表示不允许退出(quitAllowed = false)
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
// 将当前线程生成的 Looper 赋值给全局静态变量 sMainLooper
sMainLooper = myLooper();
}
}
- 不可退出性 (
quitAllowed = false):prepare(false)最终在实例化MessageQueue时,将其内部变量mQuitAllowed置为false。这意味着主线程的消息队列在整个应用进程生命周期内绝对不允许被销毁。如果开发者在代码中强行调用Looper.getMainLooper().quit(),底层机制会直接抛出IllegalStateException异常。 - 全局单例暴露 (
sMainLooper):初始化完成后,该Looper实例被存储在全局静态变量sMainLooper中。这使得应用内任何子线程都可以通过Looper.getMainLooper()安全、直接地获取到主线程的 Looper 引用,从而实现子线程向主线程跨线程投递消息(UI 更新)。
系统消息接收器实例化:sMainThreadHandler
主线程配置好 Looper 后,需要一个对应的 Handler 来处理系统层面的消息。
final H mH = new H();
// ActivityThread.java 内部类 H
class H extends Handler {
public static final int BIND_APPLICATION = 110;
public static final int EXECUTE_TRANSACTION = 159;
// ...
public void handleMessage(Message msg) {
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
break;
// ...
}
}
}
ActivityThread内部维护了一个名为H的类(继承自Handler)。- 在
ActivityThread实例化时,会同步实例化H。因为此时主线程的 Looper 已经准备完毕,H实例化时会自动绑定到主线程的MessageQueue上。 - 作用:应用生命周期(如 Application 启动、Activity 的 onCreate/onResume)全部是由系统服务(AMS)发送跨进程指令,应用进程接收后,封装为
Message投递到主线程的MessageQueue中,最终交由H类进行具体的生命周期方法反射调用。
启动循环:Looper.loop()
这是之前分析 Looper 时提到过的循环引擎。执行到该语句时,主线程正式进入事件驱动模式。
- 该方法内部开启无限循环,反复调用
MessageQueue.next()。 - 若队列无消息,主线程利用底层 Linux
epoll机制挂起休眠,释放 CPU 时间片。 - 若队列有消息,唤醒线程,取出消息交由对应的
Handler(msg.target) 执行,执行完毕后回收Message对象。
Handler 内存泄露分析
为什么会内存泄漏?
在 Java 语言规范中,非静态内部类和匿名内部类在编译后,会隐式地持有一个外部类的强引用。
比如在 Activity 中这么写:
public class MainActivity extends AppCompatActivity {
// 隐式持有 MainActivity.this 的强引用
private Handler mHandler = new Handler(Looper.getMainLopper()) {
@Override
public void handleMessage(Message msg) {
// 这里可以直接访问 MainActivity 的成员变量,底层就是依赖这个隐式引用
textView.setText("Done");
}
};
// 或者使用匿名内部类的 Runnable
private void loadData() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() { ... } // 同样隐式持有 MainActivity.this
}, 10000); // 延迟 10 秒
}
}
当调用 sendMessageDelayed 或 postDelayed 发送一个延迟消息时,该 Message 对象会被插入到主线程的 MessageQueue 中排队等待。
根据前面梳理的 Handler 源码:
sendMessage底层会将Message.target强引用赋值为当前的Handler实例。
场景:如果此时用户按下了返回键,MainActivity 执行了 onDestroy() 进入销毁状态,但 MessageQueue 中那个延迟 10 秒的 Message 还未被 Looper 取出。
当 JVM 触发垃圾回收(GC)时,GC 算法会从 GC Roots 开始向下进行可达性分析。只要对象能通过强引用链连接到 GC Root,该对象就绝对不会被回收。
此时,内存中存在一条完整的 强引用链:
结果:尽管 MainActivity 已经在屏幕上消失且走完了生命周期,但由于上述长达 8 个节点的强引用链依然连通,GC 无法回收 MainActivity 占据的内存(包含其内部所有的 View、Bitmap 等大对象)。应用发生明显的内存泄漏。
解决方案
方案一:静态内部类+弱引用
public class MainActivity extends AppCompatActivity {
// 实例化时显式传入当前 Activity
private final SafeHandler mHandler = new SafeHandler(this);
// 声明为 static:阻断编译器自动生成对 MainActivity.this 的强引用 (this$0)
private static class SafeHandler extends Handler {
// 声明 WeakReference:包装外部类的实例
private final WeakReference<MainActivity> mActivityRef;
public SafeHandler(MainActivity activity) {
super(Looper.getMainLooper());
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
// 提取弱引用对象
MainActivity activity = mActivityRef.get();
// 严格判空与状态检查:发生 GC 时 activity 将变为 null
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
// 安全执行 UI 操作
activity.updateUI();
}
}
}
}
原理:
- 静态内部类不持有外部类引用,斩断自带的
this$0 - 用
WeakReference弱引用持有 Activity,GC 回收时无视弱引用,直接回收 Activity - 消息依旧在队列、Handler 依旧活着,但是拽不住 Activity,消息本身还会留在队列占内存
方案二:Activity 销毁时清空队列所有消息
该方案的核心思想是物理截断 MessageQueue 到 Message 的强引用链。允许 Handler 持有 Activity 的强引用,但在 Activity 销毁的瞬间,强制清空排队中的消息。
public class MainActivity extends AppCompatActivity {
// 允许使用匿名内部类(持有隐式强引用)
private final Handler mHandler = new Handler(Looper.getMainLooper());
@Override
protected void onDestroy() {
super.onDestroy();
// 核心清理动作
if (mHandler != null) {
// 传入 null,代表移除当前 mHandler 发送的【所有】Message 和 Runnable
mHandler.removeCallbacksAndMessages(null);
}
}
}
原理:调用 removeCallbacksAndMessages(null) 后,底层会遍历主线程的 MessageQueue,找出所有 msg.target == mHandler 的节点,将其从单链表中解绑,并调用 msg.recycleUnchecked() 将数据清空并放回全局对象池。 由于 Message 被移出队列,主线程 Looper 的强引用链在此处断裂,Handler 和 Activity 瞬间降级为不可达对象(Unreachable),随后被 GC 回收。
主线程的Looper.loop是个死循环,会造成 ANR吗?
这是一道经典的“挖坑题”。
死循环和 ANR 毫无因果关系
ANR (Application Not Responding) 并非由于 CPU 资源被过度消耗或线程一直在运行导致。ANR 的本质是系统监控超时机制。
当系统的 InputDispatcher(输入调度器)或 ActivityManagerService(AMS)向应用进程派发事件后,会在系统侧启动一个定时器。如果应用进程未能在规定时间内处理完该事件并向系统发送 ACK(确认响应),系统就会判定应用无响应并弹出 ANR 对话框。
而主线程的 for(;;) 循环之所以没有卡死系统,是因为它并非忙等待 (Busy-Waiting),而是基于 Linux 内核的 epoll I/O 多路复用机制实现的阻塞等待。
- 无消息时: 当
MessageQueue为空,代码执行到nativePollOnce(ptr, -1)时,主线程会陷入内核态并被操作系统挂起。此时线程状态为Sleeping,彻底让出 CPU 时间片,CPU 占用率为 0%。 - 有消息时: 当其他线程或系统进程通过底层文件描述符(
eventfd)写入数据时,操作系统会产生硬件中断,唤醒主线程,使其恢复Runnable状态继续处理消息。
主线程的死循环处于“事件驱动”的休眠/唤醒交替状态,既不消耗过多的 CPU 资源,也不会主动触发系统超时。
补充
既然死循环不会导致 ANR,那 ANR 究竟是怎么产生的?
原因在于:单线程串行处理机制与耗时操作的冲突。
主线程的 MessageQueue 是一个单向链表,所有消息必须严格按顺序排队执行。 假设当前队列的执行序列如下: [消息 A: 数据库复杂查询 (耗时 8 秒)] -> [消息 B: 屏幕触摸事件 (系统要求 5 秒内响应)]
- 执行阻塞:Looper 取出消息 A,开始执行。主线程被消息 A 的方法体阻塞 8 秒。
- 系统计时:在消息 A 执行的第 1 秒,用户点击了屏幕。系统
InputDispatcher将触摸事件(消息 B)跨进程发送到应用的MessageQueue,并在系统服务侧开启一个 5 秒的倒计时(Watchdog)。 - 排队等待:由于消息 A 尚未执行完毕,
Looper无法执行下一次queue.next(),消息 B 只能在队列中被迫等待。 - 触发超时:系统侧的 5 秒倒计时结束,系统检查发现消息 B 仍未被应用进程处理完毕。
- 抛出 ANR:系统直接中断应用的正常逻辑,收集 Trace 信息,并在屏幕上弹出 ANR 警告。
为什么主线程不能退出 loop()?
标准的 Java 程序中,main() 方法中的指令全部执行完毕后,主线程即刻销毁,随之 JVM 进程终止。
Android 操作系统采用了强事件驱动(Event-Driven)架构。
- 应用的 UI 绘制(VSYNC 信号)
- 生命周期的扭转(
onCreate,onResume等) - 用户的交互响应(Touch, Key 事件) 以上所有行为,底层全部被封装为
Message,由系统跨进程投递到应用主线程的MessageQueue中。
如果 loop() 退出:ActivityThread.main() 方法将走到尽头,主线程的生命周期结束。应用进程将立即失去处理任何系统级指令和用户交互的能力,操作系统会判定该进程已死亡并回收其内存。因此,主线程的无限轮询是维持应用存活的绝对物理前提。
为什么严禁在主线程执行耗时操作?
除了会引发上述的 ANR 超时(Input 5秒,Broadcast 10秒,Service 20秒),更基础的性能灾难在于破坏屏幕刷新机制(掉帧)。
- VSYNC 刷新率:现代屏幕刷新率一般为 60Hz,即每 16.7ms 需要渲染一帧新画面。
- 绘制消息优先级:UI 的测量、布局、绘制也是由底层通过
Choreographer发送异步消息到主线程执行的。 - 掉帧现象:如果主线程中某个普通消息(如读取本地 JSON 文件)耗时了 50ms。那么在这 50ms 内,系统发出的 VSYNC 绘制消息将被迫排队,导致至少 3 帧无法渲染,用户肉眼就会察觉到明显的 UI 卡顿(Jank)。