面試場景
平時開發用到其他執行緒嗎?都是如何處理的?
基本都用 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()
裡面做了什麼?
至此,你大概明白了兩種方式的區別了。
寫在最後
本次內容可能講的比較多和亂,還望大家跟著到原始碼中一步一步分析,最困難的時候,就是提升最大的時候!
我是南塵,只做比心的公眾號,歡迎關注我。
做不完的開源,寫不完的矯情。歡迎掃描下方二維碼或者公眾號搜尋「nanchen」關注我的微信公眾號,目前多運營 Android ,儘自己所能為你提升。如果你喜歡,為我點贊分享吧~