1. 程式人生 > >Android源碼學習-----HandlerThread

Android源碼學習-----HandlerThread

對象創建 run 兩個 新的 不為 safe 排序 locked null

HandlerThread

1.run()方法

HandlerThread 從繼承關系上看, 它繼承Thread類, 由此可以得知這個類其實是一個線程類,既然是一個線程類, 那麽肯定是要重寫Thread中的run()方法, 所以可以瀏覽下run()方法

技術分享圖片

從紅色箭頭的三個方法中, 看到有三個方法, Looper.prepare(), Looper.myLooper(), Looper.loop(), 這三個方法其實是, 在子線程創建Handler時所要調用的三個方法, 在HandlerThreadle類中, 和其它類的區別在於創建了一個Looper, 讓它能夠在該線程中創建Handler對象

1.1 run()方法中的Synchronized和notifyAll

技術分享圖片

通常情況下, 只要出現了synchronized關鍵字, 往往意味著此處會造成線程安全問題, 並且這裏調用了Thread中的notifyAll()方法來喚醒其它線程, 此處應該能夠推斷出, 既然調用了notifyAll方法, 那麽很有可能其它地方使用了wait()方法, 具體調用位置請看 getLooper方法

2.getLooper()方法

技術分享圖片

該方法中有一行註釋, 大概意思是, 如果當這個線程已經被創建了, 但是looper還沒有創建完成, 此時需要讓線程進入wait()狀態, 等待looper對象創建完成, 當looper對象在run()方法中通過Looper.myLooper()創建完成後, 會緊接著調用notifyAll()方法來喚醒wait()狀態下的線程

3. quit()方法和quitSafely()方法

3.1 用途

技術分享圖片技術分享圖片

這兩個方法的用途其實都是終止looper的, 因為每次創建一個HandlerThread對象, 調用run()方法, 就會創建一個Looper對象, 而Looper是一個死循環, 這樣會消耗大量的資源, 所以正確的操作方式為, 當界面銷毀的時候, 調用quit()方法或者quitSafely()方法, 將Looper停止掉

3.2 源碼

兩個方法看起來很類似, 區別在於一個調用了looper對象中的quit()方法, 另一個調用了quitSafely()方法, 跟進源碼

技術分享圖片

圖中的mQueue其實是一個MessageQueue對象, 這兩個方法最終調用的就是MessageQueue中的quit方法,只不過傳入的參數分別是true 和 false

查看MessageQueue中的quit方法

技術分享圖片

傳入的boolean值最終在這裏判斷, 如果為true ,也就是調用的是quitSafely()方法, 那麽執行的是removeAllFutureMessagesLocked()方法, 如果為false, 那麽調用的就是quit()方法, 最終執行的就是removeAllMessagesLocked()方法.

繼續跟進removeAllFutureMessageLocked()方法, 裏面也會調用方法 removeAllMessageLocked(),

技術分享圖片

這兩個方法的有一個共同點就是, 當方法被調用時, Looper不會再接收新的Message消息,

區別在於不帶future的方法, 會將消息隊列中的所有類型的消息清空, 無論是延遲發送的還是立即執行的, 而帶future的方法, 只是清除掉了延遲消息, 對於非延遲消息, 則會將其交給handler去執行.

技術分享圖片

圖片上面是源碼的處理,

但是, 為什麽n.when > now 就表示後就可以跳出循環,意味著沒有立即執行的消息了?

這是因為MessageQueue消息隊列處理方式的關系, 簡單的可以理解為, MessageQueue消息隊列是按照消息的處理時間的從早到晚排序的

4. MessageQueue插入消息的方式簡介

Handler將一個消息發送出去後, 這個消息會被插入到消息隊列, 但並不是無腦的將這個消息插入到最末尾, 先看下Message中的部分成員屬性

技術分享圖片

When:是這個消息的執行時間, 也就是判斷這個消息是否是一個延遲消息, 以及延遲多久

Data: 是這個消息中攜帶的數據, 並且可以看出Message是利用Bundle來處理消息的

Next: 是一個消息對象, 上面有一行註釋, 大概意識是有的時候需要用next來存儲一些鏈關系, 這其實是類似鏈表的結構, 鏈表結構中有頭指針 頭節點等概念, 這裏面假設mMessage永遠指向隊列中的第一個Message

技術分享圖片

當一個新的消息發送過來時, 分情況分析,

第一種情況, 滿足放在首位的判斷條件, 那麽就讓mMessages來指向這個新插入的消息即可, 那麽放在首位的條件是什麽? 請看下面的源碼

技術分享圖片

熟悉handler源碼的應該知道, Handler發送消息, 無論調用哪個方法, 最終都是由sendMessageAtTime()這個方法來執行, 而Handler的sendMessageAtTime()方法中調用了這個enqueueMessage()方法, 在enqueueMessage()這個方法中, 首條消息對象mMessage賦值給了 一個Message對象 p, 然後對這個p進行了一些列判斷,

P == null: 這個判斷表示消息隊列中mMessage對象是否為null, 如果為null, 那麽證明此時整個消息隊列都是空的, 所以滿足這個條件, 新的消息可以放到第一個位置

When == 0: when就是 handler發送消息時傳入過來的消息觸發時間, 如果為0, 表示這個消息是個非延遲消息, 需要立刻執行, 此時新的消息也要放到第一條,

When < p.when: 也就是handler新發送的這個消息的觸發時間, 小於第一條消息的觸發時間, 那麽也要放到第一個位置

只要滿足上面三種情況, 那麽就讓mMessage指向新插入的消息即可,

技術分享圖片

源碼

技術分享圖片

第二種情況

也就是新發送來的消息msg的觸發時間, 要晚於mMessage的時間, 那麽就會將msg的when和消息隊列後面的每個時間when做下對比, 直到符合從早到晚的順序為止

技術分享圖片

可以看出源碼中有一個死循環, 會一直判斷消息隊列的後面是否還有消息, 以及觸發時間, 當滿足了任意一個條件後, 就會停止循環判斷, 然後將新消息msg插入到隊列中

源碼中可以看出聲明了一個新的消息對象 prev, 這個對象永遠指向的是msg的前一個消息

技術分享圖片

下面是過程圖, 圖中0x1010等假設為消息的地址, 僅供理解參考用, 不為真實地址, 所以無論何時, prev永遠記錄的是p的前一個地址

技術分享圖片

當新的消息msg找到何時的位置時, 也就是prev.when < msg.when < p.when時, 就會將msg插入到prev之後, p之前這個位置

技術分享圖片

技術分享圖片

到此為止, 新的消息msg就插入到了MessageQueue隊列中了

如有錯誤, 敬請指正, 感激不盡!

Android源碼學習-----HandlerThread