Handler 源码解析,面试怎么回答

863 阅读5分钟

作为Android 开发者我们都知道,更新UI我们都放到主线程,耗时操作比如网络请求,或者SDK 初始化我们放到子线程,所以当耗时操作完成以后,我们需要去告诉主线程更新UI ,所以这里就有了Android 特有消息机制。 先抛出几个常见的面试题,当然这些也是我在各大厂面试经常被问到的。当然这些问题我们剖析源码也是可以合理的回答的

  1. 在子线程使用Handler 发送消息为什么主线程能收到消息?
  2. 使用Handler postDelayed() 发送延时消息,一定会准时执行吗?
  3. 怎么在子线程初始化一个Lopper ?
  4. 主线程开始消息循环的时候,为什么当前主线程没有阻塞?
  5. Hanlder 发送过期消息会怎样?

首先,一般开始回答Handler 的时候一定要把这三个与Handler 强相关的东西抛出来,ThreadLocal Looper MessageQueue当我们点击桌面应用冷启动开启一个桌面应用的时候(本文暂不讲App 启动流程)会走到ActivityThread 的main 方法

注意这里是主线程,我们继续跟进去看Looper.prepareMainLooper() 具体做了什么事情,下面这张图是Looper 的静态方法,
该方法中又继续调了自身的prepare() 方法,看到没有ThreadLocal出场了

看一下,ThreadLocal get() 方法做了什么、在ThreadLocal 的get 方法中,首先获取在当前调用这个方法的线程的对象,这里分析的是主线程,以线程当前的ThreadLocalMap ,第一次调用这个方法的时候Thread的ThreadLocalMap 肯定不存在的。所以如果在当前进程内再调用这个prePareMainLooper() 方法会抛出异常,所以这里给静态变量当前新建了一个Looper 对象,在主线程中quitAllowed 为false ,所以这里可以回到第三个问题,当我们需要在子线程创建Looper 那我们需要调用Looper.prepare() 方法,可以在子线程初始化Looper()
在Looper 的私有构造函数中又初始化了MessageQueue ,并给Looper 的mThread 赋值当前的线程以备其他地方使用
,紧接着将这个New 出的Looper 获取到Thread 的ThreadLocalMap(不为空,因为Thread 的构造中已经创建了新的ThreadLocalMap),给Thread 的ThreadLocalMap put Key 为ThreadLocal Lopper 为 Value ,到了这里主线程的Looper 才算初始化好,当然Handler 还没有见过,所谓,兵马未动粮草先行。 并且这里将ThreadLocal 讲的不算详细,后面有专门的文章介绍为什么要使用ThreadLocal 同样这时候在ActivityThread 的main 方法中调用了 Looper.loop() 方法 这时候我们看到,会去确认当前的线程是本地进程的身份, 紧接着回去当前线程的MessageQueue 去调用next() 方法查找 在这个方法中,找到了从Message 这个单链表中去查找需要被处理的消息,(这里注释说会可能阻塞),我们来看一下为什么可能会阻塞。 这里的逻辑是当,正要被执行的消息。还没有到达需要执行消息的时间的时候,就会一直阻塞在这里。所以这我们使用handler sendMessageDelayed() 方法时候,里面的参数 delayMillis 当delayMillis小于0 的时候给当消息赋值为当前时间。所以这里可以回答第5 个问题,当发送一个过期消息,会将delayMills 值赋值为0 。

这里的的Post(Runable) sendMessageDelayed(Message msg ,long delayMills), sendMessageAtFrontOfQueue(Message msg)最终都会走到 enqueueMessage(queue,msg,updatemills) ,这时候给当前的Message 的target 赋值,又会把当前消息加到MessageQueue 中,当我们看一下MessageQueue 中
我们继续看Loope() 方法中干了什么
这看到了这个消息给了给了msg 的target 中的dispatchMessage(msg) 方法中处理了。也就是Handler 中的dispatchMessahe(msg) 看到这里我们就可以回答第一个问题,在子线程发送消息,为什么在主线程可以收到消息只要我的Handler 中的Looper 发送的消息是主线程的,那我在线程发送消息,那么最终会回调到主线程的Handler,

这个方法中,如果我们给发送的一个消息赋值了Runable 也就是msg 的Callback,那么调用的就是handeCallBack(),如果我们在初始化值的时候给handler 给构造函数传入一个CallBack 那么会回调到handleMessage(msg),如果msg的callback 不存在那么最终最走到HandleMessage(msg)。 好了,我们来回答这个问题,postDelayed 准不准,只能说相对比较准,因为我们在sendMessageAtTime() 这个方法中

在这里就把时间处理好了,但是我们还要enqueueMessage(),到真正插入时间,这里是有误差的,因为这时候已经过了一段时间了,所以说在这里说handler.PostDelayed() 发送延时消息是不准确的。 现在我们来回答第四个问题,为什么主线消息中Handler 没有阻塞。这里以我个人的理解,因为整个进程中会有各种状态,会有各种消息,所有会一直的往MessageQueue 中添加消息,如果当前有阻塞的可能,我们说的是可能,当重新插入消息又会将该线程唤起。所以有整个过程中消息是不会阻塞的。(个人拙见)(注:主线程的Looper 是不能退出的,如果退出来,当前进程就结束了)