Handler
概要
Handler 用于线程间的消息传递,它能够将一个线程中的工作切换到另一个线程执行。切换的指标线程与 Handler 外部持有的 Looper 所在线程 统一。若初始化 Handler 时未手动设置 Looper,Handler 会通过 ThreadLocal 获取并持有以后(初始化 Handler 时)线程的 Looper。当 Handler 发送一条音讯后,这条音讯会进入指标线程的 MessageQueue,指标线程的 Looper 扫描并且取出音讯,最终由 Handler 执行这条音讯。
结构器
Handler 的结构器大抵分为以下两种:
public Handler(Callback callback, boolean async){}
public Handler(Looper looper, Callback callback, boolean async){}
复制代码
结构器的参数列表:
- callback:Handler 解决音讯的接口回调,执行音讯时可能会调用该接口。
- async:默认 false,若该值为 true,则音讯队列中的所有音讯均是 AsyncMessage。AsyncMessage 的概念请看后续章节。
- looper:音讯的查问者,会一直轮询查看 MessageQueue 是否有音讯。
若调用者传递 Looper,间接应用该 Looper;否则通过 ThreadLocal 从以后线程中获取 Looper。所以执行工作所在的指标线程 不是创立 Handler 时所在的线程,而是 Looper 所在的线程。
sendMessageAtTime
无论是应用 post(Runnable r)还是 sendMessage(Message m)发送音讯,最终都会执行到 sendMessageAtTime 办法。该办法指定了 Message 的执行者(msg.target=handler)和调用机会(msg.when)。
dispatchMessage
dispatchMessage 办法用于执行当时注册的 Message 和 Handler 回调,源码如下:
public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);
} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}
}
handleMessage(msg);
}
}
复制代码
能够发现回调的优先级是:Message 的回调 >Handler 的回调(结构器章节中的 callback)>Handler 子类重写的 handleMessage 办法。
ThreadLocal
ThreadLocal 是一个线程外部的数据存储类,用于寄存以线程为作用域的数据,在不同的线程中能够持有不同的数据正本。通过 ThreadLocal 就能够很不便的查找到以后线程的 Looper。ThreadLocal 外部实现的 UML 类图如下:
通过 ThreadLocal 查找 Looper 的流程如下:
- 通过 Thread.currentThread()获取以后线程对象。
- 取出线程对象持有的 ThreadLocalMap 对象。
- 以本身为 key,获取 ThreadLocalMap 中对应 Entry 的 value。
Looper
Looper 在 Handler 中扮演着音讯循环的角色。它会一直查问 MessageQueue 中是否有音讯。当没有音讯时 Looper 将始终阻塞。
若以后线程没有 Looper,且调用者未传 Looper,Handler 会因为未获取 Looper 而报错。解决办法是通过 Looper.prepare 在以后线程手动创立一个 Looper,并通过 Looper.loop 开启音讯循环:
new Thread("Thread#2") {
@override
public void run() {Looper.prepare();
Handler handler = new Handler();
Looper.loop();}
}
复制代码
Looper 提供了 quit 和 quitSafely 两种形式来退出一个 Looper。区别在于前者会间接退出;后者则是在解决完音讯队列的已有音讯后才平安退出。
Looper 所在的线程会始终处于运行状态,所以倡议音讯处理完毕后及时退出 Looper,开释线程。
MessageQueue
MessageQueue 是音讯的存储队列,外部提供了很多精彩的机制。
IdleHandler
IdleHandler 实质上只是一个形象的回调接口,没有做任何操作:
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();}
复制代码
看上述正文能够理解,MessageQueue 会在将要进入阻塞时执行 IdleHandler 的 queueIdle 办法,队列阻塞的触发机会是:
- 音讯队列没有音讯。
- 队首音讯的执行工夫大于以后工夫。
当咱们心愿一个工作在队列下次将要阻塞时调用,就能够应用 IdleHandler。在 Android 工程中最常见的例子就是:给 Activity 提供生命周期以外的回调。
比方我心愿在布局绘制实现后执行某个操作,然而 Activity 的 onStart 和 onResume 回调均在 View 绘制实现之前执行,能够看看 onResume 的官网正文:
/**
* ...
* <p>Keep in mind that onResume is not the best indicator that your activity
* is visible to the user; a system window such as the keyguard may be in
* front. Use {@link #onWindowFocusChanged} to know for certain that your
* activity is visible to the user (for example, to resume a game).
* ...
*/
@CallSuper
protected void onResume() {...}
复制代码
这种状况下就能够给 MessageQueue 设置一个 IdleHandler,等以后队列中的音讯(包含绘制工作)执行结束并将要进入阻塞状态时,调用 IdleHandler 的工作,确保工作在绘制完结后执行。
应用形式如下所示:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {Looper.myLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// do something when queue is idle
// 返回值示意 bKeepAlive 标识:true-> 持续应用,false-> 销毁该 Handler
return false;
}
});
}
复制代码
AsyncMessage 和 SyncBarrier
顾名思义,SyncBarrier 示意同步栅栏(也叫作阻碍音讯),用于阻塞 SyncMessage,优先执行 AsyncMessage。该机制大大晋升了 MessageQueue 的操作灵活性。
在进一步理解这两个概念之前,须要先理解 MessageQueue 插入音讯的机制,MessageQueue 的 enqueueMessage 源码如下(省略了唤醒队列的相干代码):
boolean enqueueMessage(Message msg, long when) {synchronized (this) {msg.markInUse();
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
// New head.
msg.next = p;
mMessages = msg;
} else {
// Inserted within the middle of the queue.
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {break;}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
return true;
}
复制代码
从上述源码可知,音讯依照调用机会(when)有序排列,当 when 等于 0 时,间接将音讯插在队头;当 when 等于队列中音讯的 when 时,将音讯插在这些音讯的前方。
假如这样一个场景:咱们有一个十分紧急的工作,心愿可能优先执行,该如何解决?
很简略,发送一个 when 为 0 的音讯,它将主动被插到列表的头部。Handler 中也提供了现成的接口:
public final boolean postAtFrontOfQueue(Runnable r)
{return sendMessageAtFrontOfQueue(getPostMessage(r));
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {return enqueueMessage(queue, msg, 0);
}
复制代码
将场景降级一下:咱们有一个工作 A,其余所有工作都依赖于 A,若 A 未执行,则其余所有工作都不容许执行。
A 插入队列的工夫和执行工夫都是不确定的,在此之前,所有工作都不容许执行。依照以后的机制无奈实现该需要,此时 SyncBarrier 和 AsyncMessage 就派上了用场,实现流程如下:
- 调用 MessageQueue.postSyncBarrier 将 SyncBarrier 插入队列:SyncBarrier 实质上是一个 target 为空的音讯,插入逻辑和一般音讯统一,也是依照 when 确定插入地位。SyncBarrier 的 when 固定是SystemClock.uptimeMillis(),因而将其插入到队列的两头(SyncBarrier 后面可能会有一些无时延的音讯,前面可能会有带时延的音讯)。
- 插入 SyncBarrier 后,轮询音讯直至 SyncBarrier 排到队列头节点,此时应用 next 办法查问音讯将主动过滤同步音讯,只执行异步音讯。源码如下所示:
// mMessages 示意队首音讯
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());
}
复制代码
- 插入工作 A(将 A 定义为 AsyncMessage),因为 SyncBarrier 的存在,A 将优先被执行(不排除 A 有时延,此时队列将进入阻塞状态,即使队列里可能存在无时延的同步音讯)。
- 只有 SyncBarrier 放在队首,同步音讯将始终被阻塞,音讯队列只能输入 AsyncMessage。当工作 A 执行结束后,须要调用 removeSyncBarrier 手动将 SyncBarrier 移除。
Handler 提供了接口让咱们插入 AsyncMessage,即结构器中的 asyc 参数。当 async 为 true 时,所有通过 Handler 传递的音讯均会被定义为 AsyncMessage(前提是要和 SyncBarrier 配合应用,不然 AsyncMessage 没有成果)。
再从新思考 SyncBarrier 和 AsyncMessage 机制的利用场景,实质上就是为了 阻塞从 Barrier 音讯到 AsyncMessage 音讯之间的同步音讯的执行。
在 Android 源码中,布局的绘制就应用了这种机制。在 ViewRootImpl 的 scheduleTraversals 办法中,会当时往主线程的音讯队列设置 Barrier,再去提交 AsyncMessage,阻塞在此期间的所有同步音讯。源码如下:
void scheduleTraversals() {if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 设置 Barrier
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 该办法最终会提交一个 AsyncMessage
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();}
}
复制代码
Tips:对于 Barrier 的概念在 Java 并发中多有波及,比方 CountDownLatch、CyclicBarrier 等。详情请查看《Thinking in Java》21.7 章节。
阻塞和唤醒机制
阻塞和唤醒机制是 MessageQueue 的精华,极大升高了 Loop 轮询的频率,缩小性能开销。
在 IdleHandler 章节曾经提及 MessageQueue 阻塞的机会:
- 音讯队列没有音讯。
- 队首音讯的执行工夫大于以后工夫。
next 办法的源码如下:
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();
}
// 要害办法,将线程阻塞 nextPollTimeoutMillis 毫秒,若 nextPollTimeoutMillis 为 -1,线程将始终处于阻塞状态。nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Ignore SyncBarrier code
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
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;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Ignore IdleHandler code
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
}
}
}
复制代码
插入音讯时唤醒 MessageQueue 的机会(假如队列处于阻塞状态):
- 队首插入一条 SyncMessage。
- 队首是一个栅栏,且插入一条离栅栏最近的 AsyncMessage。
enqueueMessage 办法的源码如下:
boolean enqueueMessage(Message msg, long when) {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;
}
复制代码
唤醒的第二种机会特意强调了插入 离 Barrier 最近的 AsyncMessage。对于如下的阻塞状况,插入 AsyncMessage 时不须要将其唤醒:
[图片上传失败 …(image-d6b8ad-1636620493886)]
Handler 内存透露剖析
理解了 Handler 的外部原理后,再来剖析由 Handler 引起的内存泄露问题:
- 当定义了一个非动态的 Handler 外部类时,外部类会隐式持有外围类的援用。
- Handler 执行 sendMessageAtTime 办法时,Message 的 target 参数会持有 Handler 对象。
- 当 Message 没有被执行时(比方 now<when),若退出了 Activity,此时 Message 仍然持有 Handler 对象,而 Handler 持有 Activity 的对象,导致内存泄露。
解决方案:
- 将 Handler 定义为动态外部类。
- 退出 Activity 时清空 MessageQueue 中对应的 Message。
相干视频举荐:
【Android 源码】handler 运行的整个流程源码解析
本文转自 https://juejin.cn/post/6947962229291647007,如有侵权,请分割删除。