介绍
Handler是Android的消息机制,可以用于App内部通信,比如线程间通信。这套机制的实现基于生产者和消费者模型,下面我会回答什么是生产者和消费者模型。Handler是如何将消息放入消息池,以及消息被谁什么时候取出这些问题进行讲解
一、思维导图
这张图基本囊括了Handler的所有知识点,可作为学习Handler的思维导图
二、Handler 设计思想
Handler的设计是基于生产者和消费者模型的方式,在实际应用中可以完成线程之间的通信。在一个生产者-消费者模式中,通常会有三个角色:生产者、消费者和消息队列。它们的工作流程如下图所示:
-
生产者: 负责生产消息,塞到共享的消息队列中可以有多个
-
消费者: 负责从共享消息队列中取出消息,执行消息对应的任务
-
消息队列: 负责存取消息
这三个角色中,因为生产者和消费者要从同一个消息队列取放消息,所以消息队列的数量要求是唯一的,生产者和消费者的数量可以任意
三、工作原理
Handler 可以发送发处理与线程MessageQueue关联的Message。当App启动时会执行ActivityThread#main方法,创建一个Lopper,他会绑定到创建它的线程。从那时起,它将消息从队列中取并分发给处理者执行它们。如果在子线程的话,要自己创建Looper,并调用loop轮询消息
3.1 类图说明
Handler:消息的生产者,负责发生消息也是消息的处理者
Looper:消息的消费者,从消息队列里面取消息,交给Handler处理
Message:消息体是一个单链表结构,生产过来的消息按时间维度以单链表的形式串起来
MessageQueue:用来管理Message,负责消息的插入和取出
4、流程图
这张图以主线程创建Looper为例,App启动之后执行ActivityThread#main方法,会去创建消息消费者Looper,Looper不断的轮询MessaqeQueue是否有新消息,没有消息的话无限休眠等待被唤醒,由于epoll机制会释放当前的cpu资源不会阻塞住主线程。直到MessageQueue有新消息加入之后,唤醒轮询。然后Looper将消息取出,并交给消息处理者
4.1 核心代码
消费消息
// 1. ActivityThread#Main
// 初始化Looper
public static void main(String[] args) {
Looper.prepareMainLooper();
Looper.loop();
}
// 2. Looper#loop 消费消息
// 从MessageQueue中取消息然后进行下发
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (; ; ) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
// 3. MessageQueue#next
// 取出位于头部的消息如果没有的话会进入block
// 取出消息的类型根据是否开启了同步屏障为条件
// 开启取异步消息,为开启取同步消息
// 最后执行空闲Handler即idlerHandler
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
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;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// 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
&& (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 MessageQueue.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 MessageQueue.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);
}
}
}
// 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.
nextPollTimeoutMillis = 0;
}
}
生产消息
// 4.Handler#enqueueMessage
// 将消息传递给MessageQueue
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
return queue.enqueueMessage(msg, uptimeMillis);
}
// 5.MessageQueue#enqueueMessage 插入消息
// MessageQueue是按照Message触发时间的先后顺序排列的,
// 队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,
// 会从队列头开始遍历,直到找到消息应该插入的合适位置,
// 以保证所有消息的时间顺序。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
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 {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
五、常见问题
5.1 什么是同步屏障
MessageQueue 中的postSyncBarrier()方法用来发送同步屏障,此方法被标记为hide。它的作用是让异步消息优先得到执行。当方法被调用时会往MessageQueue中添加一条没有target的Message。此时Looper将优先消费异步消息,同步消息被挂起无限等待。直到调用removeSyncBarrier(token)移除同步消息,恢复所有消息的正常处理。
5.2 什么是IdleHandler
Handler 提供了一种当消息队列出现空闲的时候,允许我们执行任务的一种机制。触发时机在MessageQueue取不到消息的时候,或者处理的消息是一个延迟消息的时候。
5.3 为什么会导致内存泄漏
内存泄漏的本质是生命周期的异常,没有及时释放,或者长期得不到释放。使用Handler导致的内存泄漏,是因为退出页面时没有切断GC Root 抵达 Activity 的引用链。 比如以下场景:
- 退出的时候仍有 Thread 在处理中,其引用着 Handler
- Thread 结束了,但 Message 尚在队列中排队处理或正在处理中,间接持有 Handler
解决方案:
- 使用 Handler 时采用静态内部类 + 弱引用,避免其强引用持有 Activity 的实例
- 在 Activity 结束的时候,及时地清空 Message终止 Thread 或退出 Looper
(以上的问题为了抛砖引玉,细节的话可以看看我的参考资料)
参考资料
[1] Android Handler消息机制解读
[2] Android组件系列:Handler机制详解
[3] Android消息机制1-Handler Java层
[4] 移动架构 (二) Android 中 Handler 架构分析
[5] Android同步屏障机制
[6] Handler之postSyncBarrier消息屏障
[7] Android图形渲染之Choreographer原理
[8] Handler的初级、中级、高级问法
[9] IdleHandler 的原理分析和妙用