天天看點

Handler 知識點梳理:Handler、Looper 以及 Message 三者之間的關系

前言

Handler、Looper以及Message之間的關系,概括性來說,Looper負責的是建立一個MessageQueue對象,然後進入到一個無限循環體中不斷取出消息,而這些消息都是由一個或者多個Handler進行建立處理

Messagequeue 的資料結構是什麼?

基礎資料結構中 “先進先出” 的一種資料結構

Handler post 原理

消息是通過 MessageQueen 中的 enqueueMessage()方法加入消息隊列中的,并且它在放入中就進行好排序,連結清單頭的延遲時間小,尾部延遲時間最大

  • Looper.loop()通過 MessageQueue 中的 next()取消息
  • next()中如果目前連結清單頭部消息是延遲消息,則根據延遲時間進行消息隊列會 阻塞,不傳回給 Looper message,知道時間到了,傳回給 message
  • 如果在阻塞中有新的消息插入到連結清單頭部則喚醒線程
  • Looper 将新消息交給回調給 handler 中的 handleMessage 後,繼續調用 MessageQueen的next()方法,如果剛剛的延遲消息還是時間未到,則計算時間繼續阻塞

handler.postDelay()的實作是通過MessageQueue中執行時間順序排列,消息隊列阻塞和喚醒的方式結合實作的;如果真的是通過延遲将消息放入到 MessageQueen中,那放入多個延遲消息就要維護多個定時器

Android 消息機制的簡介

在 Android 中使用消息機制,我們首先想到的就是 Handler;沒錯,Handler是 Android 消息機制的上層接口;Handler 的使用過程很簡單,通過它可以輕松地将一個任務切換到Handler所在的線程中去執行。通常情況下,Handler的使用場景就是更新UI

在子線程中,進行耗時操作,執行完操作後,發送消息,通知主線程更新 UI;這便是消息機制的典型應用場景。我們通常隻會接觸到 Handler 和 Message 來完 成消息機制,其實内部還有兩大助手來共同完成消息傳遞

消息機制的模型 消息機制主要包含: MessageQueue,Handler和Looper這三大部分,以及Message,下面我們一一介紹

  • Message: 需要傳遞的消息,可以傳遞資料;
  • MessageQueue: 消息隊列,但是它的内部實作并不是用的隊列,實際上是通過 一個單連結清單的資料結構來維護消息清單,因為單連結清單在插入和删除上比較有優 勢。主要功能向消息池投遞消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
  • Handler: 消息輔助類,主要功能向消息池發送各種消息事件 (Handler.sendMessage)和處理相應消息事件(Handler.handleMessage);
  • Looper: 不斷循環執行(Looper.loop),從 MessageQueue中讀取消息,按分發機制将消息分發給目标處理者

消息機制的架構

消息機制的運作流程: 在子線程執行完耗時操作,當 Handler 發送消息時,将會 調用MessageQueue.enqueueMessage,向消息隊列中添加消息

當通過Looper.loop 開啟循環後,會不斷地從線程池中讀取消息 ,即調用 MessageQueue.next,然後 調用目标 Handler(即發送該消息的 Handler)的 dispatchMessage 方法傳遞消息,然後傳回到 Handler 所線上程,目标 Handler 收到消息,調用 handleMessage方法,接收消息,處理消息

MessageQueue,Handler 和 Looper 三者之間的關系: 每個線程中隻能存在一個 Looper,Looper 是儲存在 ThreadLocal 中的。主線程(UI 線程)已經建立了一個 Looper,是以在主線程中不需要再建立 Looper,但是在其他線程中需要建立 Looper

每個線程中可以有多個 Handler,即一個Looper 可以處理來自多個 Handler的消息 ;Looper 中維護一個MessageQueue,來維護消息隊列,消息隊列中的 Message可以來自不同的 Handler

  • Looper 有一個 MessageQueue 消息隊列
  • MessageQueue 有一組待處理的 Message
  • Message中記錄發送和處理消息的 Handler
  • Handler中有 Looper 和 MessageQueue

我們可以使用 Handler 發送并處理與一個線程關聯的 Message 和 Runnable ;(注意:Runnable 會被封裝進一個 Message,是以它本質上還是一個 Message ) 每個 Handler 都會跟一個線程綁定,并與該線程的 MessageQueue 關聯在一起, 進而實作消息的管理以及線程間通信

  • Handler 的背後有着 Looper 以及 MessageQueue 的協助,三者通力合作,分工 明确;嘗試小結一下它們的職責,如下:
  • Looper : 負責關聯線程以及消息的分發在該線程下從 MessageQueue 擷取 Message,分發給 Handler ;
  • MessageQueue : 是個隊列,負責消息的存儲與管理,負責管理由 Handler 發送過 來的 Message ;
  • Handler : 負責發送并處理消息,面向開發者,提供 API,并隐藏背後實作的細節

    Handler 發送的消息由 MessageQueue 存儲管理,并由 Loopler 負責回調消息 到 handleMessage();線程的轉換由 Looper 完成,handleMessage() 所線上程由 Looper.loop() 調用 者所線上程決定

Handler 引起的記憶體洩露原因以及最佳解決方案

Handler 允許我們發送延時消息,如果在延時期間使用者關閉了 Activity,那麼該 Activity 會洩露

這個洩露是因為 Message 會持有 Handler,而又因為 Java 的特性,内部類會 持有外部類,使得 Activity 會被 Handler 持有,這樣最終就導緻 Activity 洩露

解決該問題的最有效的方法是:将 Handler 定義成靜态的内部類,在内部持有 Activity 的弱引用,并及時移除所有消息

示例代碼如下:

private static class SafeHandler extends Handler {
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if (activity != null) {activity.handleMessage(msg);
}
}}
           

并且再在 Activity.onDestroy() 前移除消息,加一層保障:

@Overrideprotected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
           

這樣雙重保障,就能完全避免記憶體洩露了。注意:單純的在 onDestroy 移除消息并不保險,因為 onDestroy 并不一定執行

    1. Handler 的 背 後 有 Looper 、 MessageQueue 支 撐 , Looper 負 責 消 息 分 發 , MessageQueue 負責消息管理;
    1. 在建立 Handler 之前一定需要先建立 Looper;
    1. Looper 有退出的功能,但是主線程的 Looper 不允許退出;
    1. 異步線程的 Looper 需要自己調用 Looper.myLooper().quit(); 退出;
    1. Runnable 被封裝進了 Message,可以說是一個特殊的 Message;
    1. Handler.handleMessage() 所在的線程是 Looper.loop() 方法被調用的線程,也可以說成 Looper所在的線程,并不是建立Handler的線程;
    1. 使用内部類的方式使用Handler可能會導緻記憶體洩露,即便在 Activity.onDestroy 裡移除延時消息,必須要寫成靜态内部類

Android UI 是線程不安全的,如果在子線程中嘗試進行 UI 操作,程式就有可能 會崩潰

相信大家在日常的工作當中都會經常遇到這個問題,解決的方案應該也 是早已爛熟于心,即建立一個 Message 對象,然後借助 Handler 發送出去,之 後在 Handler 的 handleMessage()方法中獲得剛才發送的 Message 對象,然 後在這裡進行 UI 操作就不會再出現崩潰了

在主線程中可 以直接建立 Handler 對象,而在子線程中需要先調用 Looper.prepare()才能創 建 Handler 對象

有關 Handler 的知識點就到這裡了, 有需要學習更多關于 Handle 知識點的同學,可以 私信 發送 “進階” 即可 擷取一份 學習筆記 ,相信能夠幫助大家 查漏補缺

資料部分内容展示如下:

《Handler 機制之 Thread》

  • 線程概念
  • Android線程的實作
  • 線程的阻塞
  • 關于線程上下文切換
  • 關于線程的安全問題
  • 守護線程
  • 線程的記憶體
Handler 知識點梳理:Handler、Looper 以及 Message 三者之間的關系

《Handler機制之ThreadLocal》

  • Java中的ThreadLocal
  • ThreadLocal的前世今生
  • Android中的ThreadLocal
  • Android 面試中的關于ThreadLocal的問題
  • ThreadLocal的結構
  • ThreadLocal靜态類ThreadLocal.Values
  • ThreadLocal的總結
Handler 知識點梳理:Handler、Looper 以及 Message 三者之間的關系

完整版PDF文檔擷取方式: 私信 發送 “進階” 即可 免費擷取

對于程式員來說,要學習的知識内容、技術有太多太多,要想不被環境淘汰就隻有不斷提升自己,從來都是我們去适應環境,而不是環境來适應我們

技術是無止境的,你需要對自己送出的每一行代碼、使用的每一個工具負責,不斷挖掘其底層原理,才能使自己的技術升華到更高的層面

Android 架構師之路還很漫長,與君共勉

繼續閱讀