面试:Handler 的工作原理是怎样的?
面试场景
平时开发用到其他线程吗?都是如何处理的?
基本都用 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 工作流程浅析
异步通信准备 => 消息入队 => 消息循环 => 消息处理
-
异步通信准备
假定是在主线程创建 Handler,则会直接在主线程中创建处理器对象Looper
、消息队列对象MessageQueue
和 Handler 对象。需要注意的是,Looper
和MessageQueue
均是属于其 创建线程 的。Looper
对象的创建一般通过Looper.prepareMainLooper()
和Looper.prepare()
两个方法,而创建Looper
对象的同时,将会自动创建MessageQueue
,创建好MessageQueue
后,Looper
将自动进入消息循环。此时,Handler
自动绑定了主线程的Looper
和MessageQueue
。 -
消息入队
工作线程通过Handler
发送消息Message
到消息队列MessageQueue
中,消息内容一般是 UI 操作。发送消息一般都是通过Handler.sendMessage(Message msg)
和Handler.post(Runnabe r)
两个方法来进行的。而入队一般是通过MessageQueue.enqueueeMessage(Message msg,long when)
来处理。 -
消息循环
主要分为「消息出队」和「消息分发」两个步骤,Looper
会通过循环 取出 消息队列MessageQueue
里面的消息Message
,并 分发 到创建该消息的处理者Handler
。如果消息循环过程中,消息队列MessageQueue
为空队列的话,则线程阻塞。 -
消息处理
Handler
接收到Looper
发来的消息,开始进行处理。
对于 Handler ,一些需要注意的地方
- 1 个线程
Thread
只能绑定 1个循环器Looper
,但可以有多个处理者Handler
- 1 个循环器
Looper
可绑定多个处理者Handler
- 1 个处理者
Handler
只能绑定 1 个 1 个循环器Looper
常规情况下,这些相关对象是怎么创建的?
前面我们说到 Looper
是通过 Looper.prepare()
和 Looper.prepareMainLooer()
创建的,我们不妨看看源码里面到底做了什么。
我们不得不看看 Looper
的构造方法都做了什么。
显而易见,确实在创建了 Looper
对象的时候,自动创建了消息队列对象 MessageQueue
。
而 Looper.prepareMainLooper()
从名称也很容易看出来,是直接在主线程内创建对象了。而在我们日常开发中,经常都是在主线程使用 Handler
,所以导致了很少用到 Looper.prepare()
方法。
而生成 Looper
和 MessageQueue
对象后,则自动进入消息循环:Looper.loop()
,我们不妨再看看里面到底做了什么?
截图中的代码比较简单,大家应该不难看懂,我们再看看如何通过 MessageQueue.next()
来取消息设置阻塞状态的。
我们取消息采用了一个无限 for 循环,当没有消息的时候,则把标记位 nextPollTimeOutMillis
设置为 -1,在进行下一次循环的时候,通过 nativePollOnce()
直接让其处于线程阻塞状态。
再看看我们的消息分发是怎么处理的,主要看上面的 msg.target.dispatchMessage(msg)
方法。
原来 msg.target
返回的是一个 Handler
对象,我们直接看看 Handler.dipatchMessage(Message msg)
做了什么。
总结:
- 在主线程中
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.sendMessage(Message msg)
这种常规使用方式,实际上,我们有时候也会用 Handler.post(Runnable r)
进行处理,我们当然应该看看里面是怎么处理的。
从官方注释可以看到,这会直接将 Runnable
对象加到消息队列中,我们来看看 `getPostMessage(r) 到底做了什么。
我们上面的分析是对的。在 getPostMessage(Runnable r)
方法中,我们除了通过 Message.obtain()
方法来创建消息对象外,专门把 Runnable
对象赋值给了 callback
,这样才用了上面做消息分发的时候,通过这个标记来判断是用的 post()
还是 sendMessage()
方式。
到底是如何发消息的?
一直在说通过 sendMessage()
方式来发消息,到底这个消息是怎么发送的呢?
直接看 sendMessageAtTime()
。
enqueueMessage()
里面做了什么?
至此,你大概明白了两种方式的区别了。
写在最后
本次内容可能讲的比较多和乱,还望大家跟着到源码中一步一步分析,最困难的时候,就是提升最大的时候!