面試場景
平時開發用到其他線程嗎?都是如何處理的?
基本都用 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()
里面做了什么?

至此,你大概明白了兩種方式的區別了。
寫在最后
本次內容可能講的比較多和亂,還望大家跟着到源碼中一步一步分析,最困難的時候,就是提升最大的時候!