Message对象的when?Hanlder是如何将一个延迟消息添加到消息队列的?

1. 前言

今天在面试三七互娱的时候两个面试官轮流上阵,那个时候确实是比较虚的,幸好问的都还知道。只是对于Handler消息机制中如题的两个点,确实没有怎么注意过,也没有答出来。这里记录下。

2. 解答

平时没怎么用到过这个字段,经常用的是whatobj这两个字段。那么就来看下这个when。先百度下:

what区分消息要做的事情,when表示什么时候发生的。

害,这么说来延迟消息的应该也就是一样加入到消息队列,只是标识了执行的时机,使用when来标识。那么岂不是面试官当时连着这两个问题是在做提示?麻了

为了验证上面的猜想,来看一下源码:

// Handler.java
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    // 延迟 =》 系统开机到当前的时间总毫秒数 + 延迟毫秒数 
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) { // 队列为null对象,直接返回false
        ...
    }
    // 做一次转换,传入消息队列对象
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                               long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 入队的为Message消息,和延迟毫秒数
    return queue.enqueueMessage(msg, uptimeMillis);
}

// MessageQueue.java
// 在enqueueMessage的地方传入的就是消息对象和when对象,也就是会传入延迟毫秒数
boolean enqueueMessage(Message msg, long when) {
}

从上面我们知道当有一个延迟的消息添加到消息队列的时候,首先会做一个时间的转换,计算出一个和系统开机时间相关的更大的延迟毫秒数,也就是when。然后最终会调用enqueueMessage方法,这个方法传入两个对象一个为Message,另一个为when,也就是执行的时间。那么不妨继续来看下源码:

// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
	...
    synchronized (this) {
        ...
        msg.when = when; // 将之间赋值到了Message对象中
        Message p = mMessages; // 成员变量mMessages
        boolean needWake;
        // 判断下Message的执行时间
        // 1. 如果没有上一个Message对象,说明是第一个消息对象,直接插入链表首部;
        // 2. 如果when==0,也就是系统刚开机,且非延迟消息,直接插入到链表首部;
        // 3. 如果当前延迟小于链表首部的延迟,直接添加到首部;
        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插入到链表中合适when的位置
            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;
}

其实也就是说,最终的执行时间其实是赋值到了Message对象中,而在消息队列的这个链表中进行插入的时候会分情况进行判断:

  • 如果当前消息队列还没有接收过消息,就直接将消息置于链表首部;
  • 如果当前消息的when字段小于链表头结点的when值,就直接将消息置于链表首部;
  • 这个链表是按照when进行排序的链表,在进行Message插入的过程,会对消息的延迟时间来进行比较,插入到链表中合适的位置中。最终使得整个消息队列中,消息均呈现延迟有序的状态。

最终,读了源码发现和上面的猜想略有出入,但是却也相差不大。

到这里我才知道原来消息队列中,消息插入到单链表中是按照Messagewhen字段来进行对比的,按照when的大小关系进行比较,以插入到合适的链表位置,即队列位置。

而不是一直以为的添加到队列尾部。害,一直理解错误,还要继续加油!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦否

文章对你有用?不妨打赏一毛两毛

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值