Android 全面解析之 Handler 机制:常见问题汇总,kotlin 静态类
Handler 是如何切换线程的?
答: 使用不同线程的 Looper 处理消息。
前面我们聊到,代码的执行线程,并不是代码本身决定,而是执行这段代码的逻辑是在哪个线程,或者说是哪个线程的逻辑调用的。每个 Looper 都运行在对应的线程,所以不同的 Looper 调用的 dispatchMessage 方法就运行在其所在的线程了。
Handler 的阻塞唤醒机制是怎么回事?
答: Handler 的阻塞唤醒机制是基于 Linux 的阻塞唤醒机制。
这个机制也是类似于 handler 机制的模式。在本地创建一个文件描述符,然后需要等待的一方则监听这个文件描述符,唤醒的一方只需要修改这个文件,那么等待的一方就会收到文件从而打破唤醒。
能不能让一个 Message 加急被处理?/ 什么是 Handler 同步屏障?
答:可以 / 一种使得异步消息可以被更快处理的机制
如果向主线程发送了一个 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 发送的全部 Message 都是异步的
给 Message 标志异步
Handler 有一系列带 Boolean 类型的参数的构造器,这个参数就是决定是否是异步 Handler:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
// 这里赋值
mAsynchronous = async;
}
在发送消息的时候就会给 Message 赋值:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// 赋值
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
但是异步类型的 Handler 构造器是标记为 hide,我们无法使用,所以我们使用异步消息只有通过给 Message 设置异步标志:
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_
ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
但是!!!!,其实同步屏障对于我们的日常使用的话其实是没有多大用处。因为设置同步屏障和创建异步 Handler 的方法都是标志为 hide,说明谷歌不想要我们去使用他。所以这里同步屏障也作为一个了解,可以更加全面地理解源码中的内容。
什么是 IdleHandler?
答: 当 MessageQueue 为空或者目前没有需要执行的 Message 时会回调的接口对象。
IdleHandler 看起来好像是个 Handler,但他其实只是一个有单方法的接口,也称为函数型接口:
public static interface IdleHandler {
boolean queueIdle();
}
在 MessageQueue 中有一个 List 存储了 IdleHandler 对象,当 MessageQueue 没有需要被执行的 Message 时就会遍历回调所有的 IdleHandler。所以 IdleHandler 主要用于在消息队列空闲的时候处理一些轻量级的工作。
IdleHandler 的调用是在 next 方法中:
Message next() {
// 如果 looper 已经退出了,这里就返回 null
final long ptr = mPtr;
if (ptr == 0) {
评论