面試場景
平時開發用到其他線程嗎?都是如何處理的?
基本都用 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
和 Handler 對象。需要注意的是,MessageQueue
Looper
均是屬于其 建立線程 的。MessageQueue
對象的建立一般通過Looper
Looper.prepareMainLooper()
兩個方法,而建立Looper.prepare()
對象的同時,将會自動建立Looper
,建立好MessageQueue
後,MessageQueue
将自動進入消息循環。此時,Looper
自動綁定了主線程的Handler
Looper
。MessageQueue
-
消息入隊
工作線程通過
發送消息Handler
到消息隊列Message
中,消息内容一般是 UI 操作。發送消息一般都是通過MessageQueue
Handler.sendMessage(Message msg)
兩個方法來進行的。而入隊一般是通過Handler.post(Runnabe r)
來處理。MessageQueue.enqueueeMessage(Message msg,long when)
-
消息循環
主要分為「消息出隊」和「消息分發」兩個步驟,
會通過循環 取出 消息隊列Looper
裡面的消息MessageQueue
,并 分發 到建立該消息的處理者Message
。如果消息循環過程中,消息隊列Handler
為空隊列的話,則線程阻塞。MessageQueue
- 消息處理
接收到Handler
發來的消息,開始進行處理。Looper
對于 Handler ,一些需要注意的地方
- 1 個線程
隻能綁定 1個循環器Thread
,但可以有多個處理者Looper
Handler
- 1 個循環器
可綁定多個處理者Looper
Handler
- 1 個處理者
隻能綁定 1 個 1 個循環器Handler
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
2. 若
run()
屬性不為空,則代表使用了
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()
裡面做了什麼?
至此,你大概明白了兩種方式的差別了。
寫在最後
本次内容可能講的比較多和亂,還望大家跟着到源碼中一步一步分析,最困難的時候,就是提升最大的時候!