Handler源码分析
Handler的主要使用场景是子线程完成耗时操作的过程中,通过Handler向主线程中发送消息Message,用来刷新UI界面。
从new Handler()开始
在无参构造函数里面调用了重载的构造方法并分别传入null和false。并且在构造方法中给两个全局变量赋值:mLooper和mQueue。
这两者都是通过Looper来获取,具体代码如下:
可以看出,myLooper通过一个线程本地变量中的存根,然后mQueue是Looper中的一个全局变量,类型是MessageQueue类型。
Looper介绍
首先思考一个问题,启动一个Java程序的入口函数是main方法,但是当main方法执行完毕后此程序停止运行,也就是进程会自动终止。但是当我们打开一个activity之后,只要我们不按下返回键activity会一直显示在屏幕上,也就是activity所在的进程会一直处于运行状态。实际上Looper内部维护一个无限循环,保证App进程持续进行。
Looper初始化
ActivityThread 的main方法是一个新的App进程的入口,其具体实现如下:
解释:
- 图中1处就是初始化当前进程的Looper对象;
- 图中2处调用Looper的loop方法开启无限循环。
prepareMainLooper方法如下:
图中1处在prepareMainLooper中调用prepare方法创建Looper对象,仔细查看发现其实就是new出一个Looper。核心之处在于将new出来的Looper设置到了线程本地变量sThreadLocal中。也就是说创建的Looper与当前线程发生了绑定。
Looper的构造方法如下:
可以看出,在构造方法中初始化了消息队列MessageQueue对象。
prepare方法执行完毕之后,会在图中3处调用myLooper方法,从sThreadLocal中取出Looper对象并赋值给sMainLooper变量。
注意:
图中2处在创建对象之前,会判断sThreadLocal中是否已经绑定过Looper对象,如果是则抛出异常。这行代码的目的是确保在一个线程中**Looper.prepare()**方法只能被调用一次。
在MainActivity所在进程被创建时,Loope人的prepare方法已经在main方法中调用了1遍。这会直接导致一个非常重要的结果:
- prepare方法在一个线程中只能被调用一次
- Looper的构造方法在一个线程中只能被调用一次
- 最终导致MessageQueue在一个线程中只会被初始化一次
也就是说UI线程中只会存在一个MessageQueue对象,后续我们通过Handler发送的消息都会被发送到这个MessageQueue中。
Looper负责做什么事情
用一句话总结Looper做的事情就是:不断从MessageQueue中取出Message,然后处理Message中指定的任务。
在ActivityThread中的main方法,除了调用Looper.prepareMainLooper初始化Looper对象之外,还调用了Looper.loop()方法开启无限循环,Looper的主要功能就是在这个循环中完成的。
很显然,loop方法中执行了一个死循环,这也是一个Android App进程能够持续运行的原因。
图中1处不断地调用MessageQueue的next方法取出Message。如果message不为null则调用图中2处进行后续处理。具体就是从Message中取出target对象,然后调用其dispatchMessage方法处理Message自身。那这个target是谁?查看Message.java源码可以看出target就是Handler对象,如下所示:
Handler的dispatchMessage方法如下:
可以看出,在dispatchMessage方法中会调用一个空方法handleMessage,而这个方法也正是我们创建Handler时需要覆盖的方法。那么Handler是何时将其设置为一个Message的target的呢?
Handler的sendMessage方法
Handler有几个重载的sendMessage方法,但是基本上都大同小异。用最普通的sendMessage方法来分析,代码具体如下:
可以看出,经过几层调用之后,sendMessage最终会调用enqueueMessage方法将Message插入到消息队列中。而这个消息队列就是我们刚才分析的在ActivityThread的main方法中通过Looper创建的MessageQueue。
Handler的enqueueMessage方法
可以看出:
- 在图中1处enqueueMessage方法中,将Handler自身设置为Message的target对象。因此后续Message会调用此Handler的dispatchMessage处理;
- 在图中2处会判断如果Message的target没有被设置的话,则直接抛出异常;
- 图中3处会按照Message的时间when来有序的插入MessageQueue中,可以看出MessageQueue实际上是一个有序队列,只不过是按照Message的执行时间来排序。
面试常见问题:
Handler的post(Runnable)与sendMessage有什么区别
看一下post()的源码实现如下:
实际上post()会将Runnable赋值到Message的callback变量中,那么这个Runnable是在什么地方被执行的呢?Looper从MessageQueue中取出Message之后,会调用dispatchMessage方法进行处理,再看其实现:
可以看出,dispatchMessage分两种情况:
- 如果Message的Callback不为null,一般为通过post()方式,会直接执行Runnable的run方法。因此这里的Runnable实际上就是一个回调接口,跟线程Thread没有任何关系。
- 如果Message的Callback为null,这种一般为sendMessage的方式,则会调用Handler的handleMessage方法进行处理。
Looper.loop()为什么不会阻塞线程
刚才我们了解到,Looper中的loop方法实际上是一个死循环,但是我们的UI线程却并没有被阻塞,反而还能进行各种手势操作,这是因为在MessageQueue的next方法中,有如下一段代码:
nativePollOnce方法是一个native方法,当调用此方法时,主线程会释放cpu资源进入休眠状态,直到下条消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作,这里采用epoll机制。nativePollOnce函数分析
Handler的sendMessageDelayed或者postDelayed是如何实现的
在向MessageQueue队列中插入Message时,会根据Message的执行时间排序,而消息的延时处理的核心实现是在获取Message的阶段,接下来看一下MessageQueue的next方法:
图中蓝框处表示从MessageQueue中取出一个Message,但是当前的系统时间小于Message.when,因此会计算一个timeout,目的是实现在timeout时间段后再将UI线程唤醒,因此后续处理Message的代码只会在timeout时间之后才会被cpu执行
注意:在上述代码中也能看出,如果当前系统时间大于或等于Message.when,那么会返回Message给Loop.loop()。但是这个逻辑只能保证在when之前消息不被处理,不能够保证一定在when时处理。