面試場景
平時(shí)開發(fā)用到其他線程嗎?都是如何處理的方咆?
基本都用 RxJava 的線程調(diào)度切換月腋,嗯對,就是那個(gè) observeOn
和 subscribeOn
可以直接處理,比如網(wǎng)絡(luò)操作榆骚,RxJava 提供了一個(gè)叫 io
線程的處理片拍。
在 RxJava 的廣泛使用之前,有使用過其他操作方式嗎妓肢?比如 Handler 什么的穆碎?
當(dāng)然用過呀。
那你講講 Handler 的工作原理吧职恳。
Handler 工作流程基本包括 Handler所禀、Looper、Message放钦、MessageQueue 四個(gè)部分色徘。但我們在日常開發(fā)中,經(jīng)常都只會用到 Handler 和 Message 兩個(gè)類操禀。Message 負(fù)責(zé)消息的搭載褂策,里面有個(gè) target
用于標(biāo)記消息,obj
用于存放內(nèi)容颓屑,Handler 負(fù)責(zé)消息的分發(fā)和處理斤寂。
一般在開發(fā)中是怎么使用 Handler 的?
官方不允許在子線程中更新 UI揪惦,所以我們經(jīng)常會把需要更新 UI 的消息直接發(fā)給處理器 Handler遍搞,通過重寫 Handler 的 handleMessage()
方法進(jìn)行 UI 的相關(guān)操作。
那使用中就沒什么需要注意的嗎器腋?
有溪猿,Handler 如果設(shè)置為私有變量的話,Android Studio 會報(bào)警告纫塌,提示可能會造成內(nèi)存泄漏诊县,這種情況可以通過設(shè)置為靜態(tài)內(nèi)部類 + 弱引用,或者在 onDestroy()
方法中調(diào)用 Handler.removeCallbacksAndMessages(null)
即可避免措左;
正文
總的來說這位面試的童鞋答的其實(shí)還是沒那么差依痊,不過細(xì)節(jié)程度還不夠,所以南塵就來帶大家一起走進(jìn) Handler怎披。
Handler 工作流程淺析
異步通信準(zhǔn)備 => 消息入隊(duì) => 消息循環(huán) => 消息處理
異步通信準(zhǔn)備
假定是在主線程創(chuàng)建 Handler胸嘁,則會直接在主線程中創(chuàng)建處理器對象Looper
、消息隊(duì)列對象MessageQueue
和 Handler 對象钳枕。需要注意的是缴渊,Looper
和MessageQueue
均是屬于其 創(chuàng)建線程 的。Looper
對象的創(chuàng)建一般通過Looper.prepareMainLooper()
和Looper.prepare()
兩個(gè)方法鱼炒,而創(chuàng)建Looper
對象的同時(shí),將會自動創(chuàng)建MessageQueue
蝌借,創(chuàng)建好MessageQueue
后昔瞧,Looper
將自動進(jìn)入消息循環(huán)指蚁。此時(shí),Handler
自動綁定了主線程的Looper
和MessageQueue
自晰。消息入隊(duì)
工作線程通過Handler
發(fā)送消息Message
到消息隊(duì)列MessageQueue
中凝化,消息內(nèi)容一般是 UI 操作。發(fā)送消息一般都是通過Handler.sendMessage(Message msg)
和Handler.post(Runnabe r)
兩個(gè)方法來進(jìn)行的酬荞。而入隊(duì)一般是通過MessageQueue.enqueueeMessage(Message msg,long when)
來處理搓劫。消息循環(huán)
主要分為「消息出隊(duì)」和「消息分發(fā)」兩個(gè)步驟,Looper
會通過循環(huán) 取出 消息隊(duì)列MessageQueue
里面的消息Message
混巧,并 分發(fā) 到創(chuàng)建該消息的處理者Handler
枪向。如果消息循環(huán)過程中,消息隊(duì)列MessageQueue
為空隊(duì)列的話咧党,則線程阻塞秘蛔。消息處理
Handler
接收到Looper
發(fā)來的消息,開始進(jìn)行處理傍衡。
對于 Handler 深员,一些需要注意的地方
- 1 個(gè)線程
Thread
只能綁定 1個(gè)循環(huán)器Looper
,但可以有多個(gè)處理者Handler
- 1 個(gè)循環(huán)器
Looper
可綁定多個(gè)處理者Handler
- 1 個(gè)處理者
Handler
只能綁定 1 個(gè) 1 個(gè)循環(huán)器Looper
常規(guī)情況下蛙埂,這些相關(guān)對象是怎么創(chuàng)建的倦畅?
前面我們說到 Looper
是通過 Looper.prepare()
和 Looper.prepareMainLooer()
創(chuàng)建的,我們不妨看看源碼里面到底做了什么绣的。
我們不得不看看 Looper
的構(gòu)造方法都做了什么滔迈。
顯而易見,確實(shí)在創(chuàng)建了 Looper
對象的時(shí)候被辑,自動創(chuàng)建了消息隊(duì)列對象 MessageQueue
燎悍。
而 Looper.prepareMainLooper()
從名稱也很容易看出來,是直接在主線程內(nèi)創(chuàng)建對象了盼理。而在我們?nèi)粘i_發(fā)中谈山,經(jīng)常都是在主線程使用 Handler
,所以導(dǎo)致了很少用到 Looper.prepare()
方法宏怔。
而生成 Looper
和 MessageQueue
對象后奏路,則自動進(jìn)入消息循環(huán):Looper.loop()
,我們不妨再看看里面到底做了什么臊诊?
截圖中的代碼比較簡單鸽粉,大家應(yīng)該不難看懂,我們再看看如何通過 MessageQueue.next()
來取消息設(shè)置阻塞狀態(tài)的抓艳。
我們?nèi)∠⒉捎昧艘粋€(gè)無限 for 循環(huán)触机,當(dāng)沒有消息的時(shí)候,則把標(biāo)記位 nextPollTimeOutMillis
設(shè)置為 -1,在進(jìn)行下一次循環(huán)的時(shí)候儡首,通過 nativePollOnce()
直接讓其處于線程阻塞狀態(tài)片任。
再看看我們的消息分發(fā)是怎么處理的,主要看上面的 msg.target.dispatchMessage(msg)
方法蔬胯。
原來 msg.target
返回的是一個(gè) Handler
對象对供,我們直接看看 Handler.dipatchMessage(Message msg)
做了什么。
總結(jié):
- 在主線程中
Looper
對象自動生成氛濒,無需手動生成产场。而在子線程中,一定要調(diào)用Looper.prepare()
創(chuàng)建Looper
對象舞竿。如果在子線程不手動創(chuàng)建京景,則無法生成Handler
對象。- 分發(fā)消息給
Handler
的過程為:根據(jù)出隊(duì)消息的歸屬者炬灭,通過dispatchMessage(msg)
進(jìn)行分發(fā)醋粟,最終回調(diào)復(fù)寫的handleMessage(Message msg)
。- 在消息分發(fā)
dispatchMessage(msg)
方法中重归,會進(jìn)行 1 次發(fā)送方式判斷:
1. 若msg.callback
屬性不為空米愿,則代表使用了post(Runnable r)
發(fā)送消息,則直接回調(diào)Runnable
對象里面復(fù)寫的run()
鼻吮。
2. 若msg.callback
屬性為空育苟,則代表使用了sendMessage(Message msg)
發(fā)送消息,直接回調(diào)復(fù)寫的handleMessage(msg)
椎木。
常規(guī)的消息 Message 是如何創(chuàng)建的违柏?
我們經(jīng)常會在 Handler
的使用中創(chuàng)建消息對象 Message
,創(chuàng)建方式也有兩個(gè) new Message()
或者 Message.obtain()
香椎。我們通常都更青睞于 Message.obtain()
這種方式漱竖,因?yàn)檫@樣的方式,可以有效避免重復(fù)創(chuàng)建 Message
對象畜伐。實(shí)際上在代碼中也是顯而易見的馍惹。
Handler 的另外一種使用方式
前面主要講解了 Handler.sendMessage(Message msg)
這種常規(guī)使用方式,實(shí)際上玛界,我們有時(shí)候也會用 Handler.post(Runnable r)
進(jìn)行處理万矾,我們當(dāng)然應(yīng)該看看里面是怎么處理的。
從官方注釋可以看到慎框,這會直接將 Runnable
對象加到消息隊(duì)列中良狈,我們來看看 `getPostMessage(r) 到底做了什么。
我們上面的分析是對的笨枯。在 getPostMessage(Runnable r)
方法中薪丁,我們除了通過 Message.obtain()
方法來創(chuàng)建消息對象外遇西,專門把 Runnable
對象賦值給了 callback
,這樣才用了上面做消息分發(fā)的時(shí)候窥突,通過這個(gè)標(biāo)記來判斷是用的 post()
還是 sendMessage()
方式努溃。
到底是如何發(fā)消息的硫嘶?
一直在說通過 sendMessage()
方式來發(fā)消息阻问,到底這個(gè)消息是怎么發(fā)送的呢?
直接看 sendMessageAtTime()
沦疾。
enqueueMessage()
里面做了什么称近?
至此,你大概明白了兩種方式的區(qū)別了哮塞。
寫在最后
本次內(nèi)容可能講的比較多和亂刨秆,還望大家跟著到源碼中一步一步分析,最困難的時(shí)候忆畅,就是提升最大的時(shí)候衡未!