前言
Handler 消息机制是 Android 中用于处理线程间通信的重要机制。在 Android 开发中,UI 线程(主线程)是一个非常重要的线程,用来处理用户交互、界面更新等操作。但是,如果在 UI 线程中执行耗时操作,就会导致界面卡顿甚至 ANR(Application Not Responding)的情况发生。
为了解决这个问题,Android 提供了 Handler 消息机制,通过它可以实现不同线程之间的通信。Handler 主要用于向特定的线程发送消息,然后在该线程中处理这些消息。通过 Handler,我们可以将耗时操作放到子线程中执行,然后利用 Handler 将结果发送回 UI 线程进行界面更新。
在使用 Handler 消息机制时,通常会涉及到以下几个核心概念:
- Handler:用于发送和处理消息的类,可以与 Looper 和 MessageQueue 一起工作来实现消息的处理。
- Looper:用于不断地从 MessageQueue 中取出消息,并将其分发给对应的 Handler 进行处理。
- MessageQueue:用于存储消息的队列,Handler 发送的消息会被添加到 MessageQueue 中等待处理。
- Message:消息的载体,包含了要传递的数据及相关信息。
通过 Handler 消息机制,我们可以实现线程间的通信,方便地在不同线程之间进行数据传递和操作处理,确保 UI 界面的流畅性和响应性。在 Android 开发中,Handler 消息机制被广泛应用于异步任务处理、定时任务调度等场景。
下图是网上流传较为形象的 Handler 消息机制的流程图解: 下面我们将分析上图流程,并通过源码来解析 Handler 消息机制的原理。
1、Handler 处理消息的流程解析
1)发送消息
要处理消息,首先要发送,Handler 类提供了很多发送消息的方法提供给用户根据需求调用,但不论哪个方法,最终都会调用到 Handler 的私有方法:Handler.enqueueMessage()
-> MessageQueue.enqueueMessage()
,如下图:
这里的 MessageQueue 相当于一个单链表,每个 Message 内部都有一个 Message 类型的成员变量 next
,在 MessageQueue.enqueueMessage()
方法中,就是将 MessageQueue 中的最后一个 Message 的 next
变量赋值为当前 Message。
2)接收消息、处理消息
消息发送之后,怎么接收并处理呢?这里就要用到 Looper,我们来从源码看看执行流程:
Java
public static void loop() {
......
......
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
Java
private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
return false;
}
......
......
try {
msg.target.dispatchMessage(msg);
......
} catch (Exception exception) {
......
} finally {
......
}
......
......
}
来看 me.mQueue.next()
方法:
Java
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
......
......
}
}
这个 next()
方法其实就是从 MessageQueue 中轮询取一个 Message,如果没有就返回 null
。如果取到了 Message,那么上述 loopOnce()
方法会继续向下运行,到了 msg.target.dispatchMessage(msg)
这行调用的就是 Handler 的 dispatchMessage()
方法,dispatchMessage()
方法又会回调到 Handler 的 handleMessage()
方法处理消息。
整体流程就是:Looper.loop()
-> MessageQueue.next()
-> handler.dispatchMessage()
-> handler.handleMessage()
2、Handler 的创建
了解完上述流程之后,我们知道 Handler 消息机制中最重要的就是 Looper,如果没有正确使用 Looper,Handler 的功能就不能正常实现,那么如果我们要自定义使用一个 Handler,该怎么做呢?
我们知道 Android 程序的主线程执行入口是 ActivityThread 的 main()
方法,其中就会开启主线程的 Looper,我们在自定义的时候模仿它的行为就好了,来看看这里是怎么做的吧:
Java
public static void main(String[] args) {
......
......
Looper.prepareMainLooper();
......
......
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
......
......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
只看创建 Handler 有关的代码,我们发现系统创建主线程的 Handler 主要分三步:
- 调用
Looper.prepareMainLooper()
:
csharp
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
可以看到方法里检查了 sMainLooper
是否为空,因此这个方法系统调用了之后我们再调用就会报错,而我们使用自定义 Handler 时需要调用的是 Looper.prepare()
。
- 调用
sMainThreadHandler = thread.getHandler()
,这步就是实例化主线程 Handler,那么我们也应当在prepare()
之后实例化一个 Handler。 - 调用
Looper.loop()
,最后就再用这个方法启动消息轮询,处理消息。
但是实例化 Handler 的顺序不是固定的,而 Looper.prepare()
方法和 Looper.loop()
方法必须先后执行。
3、Message 的创建
正常情况下,我们需要实例化一个对象都是直接调用构造函数,但是在 Handler 机制中使用 Message 则不能直接创建,原因如下:
拿一块 60Hz 的屏幕来说,每秒钟会刷新 60 次 UI,每次刷新都会发送至少 3 个 Message,分别是:1、确保 UI 能刷新的消息屏障 Message;2、刷新 UI 的 Message;3、移除消息屏障的 Message(这里提到的消息屏障下文会解释)。那么每秒钟就会创建至少 180 个 Message 对象,而对象太多就会占内存,进而导致 GC,而重复创建对象再 GC 的行为,就会导致内存抖动,我们知道每次 GC 都会有一段 STW(Stop The World) 的时间,频繁的 GC 就会导致应用卡顿,因此我们不能通过直接调用构造函数的方式来创建 Message,下面让我们看看该如何来避免上述问题。
首先我们来了解一下 Message 的设计模式:享元设计模式
,通过这个设计模式能很好的避免上述问题,和 RecyclerView 的机制类似,Message 也有自己的缓存复用机制 recycle()
和 obtain()
,我们来看看这两个方法的源码:
recycle():
Java
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
Java
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
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;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
回收的主要流程就是将一个 Message 的对象的成员变量初始化,然后判断当前缓存池的大小是否小于缓存池最大容量(50),如果小于就存入缓存池中,如果大于,则缓存池满了,就不再回收。
obtain():
Java
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
Java
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
复用的流程就是判断缓存池是否为空,不为空则从缓存池中取出一个 Message 并返回,为空则只能 new()
一个 Message 并返回。
我们在使用 Message 的过程中应该通过 Handler 中的 obtainMessage()
方法来获取一个 Message 对象:
Java
public final Message obtainMessage()
{
return Message.obtain(this);
}
这里的调用链是:handler.obtainMessage()
-> Message.obtain(Handler h)
-> Message.obtain()
4、消息屏障机制
在了解消息屏障机制前,我们首先要知道这里的 Message 主要分为三类:普通 Message、屏障 Message、异步 Message,其中由于消息屏障机制,异步 Message 的执行优先级最高,比如我们上面提到了 UI 刷新的例子,为了不让界面卡顿,那么每次执行 UI 刷新的 Message 都必须立刻执行,但是正常情况下 MessageQueue 中的 Message 执行是按顺序的,因此就将 UI 刷新的 Message 类型设置为异步消息,使得 UI 刷新的 Message 能立刻执行。
消息屏障机制的作用主要体现在以下几个方面:
- 控制消息处理顺序: 消息屏障机制可以确保消息队列中的消息按照特定的顺序被处理。这对于一些需要严格控制消息处理顺序的场景非常有用,可以避免并发问题,确保消息的处理符合预期。
- 优化性能: 通过消息屏障,可以将一组相关的消息放在一个屏障消息后面,确保它们在一起被处理。这样可以减少消息处理的次数,优化程序的性能。
- 避免死锁: 在多线程环境下,如果消息处理的顺序不正确,可能会导致死锁问题。消息屏障机制可以帮助避免这种情况的发生,确保消息的处理顺序是安全的。
下面我们来了解一下消息屏障机制的执行流程。
1) postSyncBarrier()
发送消息屏障 Message
Java
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
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;
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;
}
}
可以看到,先是通过 obtain()
方法获取了一个 Message,随后在消息队列合适的位置插入这个 Message,那普通 Message 和消息屏障 Message 有什么区别呢?仔细看上面代码,发现并没有给获取到的 Message 的 target
变量赋值(这个 target
变量就是处理这个 Message 的 Handler),也就是说:target
为 null
的 Message 就是屏障消息,MessageQueue 就是靠这个变量来区分消息屏障和其他 Message 的。
2) postCallbackDelayedInternal()
发送需要优先处理的异步 Message
发送异步消息最终都会调用到下面这个方法:
Java
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
可以看到在获取消息之后,调用了 msg.setAsynchronous(true)
方法,将标志位设置为 true
,然后才发送出去,这样在 next()
方法中就能分辨出异步 Message 并优先处理。
3) 处理消息屏障 Message
还记得我们上文提到的从 MessageQueue 中取出 Message 的 next()
方法吗,不记得的可以翻上去看看,就是那个 for()
循环语句里全部省略没有粘贴上来的那块,
Java
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
......
......
}
我们可以看到同步代码块里的第一个 if
语句就判断了当前 Message 的 target
是否为 null
,即是否为消息屏障,如果满足条件,则进入 do{} while()
循环找到满足 msg != null && !msg.isAsynchronous()
条件的 Message 并执行,这个 isAsynchronous()
就是判断是否为异步消息的标志,我们下面会介绍。
4) removeSyncBarrier()
移除消息屏障 Message
Java
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
这里就是调用了 Message 的 recycleUnchecked()
方法回收了这个 Message。
那么,为什么要移除消息屏障呢?
因为如果不移除的话,根据 next()
方法中的 for()
循环执行逻辑,在有消息屏障的情况下只会处理异步消息,那么普通的消息就不能执行,因此必须移除。
5、IdleHandler
IdleHandler 又是什么东西? 来看👇👇👇
Java
public static interface IdleHandler {
boolean queueIdle();
}
它是一个接口,在 MessageQueue 空闲时,就会执行它的 queueIdle()
方法,我们可以通过下面的方法添加 IdleHandler:
Java
Looper.myQueue().addIdleHandler(new IdleHandler(){
@Override
public boolean queueIdle(){
// 在这里处理你想做的事,比如打印一个日志
Log.v("TAG", String.valueOf(System.currentTimeMillis()));
// 这里的返回值决定了是否保留,如果返回 true,则每次空闲都执行,返回 false,则一次空闲执行完之后就被移除了。
return false;
}
});
或 Kotlin 版本:
kotlin
Looper.myQueue().addIdleHandler {
Log.v("${System.currentTimeMillis()}")
return true
}
那么这个 queueIdle()
方法在什么时候执行呢?还是在上面提到的 MessageQueue 的 next()
方法中(这么多次提到,可见这个 next()
方法有多重要):
Java
for (;;) {
......
synchronized (this) {
......
......
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
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, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
可以看到,上面流程是先找到所有的 IdleHandler,遍历执行 queueIdle()
并获取返回值,之后根据返回值判断是否移除这个 IdleHandler。
说了这么多,IdleHandler 一般用在哪呢?用在:
- Activity 启动优化:
onCreate()
,onStart()
,onResume()
中耗时较短但非必要的代码可以放到 IdleHandler 中执行,减少启动时间。 - 想要一个 View 绘制完成之后,添加其他依附于这个 View 的 View,当然这个用 View 的
post()
方法也能实现,区别就是前者会在 MessageQueue 空闲时执行。 - 添加一个返回值为 true 的 IdleHandler,在里面执行方法让某个 View 不停执行某个操作。
- 一些三方库中会使用,比如 LeakCanary、Glide 中使用到,用来检测内存泄漏。
👉 以上就是对 Handler 消息机制原理的解析。