一个 Handler 面试题引发的血案!!!

简介: 一个 Handler 面试题引发的血案!!!

一位热心群友在面试时抛了一个问题:

说下 handler 机制,Looper 通过 MessageQueue 取消息,消息队列是先进先出模式,那我延迟发两个消息,第一个消息延迟2个小时,第二个消息延迟1个小时,那么第二个消息需要等3个小时才能取到吗?

鉴于这个血案,我们来翻翻案,一探究竟。

已知


  • Main Handler 在 ActivityThread 的时候就 Looper.loop
  • 所有的消息都是通过 Looper.loop 进行分发


  • Message 消息队列对于延迟消息是如何处理的?


解题步骤氛围两步来看:

  • 分发消息 sendMessageDelayed
  • 接收消息 dispatchMessage

分发消息


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 在发送消息时都会进入这一步,从这段代码中我们捋出几个重要点:


  • delay 设置的延迟时间低于0时默认为0
  • uptimeMillis 为当前 时间戳+延迟时间 (注意,这里后面需要用上)
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码


最终会调用到 enqueueMessage ,这里给几个信息:


  • msg.target 指当前创建的 Handler
  • mAsynchronous 默认为 false
  • 最终调用 MessageQueue.enqueueMessage


来看看 MessageQueue.enqueueMessage 干了啥:


MessageQueue.class

boolean enqueueMessage(Message msg, long when) {
    ...
    synchronized (this) {
    ... 
    msg.when = when;
    Message p = mMessages;
    boolean needWake;
    //① 如果进来的消息 when 比当前头节点 p.when 还小,就想该消息插入到表头
    if (p == null || when == 0 || when < p.when) {
        msg.next = p;
        mMessages = msg;
        needWake = mBlocked;
     } else {
         ...
         Message prev;
         for (;;) {
            prev = p;
            //遍历链表
            p = p.next;
            //②
            //p==null : 只有在遍历到链表尾的时候才会为 true
            //when < p.when : 上一个消息的延迟大于当前延迟,这个地方就可以回顾面试的那个问题
            //p.when 当做第一个延迟2小时,when 当做目前进来的延迟1小时,这个时候是为 true
            if (p == null || when < p.when) {
                   break;
            }
            ...
          }
         //③
         msg.next = p;
         prev.next = msg;
     }
 }
复制代码


继续捋关键点:


  • 时间戳+延迟时间 在这个地方变成了 when ,并且赋值给了 Message
  • 其他解释看标记处


这个地方需要重点讲解 ③ 处,这个地方要分类去讨论,我们给出两个假设和例子:


假设一: p==null 为 true

p==null  为 true 的话,也就意味着链表遍历到了链尾,并且 when < p.when 一直都为 false,也就是说进来的消息延迟都是大于当前节点的延迟,这个地方我们来举个满足条件例子:


  • 原消息链:0s -> 0s -> 1s -> 4s
  • 进来延迟消息为 10s


最后的代码就是意思就是 10s.next=null4s.next=10s  ,最终链表为:


  • 0s -> 0s -> 1s -> 4s -> 10s

假设二: when < p.when 为 true


也就是说,链表还没有遍历到链尾发现进来的消息延迟小于当前节点的延迟,然后break了循环体,这个地方也来举一个满足条件的例子:


  • 原消息链:0s -> 0s -> 1s -> 4s
  • 进来延迟消息为 2s


遍历到 4s 的时候,发现 2s < 4s,break,当前 p 节点指向的是节点 4s,则最后代码的意思就是 2s.next=4s1s.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) {
                // Try to retrieve the next message.  Return if found.
                //①、获取当前的时间戳
                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;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
            ...
            nextPollTimeoutMillis = 0;
        }
   }
复制代码


详细解释下:


②标识:

还记得 msg.when 是由什么构成的嘛?时间戳+delay ,每次循环都会更新 now 的时间戳,也就是说,当前for循环会一直去执行,直到 now 大于 时间戳+delay 就可以去取消息了。


④标识:

因为消息的存取都是按时间从小到大排列的,每次取到的消息都是链表头部,这时候链头需要脱离整个链表,则设置 next=null。知道最后这个用完的消息去哪了嘛?还记得 obtainMessage 复用消息吗?

总结


延迟消息的发送是通过循环遍历,不停的获取当前时间戳来与 msg.when 比较,直到小于当前时间戳为止。那通过这段代码我们也是可以发现,通过 Handler.delay 去延迟多少秒是非常不精确的,因为相减会发生偏差


回顾问题,我们来解答:


  • MessageQueue 的实现不是队列,不要被名称迷惑,他是一个链表
  • 每次发送消息都会按照 delay 从小到大进行重排
  • 所有的 delay 消息都是并行的,不是串行的
  • 第一个延迟2个小时,第二个延迟1小时,会优先执行第二个,再过1小时执行第一个
目录
相关文章
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
61 1
|
7月前
|
存储 消息中间件 安全
27道 Handler 经典面试题,你能答出多少?
27道 Handler 经典面试题,你能答出多少?
|
4月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android 消息处理机制估计都被写烂了,但是依然还是要写一下,因为Android应用程序是通过消息来驱动的,Android某种意义上也可以说成是一个以消息驱动的系统,UI、事件、生命周期都和消息处理机制息息相关,并且消息处理机制在整个Android知识体系中也是尤其重要,在太多的源码分析的文章讲得比较繁琐,很多人对整个消息处理机制依然是懵懵懂懂,这篇文章通过一些问答的模式结合Android主线程(UI线程)的工作原理来讲解,源码注释很全,还有结合流程图,如果你对Android 消息处理机制还不是很理解,我相信只要你静下心来耐心的看,肯定会有不少的收获的。
220 3
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
5月前
|
消息中间件 调度 Android开发
Android经典面试题之View的post方法和Handler的post方法有什么区别?
本文对比了Android开发中`View.post`与`Handler.post`的使用。`View.post`将任务加入视图关联的消息队列,在视图布局后执行,适合视图操作。`Handler.post`更通用,可调度至特定Handler的线程,不仅限于视图任务。选择方法取决于具体需求和上下文。
63 0
|
Android开发
Android面试常客之Handler全解1
Android面试常客之Handler全解
|
消息中间件 Android开发
Android面试常客之Handler全解2
Android面试常客之Handler全解
|
消息中间件 Android开发
|
消息中间件 调度 Android开发
面试:Handler 的工作原理是怎样的?
面试场景 平时开发用到其他线程吗?都是如何处理的? 基本都用 RxJava 的线程调度切换,嗯对,就是那个 observeOn 和 subscribeOn 可以直接处理,比如网络操作,RxJava 提供了一个叫 io 线程的处理。
1215 0
|
消息中间件 Android开发
Android 面试(五):探索 Android 的 Handler
这是 面试系列 的第五期。本期我们将来探讨一下 Android 异步消息处理线程 —— Handler。 往期内容传递:Android 面试(一):说说 Android 的四种启动模式Android 面试(二):如何理解 Activity 的生命周期Android 面试(三):用广播 BroadcastReceiver 更新 UI 界面真的好吗?Android 面试(四):Android Service 你真的能应答自如了吗? 开始 Android 的消息机制,也就是 Handler 机制,相信各位都已经是烂熟于心了吧。
1550 0

热门文章

最新文章