Handler源码分析

Handler的主要使用场景是子线程完成耗时操作的过程中,通过Handler向主线程中发送消息Message,用来刷新UI界面。

从new Handler()开始

Handler源码分析

在无参构造函数里面调用了重载的构造方法并分别传入null和false。并且在构造方法中给两个全局变量赋值:mLooper和mQueue。

这两者都是通过Looper来获取,具体代码如下:

Handler源码分析

可以看出,myLooper通过一个线程本地变量中的存根,然后mQueue是Looper中的一个全局变量,类型是MessageQueue类型。

Looper介绍

首先思考一个问题,启动一个Java程序的入口函数是main方法,但是当main方法执行完毕后此程序停止运行,也就是进程会自动终止。但是当我们打开一个activity之后,只要我们不按下返回键activity会一直显示在屏幕上,也就是activity所在的进程会一直处于运行状态。实际上Looper内部维护一个无限循环,保证App进程持续进行。

Looper初始化

ActivityThread 的main方法是一个新的App进程的入口,其具体实现如下:

Handler源码分析

解释:

  • 图中1处就是初始化当前进程的Looper对象;
  • 图中2处调用Looper的loop方法开启无限循环。

prepareMainLooper方法如下:

Handler源码分析
图中1处在prepareMainLooper中调用prepare方法创建Looper对象,仔细查看发现其实就是new出一个Looper。核心之处在于将new出来的Looper设置到了线程本地变量sThreadLocal中。也就是说创建的Looper与当前线程发生了绑定。

Looper的构造方法如下:

Handler源码分析

可以看出,在构造方法中初始化了消息队列MessageQueue对象。

prepare方法执行完毕之后,会在图中3处调用myLooper方法,从sThreadLocal中取出Looper对象并赋值给sMainLooper变量。

Handler源码分析

注意:

图中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的主要功能就是在这个循环中完成的。

Handler源码分析

很显然,loop方法中执行了一个死循环,这也是一个Android App进程能够持续运行的原因。

图中1处不断地调用MessageQueue的next方法取出Message。如果message不为null则调用图中2处进行后续处理。具体就是从Message中取出target对象,然后调用其dispatchMessage方法处理Message自身。那这个target是谁?查看Message.java源码可以看出target就是Handler对象,如下所示:

Handler源码分析

Handler的dispatchMessage方法如下:

Handler源码分析

可以看出,在dispatchMessage方法中会调用一个空方法handleMessage,而这个方法也正是我们创建Handler时需要覆盖的方法。那么Handler是何时将其设置为一个Message的target的呢?

Handler的sendMessage方法

Handler有几个重载的sendMessage方法,但是基本上都大同小异。用最普通的sendMessage方法来分析,代码具体如下:

Handler源码分析

可以看出,经过几层调用之后,sendMessage最终会调用enqueueMessage方法将Message插入到消息队列中。而这个消息队列就是我们刚才分析的在ActivityThread的main方法中通过Looper创建的MessageQueue。

Handler的enqueueMessage方法

Handler源码分析

可以看出:

  • 在图中1处enqueueMessage方法中,将Handler自身设置为Message的target对象。因此后续Message会调用此Handler的dispatchMessage处理;
  • 在图中2处会判断如果Message的target没有被设置的话,则直接抛出异常;
  • 图中3处会按照Message的时间when来有序的插入MessageQueue中,可以看出MessageQueue实际上是一个有序队列,只不过是按照Message的执行时间来排序。

面试常见问题:

Handler的post(Runnable)与sendMessage有什么区别

看一下post()的源码实现如下:

Handler源码分析

实际上post()会将Runnable赋值到Message的callback变量中,那么这个Runnable是在什么地方被执行的呢?Looper从MessageQueue中取出Message之后,会调用dispatchMessage方法进行处理,再看其实现:

Handler源码分析

可以看出,dispatchMessage分两种情况:

  1. 如果Message的Callback不为null,一般为通过post()方式,会直接执行Runnable的run方法。因此这里的Runnable实际上就是一个回调接口,跟线程Thread没有任何关系。
  2. 如果Message的Callback为null,这种一般为sendMessage的方式,则会调用Handler的handleMessage方法进行处理。

Looper.loop()为什么不会阻塞线程

刚才我们了解到,Looper中的loop方法实际上是一个死循环,但是我们的UI线程却并没有被阻塞,反而还能进行各种手势操作,这是因为在MessageQueue的next方法中,有如下一段代码:

Handler源码分析

nativePollOnce方法是一个native方法,当调用此方法时,主线程会释放cpu资源进入休眠状态,直到下条消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作,这里采用epoll机制。nativePollOnce函数分析

Handler的sendMessageDelayed或者postDelayed是如何实现的

在向MessageQueue队列中插入Message时,会根据Message的执行时间排序,而消息的延时处理的核心实现是在获取Message的阶段,接下来看一下MessageQueue的next方法:

Handler源码分析

图中蓝框处表示从MessageQueue中取出一个Message,但是当前的系统时间小于Message.when,因此会计算一个timeout,目的是实现在timeout时间段后再将UI线程唤醒,因此后续处理Message的代码只会在timeout时间之后才会被cpu执行

注意:在上述代码中也能看出,如果当前系统时间大于或等于Message.when,那么会返回Message给Loop.loop()。但是这个逻辑只能保证在when之前消息不被处理,不能够保证一定在when时处理。

相关文章:

  • 2021-10-22
  • 2022-12-23
  • 2021-07-04
  • 2021-12-21
  • 2022-12-23
  • 2021-06-19
  • 2021-06-05
猜你喜欢
  • 2022-12-23
  • 2021-07-24
  • 2021-06-02
  • 2021-09-28
  • 2021-09-30
相关资源
相似解决方案