Handler源码解读

1,600 阅读6分钟


大家面试的时候是否经常被问到有没有看过handler源码,如果回答看过了接下来就各种深入提问,直到问得你云里雾里面试官才开心。今天作者带领大家一起深入地过一遍handler源码,将面试常用问题各个击破,从此手捧offer,和面试失败say no!


看源码首先当然是从使用处看起,我们首先来看一下平时项目中handler的使用,我们首先定义了一个handler对象,并且写好了接收到消息以后需要执行的操作,然后在子线程中发送一个消息。


问题一  handler发送的消息是怎么到达handlerMessage回调的呢?


首先我们就从源头开始看起,handler.senMessage()方法一路点下去,发现最终调用的是MessageQueue类的enqueueMessage方法,也就是说最终是把message添加到了消息队列MessageQueue中,如下图所示


OK,消息放进去了,但是我们发现这里面并没有调用handler的handleMessage方法,所以我们换个角度想,放进去了总要取出来吧,不然放进去就没有意义了。于是我们就在MessageQueue中寻找返回值为Message的方法,果不其然找到了一个next方法,在该方法中返回了刚刚保存好的message,如下图


那么问题又来了,究竟是谁调用了这个next方法把消息取出来了呢?解决这个问题就要反过来考虑,需要调用next方法就需要实例化MessageQueue这个类,不然没法调用,MessageQueue是在Looper中初始化的,所以接下来我们来看看Looper类的源码。在Looper源码中是在构造函数中初始化MessageQueue的

接下来我们再搜一下调用next方法的地方



我们发现在Looper的loop方法中有一个无限循环,其中每次循环都调用MessageQueue的next方法,如果找到了就会执行msg.target.dispatchMessage(msg),那么msg.target是什么东西呢?我们接下来就点开Message类,查看一下target属性,如下

哇塞,原来是Handler对象,这里其实就是调用的handler的handlerMessage方法,现在逻辑就清晰了。Handler首先将我们发送的message保存到MessageQueue,然后Looper获取到message以后调用Handler的handlerMessage方法将message返回。


问题二 Handler是如何实现跨线程通信的?

纵览全局,我们发现,其实message相当于一个物品,MessageQueue相当于一个仓库,被handler放到MesageQueue,然后被Looper取出来还给handler,对于物品来说是不会挑选主人的,类的属性也一样,无论哪个线程都可以操作修改。所以message在子线程被放进去以后在主线程被取出来就很正常了。



问题三 Looper是在哪里被调用的?

我们平时使用Handler一般是子线程和主线程通信,而主线程的Looper其实在app初始化的时候就已经初始化并且开始Loop无限循环获取Message了,具体代码在ActivityThread的main方法中,其中prepareMainLooper就是初始化Looper,然后还调用了loop方法。如下图



问题四 如何保证Looper唯一性,一个线程不会出现多个Looper?

我们都知道,如果同一线程每次都new一个Looper的话就没办法保持唯一性。想要知道这个知识点,必须看一下Looper是怎么设置和获取的,我们点进去初始化的源码,如下图

我们发现,Looper的设置和获取并不是普通的属性get和set,而是通过ThreadLocal类来操作,所以我们点进去ThreadLocal看一下,发现里面Looper的设置和获取操作都需要传入线程参数,也就是说以线程为Key,对应了唯一的Looper,其实Looper唯一也就意味着MessageQueue也是唯一的,因为MessageQueue是在Looper构造方法里初始化一次的,但是Handler可以有多个,因为我们每初始化一个Handler对象,都会被Message记录,最后调用相应Handler的handleMessage方法,并不会有冲突。其中ThreadLocal代码如下



问题五 MessageQueue是一个什么样的数据结构?

分析这个问题,我们首先要找到其设置数据的地方,如下图所示,MessageQueue保存了每条message的值和next指针,也就是说想获取到下一条消息必须要看上一条消息的next指向,这样就导致了数据的先进先出,和LinkedList的实现类似,显然是一个单向链表的实现。



问题六 消息是怎么实现延时的?

1.既然消息是保存在队列中,那么刚保存的时候就应该保存好该消息所在的位置才对,不然在获取的时候就没办法保证延时短的消息先发送了,如问题五中的图,这个的实现其实就是死循环对每个message的延时时间进行对比,如果当前消息的延时短则更改指针指向,将当前消息插入到队列中。

2.如果发送消息的时间没到,则不交给Looper。如下图,在Looper获取消息时会对时间进行判断,如果时间还没到则不返回message,即相当于本次循环并没有到达时间需要发送的消息



问题七 Handler机制中生产者和消费者模式体现在哪里?

前面说到handler中把消息放到了messageQueue,然后Looper从messageQueue中获取消息并使用,这个相当于就是一个生产者和消费者模式,一个负责生产一个负责消费。生产消费者模式中的阻塞在handler中也是体现得淋漓尽致,在获取消息时,如果消息池中消息数量为空,那么此时进入阻塞状态,如下图


问题八 为何Looper中死循环不会阻塞UI

首先我们必须明白的是app本身就是一个死循环,不然岂不是一打开就结束了?其次Looper和更新UI是共存的关系,这里会有一个底层的睡眠机制,在需要的时候再唤起。

最后:本人小萌新,之前都是看得多写得少,现在也想把自己的所见所得记录下来给大家分享分享,若有写错或者没有补充完整的地方欢迎各位大神指教,以后有新的心得体会会及时更新本文,谢谢大家阅读!