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

面试场景

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

基本都用 RxJava 的线程调度切换,嗯对,就是那个 observeOnsubscribeOn 可以直接处理,比如网络操作,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 对象。需要注意的是,LooperMessageQueue 均是属于其 创建线程 的。Looper 对象的创建一般通过 Looper.prepareMainLooper()Looper.prepare() 两个方法,而创建 Looper 对象的同时,将会自动创建 MessageQueue,创建好 MessageQueue 后,Looper 将自动进入消息循环。此时,Handler 自动绑定了主线程的 LooperMessageQueue

  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() 创建的,我们不妨看看源码里面到底做了什么。

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

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

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

而生成 LooperMessageQueue 对象后,则自动进入消息循环: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() 里面做了什么?

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

写在最后

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,158评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,600评论 1 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,785评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,655评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,075评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,002评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,146评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,918评论 0 211
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,671评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,838评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,318评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,636评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,343评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,187评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,982评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,126评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,934评论 2 279

推荐阅读更多精彩内容