首先,需要明确一点,Handler 延时消息机制不是延时发送消息,而是延时去处理消息;举个例子,如下:
handler.postDelayed(() ->{
Log.e("zjt", "delay runnable");
}, 3_000);
上面的 Handler 不是延时3秒后再发送消息,而是将消息插入消息队列后等3秒后再去处理。
postDelayed 的方法如下:
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
其中的 getPostMessage 就是将 post 的 runnable 包装成 Message,如下:
private static Message getPostMessage(Runnable r) {
// 使用 Message.obtain() 避免重复创建实例对象,达到节约内存的目的
Message m = Message.obtain();
m.callback = r;
return m;
}
sendMessageDelayed 方法如下:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis
sendMessageAtTime 如下:
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
这里面的代码很好理解,就不说了,看看 enqueueMessage:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; // 设置 msg 的 target 为Handler
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// 异步消息,这个需要配合同步屏障来使用,可以看我之前的文章,这里不赘述
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 插入到 MessageQueue 中
return queue.enqueueMessage(msg, uptimeMillis);
}
MessageQueue 的 enqueueMessage 的方法如下:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
// 判断发送消息的进程是否还活着
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle(); // 回收消息到消息池
return false;
}
msg.markInUse(); // 标记消息正在使用
msg.when = when;
Message p = mMessages; // 获取表头消息
boolean needWake;
// 如果队列中没有消息 或者 消息为即时消息 或者 表头消息时间大于当前消息的延时时间
if (p == null || when == 0 || when
举个例子,假设我们消息队列是空的,然后我发送一个延时10s的延时消息,那么会直接把消息存入消息队列。
从消息队列中获取消息是 通过 Looper.loop() 来调用 MessageQueue 的 next()方法,next()的主要代码如下:
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();
}
// 表示要休眠多长时间,功能类似于wait(time)
// -1表示一直休眠,
// 等于0时,不堵塞
// 当有新的消息来时,如果handler对应的线程是阻塞的,那么会唤醒
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
其实,从这里就可以看出来,Handler 的延时消息是如何实现的了。
比方说 发送一个延时10s的消息,那么在 next()方法是,会阻塞 (10s + 发送消息时的系统开机时间 – 执行next()方法是系统的开机时间),到达阻塞时间时会唤醒。或者这时候有新的消息来了也会 根据 mBlocked = true来唤醒。
IdleHandler是什么?
在 MessageQueue 类中有一个 static 的接口 IdleHanlder:
public static interface IdleHandler {
boolean queueIdle();
}
当MessageQueue中无可处理的Message时回调; 作用:UI线程处理完所有事务后,回调一些额外的操作,且不会堵塞主进程;
接口中只有一个 queueIdle() 函数,线程进入堵塞时执行的额外操作可以写这里, 返回值是true的话,执行完此方法后还会保留这个IdleHandler,否则删除。