引言:
Handler 机制是 Android 多线程通信的核心框架,涉及 Handler、Looper、MessageQueue、Message 四大核心类。以下是工作流程、源码解析、常见面试题的深度分析.
主线程默认已经创建了 Looper
。子线程需要通过 Looper.prepare()
创建 Looper
,并通过 Looper.loop()
启动消息循环。
Handler
会绑定到当前线程的 Looper
。如果当前线程没有 Looper
,会抛出异常。
通过 Handler.sendMessage()
或 Handler.post()
发送消息或任务。消息会被放入 Looper
的 MessageQueue
中。
Looper
不断从 MessageQueue
中取出消息,并调用 Handler
的 handleMessage()
方法处理消息。
1.3 关键流程代码
// 子线程中创建 Looper 和 Handler
new Thread(() -> {
Looper.prepare(); // 创建 Looper
Handler handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理消息
switch (msg.what) {
case 1:
System.out.println("Received message: " + msg.obj);
break;
}
}
};
Looper.loop(); // 启动消息循环
}).start();
// 主线程中发送消息
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> {
// 在主线程中执行任务
System.out.println("Running on main thread");
});
(1)Handler 通过sendX或者postX来发送一个消息,这里的post(Runnable r)方法会将Runnable包装成一个Message,从代码中可以看到将Runnable赋值给了Message.callback了。最终sendXX和postXX都会调用到sendMessageAtTime(),在这个方法中最终调用了enqueueMessage()方法,这里注意将this赋值给了Message.target,而此处this就是Handler。enqueueMessage方法最终调用了MessageQueue的enqueueMessage方法,将消息放入队列。代码如下:
作用:用于发送和处理消息(Message
)或任务(Runnable
)。
关键源码:
public class Handler {
final Looper mLooper;
final MessageQueue mQueue;
public Handler(Looper looper) {
mLooper = looper;
mQueue = looper.mQueue;
}
// 发送消息
public boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
// 提交任务
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
// 提交延时任务
public final boolean postDelayed(Runnable r, long delayMillis){
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
// 将Runnable赋值给message.callback
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
// sendMessage()、post()等调用此处方法
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// sendXX()、postXX()等最终调用此方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
// 最终调用到enqueueMessage(), 插入到消息队列
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // 设置消息的target为当前Handler
return queue.enqueueMessage(msg, uptimeMillis);
}
// 处理消息
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 优先处理Runnable(通过post(Runnable)发送的消息)
handleCallback(msg);
} else {
if (mCallback != null) {
// 处理Callback接口
if (mCallback.handleMessage(msg)) return;
}
handleMessage(msg); // 子类重写的方法
}
}
}
(2)MessageQueue MessageQueue是一个优先级队列,核心方法是enqueueMessage和next方法,也就是将插入队列,将消息取出队列的操作。 之所以说MessageQueue是一个优先级队列是因为enqueueMessage方法中会根据Message的执行时间来对消息插入,这样越晚执行的消息会被插入到队列的后边。而next方法是一个死循环,如果队列中有消息,则next方法会将Message移除队列并返回该Message,如果队列中没有消息该方法则会处于阻塞状态。
Looper
都有一个对应的 MessageQueue
。消息队列中的消息可以设置延迟时间(delay
),用于实现定时任务。nativePollOnce
和 nativeWake
实现阻塞唤醒机制(底层使用 Linux 的 epoll
)。public final class MessageQueue {
private Message mMessages; // 链表头节点
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.when = when;
Message p = mMessages;
// 插入到队列头部(或按时间顺序插入到合适位置)
if (p == null || when < p.when) {
msg.next = p;
mMessages = msg;
} else {
// 遍历链表找到插入位置
}
nativeWake(mPtr); // 唤醒阻塞的Looper
}
return true;
}
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
// 通过epoll机制阻塞,直到有消息或超时
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) {
// 未到执行时间,计算剩余时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 取出消息并返回
mMessages = msg.next;
msg.next = null;
return msg;
}
} else {
// 队列为空,无限阻塞
nextPollTimeoutMillis = -1;
}
}
}
}
}
(3)Looper Looper可以理解为一个消息泵,Looper的核心方法是loop。注意loop方法的第一行会首先通过myLooper来得到当前线程的Looper, 接着拿到Looper中的MessageQueue,然后开启一个死循环,它会不断的通过MessageQueue的next方法将消息取出来,并执行。
ThreadLocal
绑定到线程。public final class Looper {
static final ThreadLocal sThreadLocal = new ThreadLocal<>();
final MessageQueue mQueue;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
}
// 初始化当前线程的Looper
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper per thread.");
}
sThreadLocal.set(new Looper(true));
}
// 开启消息循环
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next();
if (msg == null) {
return; // 退出循环的唯一条件:MessageQueue被销毁
}
msg.target.dispatchMessage(msg); // 分发消息给Handler
msg.recycleUnchecked(); // 回收消息到对象池
}
}
}
可以看到在取出Message后则会调用Message.target调用dispatchMessage方法,这里target就是Handler,它是在Handler的enqueueMessage时赋值的。紧接着将Message进行了回收。
(4)Message
作用:消息的载体,存储需要传递的数据和目标 Handler。
特点:Message
是一个轻量级对象,通常通过 Handler
发送。可以通过 what
字段标识消息类型,通过 obj
字段传递数据。
核心设计:通过 sPool
实现消息对象池,复用消息对象,减少内存抖动。
关键源码:
public final class Message implements Parcelable {
public int what; // 可以通过 what 字段标识消息类型,
public int arg1, arg2; // 整型参数
public Object obj; // 任意对象 通过 obj 字段传递数据。
Handler target; // 目标 Handler
Message next; // 链表结构的下一个节点
// 通过obtain()复用消息对象,避免频繁创建对象
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
}
在子线程中执行耗时操作(如网络请求、文件读写),然后通过 Handler
将结果传递到主线程更新 UI。
使用 postDelayed()
或 sendMessageDelayed()
实现定时任务。
通过 Handler
将任务发送到指定线程中执行。
Looper.loop()
不会阻塞导致 ANR?答案:
Looper.loop()
内部通过 MessageQueue.next()
的 nativePollOnce()
方法阻塞。
当没有消息时,主线程会释放 CPU 资源进入休眠状态,通过 epoll
机制监听文件描述符(如输入事件、屏幕刷新信号等)。
答案:
内存泄漏场景:非静态内部类 Handler 隐式持有外部类(如 Activity)的引用。
解决方案:
使用静态内部类 + WeakReference:
private static class SafeHandler extends Handler {
private final WeakReference mActivityRef;
SafeHandler(Activity activity) {
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = mActivityRef.get();
if (activity == null) return;
// 处理消息
}
}
在 Activity 的 onDestroy()
中移除所有消息:
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
post(Runnable)
和 sendMessage()
有何区别?答案:
post(Runnable)
最终会将 Runnable 封装成 Message 的 callback
字段。
dispatchMessage()
会优先执行 msg.callback.run()
,而不是 handleMessage()
。
答案:
主线程的 Looper 在无消息时通过 nativePollOnce()
进入休眠,释放 CPU 资源。
当有输入事件或新消息时,通过 epoll
机制唤醒主线程。
这种设计类似于操作系统的事件驱动模型,高效且节能。
答案:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); // 初始化子线程的Looper
Handler handler = new Handler();
Looper.loop(); // 开启消息循环
}
}).start();
Handler的个数与所在线程无关,可以在线程中实例化任意多个Handler。一个线程中只有一个Looper。Looper的构造方法被声明为了private,我们无法通过new关键字来实例化Looper,唯一开放的可以实例化Looper的方法是prepare。prepare方法的源码如下:
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));
}
我们知道ThreadLocal是一个线程内部的数据存储类,当某个线程调用prepare方法的时候,会首先通过ThreadLocal检查这个线程是否已经创建了Looper,如果还没创建,则实例化Looper并将实例化后的Looper保存到ThreadLocal中,而如果ThreadLocal中已经保存了Looper,则会抛出一个RuntimeException的异常。那么意味着在一个线程中最多只能调用一次prepare方法,这样就保证了Looper的唯一性。
(1)假设现在有一个线程A,在A线程中通过Looper.prepare和Looper.loop来开启Looper,并且在A线程中实例化出来一个Handler。Looper.prepare()方法被调用时会为会初始化Looper并为ThreadLocal 设置Looper,此时ThreadLocal中就存储了A线程的Looper。另外MessageQueue也会在Looper中被初始化。
(2)接着当调用Loop.loop方法时,loop方法会通过myLooper得到A线程中的Looper,进而拿到Looper中的MessageQueue,接着开启死循环等待执行MessageQueue中的方法。
(3)此时,再开启一个线程B,并在B线程中通过Handler发送出一个Message,这个Message最终会通过sendMessageAtTime方法调用到MessageQueue的equeueMessage方法将消息插入到队列。
(4)由于Looper的loop是一个死循环,当MessageQueue中被插入消息的时候,loop方法就会取出MessageQueue中的消息,并执行callback。而此时,Looper是A线程的Looper,进而调用的Message或者Handler的Callback都是执行在A线成中的。以此达到了线程的切换。
答:因为应用在启动的过程中就已经初始化主线程Looper了。
每个java应用程序都是有一个main方法入口,Android是基于Java的程序也不例外。Android程序的入口在ActivityThread的main方法中:
public static void main(String[] args) {
...
// 初始化主线程Looper
Looper.prepareMainLooper();
...
// 新建一个ActivityThread对象
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
// 获取ActivityThread的Handler,也是他的内部类H
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
// 如果loop方法结束则抛出异常,程序结束
throw new RuntimeException("Main thread loop unexpectedly exited");
}
main方法中先初始化主线程Looper,新建ActivityThread对象,然后再启动Looper,这样主线程的Looper在程序启动的时候就跑起来了。我们不需要再去初始化主线程Looper。
答:循环加锁,配合阻塞唤醒机制。
我们可以发现MessageQueue其实是“生产者-消费者”模型,Handler不断地放入消息,Looper不断地取出,这就涉及到死锁问题。如果Looper拿到锁,但是队列中没有消息,就会一直等待,而Handler需要把消息放进去,锁却被Looper拿着无法入队,这就造成了死锁。Handler机制的解决方法是循环加锁。在MessageQueue的next方法中:
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
}
}
}
我们可以看到他的等待是在锁外的,当队列中没有消息的时候,他会先释放锁,再进行等待,直到被唤醒。这样就不会造成死锁问题了。
那在入队的时候会不会因为队列已经满了然后一边在等待消息处理一边拿着锁呢?这一点不同的是MessageQueue的消息没有上限,或者说他的上限就是JVM给程序分配的内存,如果超出内存会抛出异常,但一般情况下是不会的。
答: Handler的阻塞唤醒机制是基于Linux的阻塞唤醒机制。
这个机制也是类似于handler机制的模式。在本地创建一个文件描述符,然后需要等待的一方则监听这个文件描述符,唤醒的一方只需要修改这个文件,那么等待的一方就会收到文件从而打破唤醒。和Looper监听MessageQueue,Handler添加message是比较类似的。
答:可以 / 一种使得异步消息可以被更快处理的机制
如果向主线程发送了一个UI更新的操作Message,而此时消息队列中的消息非常多,那么这个Message的处理就会变得缓慢,造成界面卡顿。所以通过同步屏障,可以使得UI绘制的Message更快被执行。
什么是同步屏障?这个“屏障”其实是一个Message,插入在MessageQueue的链表头,且其target==null。Message入队的时候不是判断了target不能为null吗?不不不,添加同步屏障是另一个方法:
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
// 把当前需要执行的Message全部执行
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 插入同步屏障
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到同步屏障就是一个特殊的target,哪里特殊呢?target==null,我们可以看到他并没有给target属性赋值。那这个target有什么用呢?看next方法:
Message next() {
...
// 阻塞时间
int nextPollTimeoutMillis = 0;
for (;;) {
...
// 阻塞对应时间
nativePollOnce(ptr, nextPollTimeoutMillis);
// 对MessageQueue进行加锁,保证线程安全
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
/**
* 1
*/
if (msg != null && msg.target == null) {
// 同步屏障,找到下一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一个消息还没开始,等待两者的时间差
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获得消息且现在要执行,标记MessageQueue为非阻塞
mBlocked = false;
/**
* 2
*/
// 一般只有异步消息才会从中间拿走消息,同步消息都是从链表头获取
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 没有消息,进入阻塞状态
nextPollTimeoutMillis = -1;
}
// 当调用Looper.quitSafely()时候执行完所有的消息后就会退出
if (mQuitting) {
dispose();
return null;
}
...
}
...
}
}
这个方法我在前面讲过,我们重点看一下关于同步屏障的部分,看注释1的地方的代码:
if (msg != null && msg.target == null) {
// 同步屏障,找到下一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
如果遇到同步屏障,那么会循环遍历整个链表找到标记为异步消息的Message,即isAsynchronous返回true,其他的消息会直接忽视,那么这样异步消息,就会提前被执行了。注释2的代码注意一下就可以了。
注意,同步屏障不会自动移除,使用完成之后需要手动进行移除,不然会造成同步消息无法被处理。从源码中可以看到如果不移除同步屏障,那么他会一直在那里,这样同步消息就永远无法被执行了。
有了同步屏障,那么唤醒的判断条件就必须再加一个:MessageQueue中有同步屏障且处于阻塞中,此时插入在所有异步消息前插入新的异步消息。这个也很好理解,跟同步消息是一样的。如果把所有的同步消息先忽视,就是插入新的链表头且队列处于阻塞状态,这个时候就需要被唤醒了。看一下源码:
boolean enqueueMessage(Message msg, long when) {
...
// 对MessageQueue进行加锁
synchronized (this) {
...
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
/**
* 1
*/
// 当线程被阻塞,且目前有同步屏障,且入队的消息是异步消息
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
/**
* 2
*/
// 如果找到一个异步消息,说明前面有延迟的异步消息需要被处理,不需要被唤醒
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
// 如果需要则唤醒队列
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
同样,这个方法我之前讲过,把无关同步屏障的代码忽视,看到注释1处的代码。如果插入的消息是异步消息,且有同步屏障,同时MessageQueue正处于阻塞状态,那么就需要唤醒。而如果这个异步消息的插入位置不是所有异步消息之前,那么不需要唤醒,如注释2。
那我们如何发送一个异步类型的消息呢?有两种办法:
Handler有一系列带Boolean类型的参数的构造器,这个参数就是决定是否是异步Handler:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
// 这里赋值
mAsynchronous = async;
}
但是异步类型的Handler构造器是标记为hide,我们无法使用,所以我们使用异步消息只有通过给Message设置异步标志:
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
其实同步屏障对于我们的日常使用的话其实是没有多大用处。因为设置同步屏障和创建异步Handler的方法都是标志为hide,说明谷歌不想要我们去使用他。所以这里同步屏障也作为一个了解,可以更加全面地理解源码中的内容。
核心流程:Handler.sendMessage()
→ MessageQueue.enqueueMessage()
→ Looper.loop()
→ MessageQueue.next()
→ Handler.dispatchMessage()
关键点:
消息复用(Message.obtain()
)。
阻塞唤醒机制(epoll
)。
线程绑定(ThreadLocal
)。