目录
一、问题
二、Handler机制和原理
1. Handler中包含的对象
2.运行机制
三、Message 消息队列对于延迟消息的处理
1. 先看一下发送消息
2.之后看一下消息队列
2.1重点讲解 3 处,这个地方要分类去讨论
3、总结
四、接收消息
总结
回顾问题
说下 handler 机制,Looper 通过 MessageQueue 取消息,消息队列是先进先出模式,那我延迟发两个消息,第一个消息延迟2个小时,第二个消息延迟1个小时,那么第二个消息需要等3个小时才能取到吗?
当然不是,其实Message Queue内部维护了一个消息链表,对于msg有一个字段when,when的值是通过进栈时间戳+delay组成的,而这个链表就是根据when字段从小到大排列的,每次循环都判断当前的时间戳,是否大于链表顶部msg的when字段,直到大于 when就可以取出链表顶部的msg消息,进行发送了。
首先是Handler,它负责Message的发送和执行处理,是Message的主要处理者
那么何为Message呢,可以理解为线程的交流信息
Message Queue,用来存放通过Handler发布的消息,按照先进先出执行(此处引出了上面的面试问题,下面会讲解)
Looper ,是每条线程里的Message Queue的管家
首先在主线程创建一个Handler对象 ,并重写handleMessage方法。
然后当在子线程中需要进行更新UI的操作,我们就创建一个Message对象,并通过handler发送这条消息出去。
之后这条消息被加入到MessageQueue队列中等待被处理,通过Looper对象会一直尝试从Message Queue中取出待处理的
消息,最后分发会Handler的handlerMessage方法中,然后进行相应的处理。
Handler.class
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
....
}
Handler 在发送消息时都会进入这一步,从这段代码中我们捋出几个重要点:
Handler.class
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
消息发出后,最终会调用到 enqueueMessage,这里
我们接着看一下MessageQueue.enqueueMessage
MessageQueue.class
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.when = when;
Message p = mMessages;
boolean needWake;
//1. 如果进来的消息 when 比当前头节点 p.when 还小,就把该消息插入到表头
if (p == || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
...
Message prev;
for (; ; ) {
prev = p;
//遍历链表 p = p.next;
//2. p== : 只有在遍历到链表尾的时候才会为 true
//when < p.when : 上一个消息的延迟大于当前延迟,这个地方就可以回顾面试的那个问题
//p.when 当做第一个延迟2小时,when 当做目前进来的延迟1小时,这个时候是为 true
if (p == || when < p.when) {
break;
}
...
}
//3.下面讲解
msg.next = p;
prev.next = msg;
}
}
继续捋关键点:
uptimeMillis(时间戳+延迟时间)在这个地方变成了 when,并且赋值给了 Message
如果进来的消息 when 比当前头节点 p.when(头节点上的Message的when) 还小或相等,就把该消息插入到表头
如果大于p.when 将会遍历链表,只有在遍历到链表尾的时候 p== : 才会为true
我们给出两个假设和例子:
1、假设一: p== 为 true
p==为 true 的话,也就意味着链表遍历到了链尾,
并且 when < p.when一直都为 false,
也就是说进来的消息延迟都是大于当前节点的延迟,
这个地方我们来举个满足条件例子:
- 原消息链:0s -> 0s -> 1s -> 4s
- 进来延迟消息为 10s
最后的代码就是意思就是 10s.next=、4s.next=10s
最终链表为:0s -> 0s -> 1s -> 4s -> 10s
2、假设二: when < p.when 为 true
也就是说,链表还没有遍历到链尾发现进来的消息延迟小于当前节点的延迟,然后break了循环体,
这个地方也来举一个满足条件的例子:
- 原消息链:0s -> 0s -> 1s -> 4s
- 进来延迟消息为 2s
遍历到 4s 的时候,发现 2s < 4s,break,
当前 p 节点指向的是节点 4s,则最后代码的意思就是 2s.next=4s、1s.next=2s,
最终链表为:0s -> 0s -> 1s -> 2s -> 4s
Handler 会根据延迟消息整理链表,最终构建出一个时间从小到大的序列
Looper.class
public static void loop
{
final MessageQueue queue = me.mQueue;
for (; ; ) {
Message msg = queue.next; // might block
// ...
try {
msg.target.dispatchMessage(msg);
} catch {
}
...
}
}
loop 会一直循环去遍历 MessageQueue 的消息,
拿到 msg 消息后,会将消息 dispatchMessage 发送出去,
那么,me.next 取消息就显得尤为重要了,我们进来看看。
MessageQueue.class
Message next
{
...
int nextPollTimeoutMillis = 0;
for (; ; ) {
...
synchronized (this) {
// 试图获取下一条消息。如果发现返回
//1、获取当前的时间戳
final long now = SystemClock.uptimeMillis;
Message prevMsg = ;
Message msg = mMessages;
...
if (msg !=) {
//2,如果当前时间戳小于所取延迟消息,则以他们的时间差返回
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 {
//3、延迟时间到了就可以拿到消息,直接返回了
// Got a message.
mBlocked = false;
if (prevMsg !=) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
//4、下面解释
msg.next =;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse;
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
nextPollTimeoutMillis = 0;
}
}
}
详细解释下:
2标识:
还记得 msg.when是由什么构成的嘛?时间戳+delay,
每次循环都会更新 now的时间戳,
也就是说,当前for循环会一直去执行,直到 now大于 时间戳+delay就可以去取消息了。
3标识:
因为消息的存取都是按时间从小到大排列的,每次取到的消息都是链表头部,
这时候链头需要脱离整个链表,则设置 next=。
知道最后这个用完的消息去哪了嘛?还记得 obtainMessage 复用消息吗?
延迟消息的发送是通过循环遍历,不停的获取当前时间戳来与 msg.when 比较,直到小于当前时间戳为止。
那通过这段代码我们也是可以发现,通过 Handler.delay 去延迟多少秒是非常不精确的,因为相减会发生偏差
参考:https://www.toutiao.com/i6667297908389315083/