共计 8008 个字符,预计需要花费 21 分钟才能阅读完成。
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,如有侵权,请分割删除。