面試: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 工作流程淺析

異步通信准備 => 消息入隊 => 消息循環 => 消息處理

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

 

我們不得不看看 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() 里面做了什么?

 

至此,你大概明白了兩種方式的區別了。

寫在最后

本次內容可能講的比較多和亂,還望大家跟着到源碼中一步一步分析,最困難的時候,就是提升最大的時候!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM