面試:Handler 的工作機制是怎樣的呢?

nanchen2251發表於2018-03-28

面試場景

平時開發用到其他執行緒嗎?都是如何處理的?

基本都用 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() 建立的,我們不妨看看原始碼裡面到底做了什麼。

面試:Handler 的工作機制是怎樣的呢?

我們不得不看看 Looper 的構造方法都做了什麼。

面試:Handler 的工作機制是怎樣的呢?

顯而易見,確實在建立了 Looper 物件的時候,自動建立了訊息佇列物件 MessageQueue

Looper.prepareMainLooper() 從名稱也很容易看出來,是直接在主執行緒內建立物件了。而在我們日常開發中,經常都是在主執行緒使用 Handler,所以導致了很少用到 Looper.prepare() 方法。

而生成 LooperMessageQueue 物件後,則自動進入訊息迴圈:Looper.loop(),我們不妨再看看裡面到底做了什麼?

面試:Handler 的工作機制是怎樣的呢?

截圖中的程式碼比較簡單,大家應該不難看懂,我們再看看如何通過 MessageQueue.next() 來取訊息設定阻塞狀態的。

面試:Handler 的工作機制是怎樣的呢?

我們取訊息採用了一個無限 for 迴圈,當沒有訊息的時候,則把標記位 nextPollTimeOutMillis 設定為 -1,在進行下一次迴圈的時候,通過 nativePollOnce() 直接讓其處於執行緒阻塞狀態。

再看看我們的訊息分發是怎麼處理的,主要看上面的 msg.target.dispatchMessage(msg) 方法。

面試:Handler 的工作機制是怎樣的呢?

原來 msg.target 返回的是一個 Handler 物件,我們直接看看 Handler.dipatchMessage(Message msg) 做了什麼。

面試:Handler 的工作機制是怎樣的呢?

總結:

  • 在主執行緒中 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 的另外一種使用方式

前面主要講解了 Handler.sendMessage(Message msg) 這種常規使用方式,實際上,我們有時候也會用 Handler.post(Runnable r) 進行處理,我們當然應該看看裡面是怎麼處理的。

面試:Handler 的工作機制是怎樣的呢?

從官方註釋可以看到,這會直接將 Runnable 物件加到訊息佇列中,我們來看看 `getPostMessage(r) 到底做了什麼。

面試:Handler 的工作機制是怎樣的呢?

我們上面的分析是對的。在 getPostMessage(Runnable r) 方法中,我們除了通過 Message.obtain() 方法來建立訊息物件外,專門把 Runnable 物件賦值給了 callback,這樣才用了上面做訊息分發的時候,通過這個標記來判斷是用的 post() 還是 sendMessage() 方式。

到底是如何發訊息的?

一直在說通過 sendMessage() 方式來發訊息,到底這個訊息是怎麼傳送的呢?

面試:Handler 的工作機制是怎樣的呢?

直接看 sendMessageAtTime()

面試:Handler 的工作機制是怎樣的呢?

enqueueMessage() 裡面做了什麼?

面試:Handler 的工作機制是怎樣的呢?

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

寫在最後

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

我是南塵,只做比心的公眾號,歡迎關注我。

面試:Handler 的工作機制是怎樣的呢?
做不完的開源,寫不完的矯情。歡迎掃描下方二維碼或者公眾號搜尋「nanchen」關注我的微信公眾號,目前多運營 Android ,儘自己所能為你提升。如果你喜歡,為我點贊分享吧~
nanchen

相關文章