面试:Handler 的工作原理是怎样的?

时间:2023-12-15 08:03:31

面试场景

平时开发用到其他线程吗?都是如何处理的?

基本都用 RxJava 的线程调度切换,嗯对,就是那个 observeOn 和 subscribeOn 可以直接处理,比如网络操作,RxJava 提供了一个叫 io 线程的处理。

在 RxJava 的广泛使用之前,有使用过其他操作方式吗?比如 Handler 什么的?

当然用过呀。

那你讲讲 Handler 的工作原理吧。

Handler 工作流程基本包括 Handler、Looper、Message、MessageQueue 四个部分。但我们在日常开发中,经常都只会用到 Handler 和 Message 两个类。Message 负责消息的搭载,里面有个 target 用于标记消息,obj 用于存放内容,Handler 负责消息的分发和处理。

一般在开发中是怎么使用 Handler 的?

官方不允许在子线程中更新 UI,所以我们经常会把需要更新 UI 的消息直接发给处理器 Handler,通过重写 Handler 的 handleMessage() 方法进行 UI 的相关操作。

那使用中就没什么需要注意的吗?

有,Handler 如果设置为私有变量的话,Android Studio 会报警告,提示可能会造成内存泄漏,这种情况可以通过设置为静态内部类 + 弱引用,或者在 onDestroy() 方法中调用 Handler.removeCallbacksAndMessages(null) 即可避免;

正文

总的来说这位面试的童鞋答的其实还是没那么差,不过细节程度还不够,所以南尘就来带大家一起走进 Handler。

Handler 工作流程浅析

异步通信准备 => 消息入队 => 消息循环 => 消息处理

  1. 异步通信准备
    假定是在主线程创建 Handler,则会直接在主线程中创建处理器对象 Looper、消息队列对象 MessageQueue 和 Handler 对象。需要注意的是,Looper 和 MessageQueue 均是属于其 创建线程 的。Looper 对象的创建一般通过 Looper.prepareMainLooper() 和 Looper.prepare() 两个方法,而创建 Looper 对象的同时,将会自动创建 MessageQueue,创建好 MessageQueue 后,Looper 将自动进入消息循环。此时,Handler 自动绑定了主线程的 Looper 和 MessageQueue

  2. 消息入队
    工作线程通过 Handler 发送消息 Message 到消息队列 MessageQueue 中,消息内容一般是 UI 操作。发送消息一般都是通过 Handler.sendMessage(Message msg) 和 Handler.post(Runnabe r) 两个方法来进行的。而入队一般是通过 MessageQueue.enqueueeMessage(Message msg,long when) 来处理。

  3. 消息循环
    主要分为「消息出队」和「消息分发」两个步骤,Looper 会通过循环 取出 消息队列 MessageQueue 里面的消息 Message,并 分发 到创建该消息的处理者 Handler。如果消息循环过程中,消息队列 MessageQueue 为空队列的话,则线程阻塞。

  4. 消息处理
    Handler 接收到 Looper 发来的消息,开始进行处理。

对于 Handler ,一些需要注意的地方

  • 1 个线程 Thread 只能绑定 1个循环器 Looper,但可以有多个处理者 Handler
  • 1 个循环器 Looper 可绑定多个处理者 Handler
  • 1 个处理者 Handler 只能绑定 1 个 1 个循环器 Looper

常规情况下,这些相关对象是怎么创建的?

前面我们说到 Looper 是通过 Looper.prepare() 和 Looper.prepareMainLooer() 创建的,我们不妨看看源码里面到底做了什么。

面试:Handler 的工作原理是怎样的?

我们不得不看看 Looper 的构造方法都做了什么。

面试:Handler 的工作原理是怎样的?

显而易见,确实在创建了 Looper 对象的时候,自动创建了消息队列对象 MessageQueue

而 Looper.prepareMainLooper() 从名称也很容易看出来,是直接在主线程内创建对象了。而在我们日常开发中,经常都是在主线程使用 Handler,所以导致了很少用到 Looper.prepare() 方法。

而生成 Looper 和 MessageQueue 对象后,则自动进入消息循环:Looper.loop(),我们不妨再看看里面到底做了什么?

面试:Handler 的工作原理是怎样的?

截图中的代码比较简单,大家应该不难看懂,我们再看看如何通过 MessageQueue.next()来取消息设置阻塞状态的。

面试:Handler 的工作原理是怎样的?

我们取消息采用了一个无限 for 循环,当没有消息的时候,则把标记位 nextPollTimeOutMillis 设置为 -1,在进行下一次循环的时候,通过 nativePollOnce() 直接让其处于线程阻塞状态。

再看看我们的消息分发是怎么处理的,主要看上面的 msg.target.dispatchMessage(msg) 方法。

面试:Handler 的工作原理是怎样的?

原来 msg.target 返回的是一个 Handler 对象,我们直接看看 Handler.dipatchMessage(Message msg) 做了什么。

面试:Handler 的工作原理是怎样的?

总结:

  • 在主线程中 Looper 对象自动生成,无需手动生成。而在子线程中,一定要调用Looper.prepare() 创建 Looper 对象。如果在子线程不手动创建,则无法生成 Handler 对象。
  • 分发消息给 Handler 的过程为:根据出队消息的归属者,通过 dispatchMessage(msg) 进行分发,最终回调复写的 handleMessage(Message msg)
  • 在消息分发 dispatchMessage(msg) 方法中,会进行 1 次发送方式判断:
    1. 若 msg.callback 属性为空,则代表使用了 post(Runnable r) 发送消息,则直接回调 Runnable 对象里面复写的 run()
    2. 若 msg.callback 属性不为空,则代表使用了 sendMessage(Message msg) 发送消息,直接回调复写的 handleMessage(msg)

常规的消息 Message 是如何创建的?

我们经常会在 Handler 的使用中创建消息对象 Message,创建方式也有两个 new Message() 或者 Message.obtain()。我们通常都更青睐于 Message.obtain() 这种方式,因为这样的方式,可以有效避免重复创建 Message 对象。实际上在代码中也是显而易见的。

面试:Handler 的工作原理是怎样的?

Handler 的另外一种使用方式

前面主要讲解了 Handler.sendMessage(Message msg) 这种常规使用方式,实际上,我们有时候也会用 Handler.post(Runnable r) 进行处理,我们当然应该看看里面是怎么处理的。

面试:Handler 的工作原理是怎样的?

从官方注释可以看到,这会直接将 Runnable 对象加到消息队列中,我们来看看 `getPostMessage(r) 到底做了什么。

面试:Handler 的工作原理是怎样的?

我们上面的分析是对的。在 getPostMessage(Runnable r) 方法中,我们除了通过 Message.obtain() 方法来创建消息对象外,专门把 Runnable 对象赋值给了 callback,这样才用了上面做消息分发的时候,通过这个标记来判断是用的 post() 还是 sendMessage() 方式。

到底是如何发消息的?

一直在说通过 sendMessage() 方式来发消息,到底这个消息是怎么发送的呢?

面试:Handler 的工作原理是怎样的?

直接看 sendMessageAtTime()

面试:Handler 的工作原理是怎样的?

enqueueMessage() 里面做了什么?

面试:Handler 的工作原理是怎样的?

至此,你大概明白了两种方式的区别了。

写在最后

本次内容可能讲的比较多和乱,还望大家跟着到源码中一步一步分析,最困难的时候,就是提升最大的时候!