天天看點

Android面試必問的 Handler 知識點

前言

  • 在 Android 中,Handler 是貫穿于整個應用的消息機制,在面試中出現的機率為:100%
  • 在這篇文章裡,我将帶你梳理 Handler 的使用攻略 & 設計原理。追求簡單易懂又不失深度,如果能幫上忙,請務必點贊加關注!

延伸文章

  • 關于ThreadLocal,請閱讀:​​《Java | ThreadLocal 用法解析》​​
  • 關于EventBus,請閱讀:​​《Android | 這是一份詳細的 EventBus 使用教程》​​

目錄

Android面試必問的 Handler 知識點

1. 概述

在 Android 中,很多地方是通過消息機制驅動的,例如線程間通信、四大元件的啟動等等。消息機制中主要涉及 的類有:Handler & Looper & MessageQueue & Message,其中 Handler 可以說是消息機制提供給 Java 層的上層接口。

1.1 概念模型

問:Handler 怎麼進行線程通信,原理是什麼?

消息機制其實并不是 Android 系統獨有的設計,在很多系統設計中都可以看到消息機制的身影,例如 IOS 的 runLoop、Web 的 Ajax 和 Spring 的消息隊列等。在所有系統設計的消息機制裡,都會有生産者與消費者的概念,如以下模型:

Android面試必問的 Handler 知識點

消息機制概念模型

其中消息緩沖區的具體實作可以是棧 & 隊列,因為隊列(特别是優先級隊列)是最常見的,是以很多情況都會直接将消息緩沖區稱為消息隊列。

1.2 架構圖

【類圖】

  • Looper 裡組合了 MessageQueue 消息隊列,建立 Looper 的同時也建立了 MessageQueue
  • MessageQueue 裡組合了待處理的 Message 連結清單
  • Message 持有用于處理消息的 Handler(target)
  • Handler 被建立時需要聚合 Looper 與 MessageQueue,預設使用的是目前線程的 Looper

1.3 消息的享元模式

消息機制裡需要頻繁建立消息對象(Message),是以消息對象需要使用享元模式來緩存,以避免重複配置設定 & 回收記憶體。具體來說,Message 使用的是有容量限制的、無頭節點的單連結清單的對象池:

Android面試必問的 Handler 知識點
Android面試必問的 Handler 知識點

Message 的資料結構

2. Handler 核心源碼分析

這一節我們來分析 Handler 的核心源碼:

2.1 啟動消息循環

  • 問:Looper 如何在子線程中建立?(位元組、小米)

要在哪個線程啟動消息循環,就需要在該線程執行​

​Looper.prepare() & Looper.loop()​

​​。隻有調用​

​Looper.loop()​

​之後,消息循環才算真正運轉起來了。具體來說,啟動消息循環的分為兩種情況:主線程消息循環 & 子線程消息循環,前者由 Framework 啟動,而後者需要我們自己啟動:

  • 主線程消息循環
Android面試必問的 Handler 知識點

可以看到,在應用啟動時,Framework 已經為主線程開啟了消息循環,後續我們熟悉的​

​startActivity & startService​

​都是通過主線程消息循環來驅動的。

  • 子線程消息循環
Android面試必問的 Handler 知識點

在子線程開啟消息循環,我們需要自己調用​

​Looper.prepare() & Looper.loop();​

​。可以直接建立線程,或者使用 HandlerThread,後者主要考慮的多線程中擷取 Looper 的同步問題,見 第 5.1 節。

小結一下:建立 Handler 的代碼需要放在​

​Looper.prepare(); & Looper.loop();​

​​中間執行,這是因為建立 Handler 對象時需要聚合 Looper 對象(預設使用的是目前線程的 Looper),而隻有執行​

​Looper.prepare();​

​之後,才會建立該線程私有的 Looper 對象,否則建立 Handler 會抛異常。

2.2 Looper 線程唯一性

  • 問:說一下 Looper、handler、線程間的關系。例如一個線程可以對應幾個 Looper、幾個Handler?
  • 問:ThreadLocal 的原理,以及在 Looper 是如何應用的?

每個線程隻允許調用一次​

​Looper.prepare()​

​,否則會抛異常。這樣設計是因為一個 Looper 對應了一個消息循環,而一個線程進行多個消息循環是沒有意義的(一個線程不可能同時進行兩個死循環)。那麼,Handler 是如何保證 Looper 線程唯一的呢?

答:首先,Handler 主要利用了 ThreadLocal 在每個線程單獨存儲副本的特性,保證了一個​

​ThreadLocal<Looper>​

​​在不同線程存取的​

​Looper​

​​對象互相獨立;其次,ThreadLocal 是 Looper 的一個​

​static final​

​​變量,這樣就保證了整個程序中 ​

​sThreadLocal​

​​對象不可變;第三,​

​Looper.prepare()​

​判斷在一個線程裡重複調用,則會抛出異常。

Android面試必問的 Handler 知識點

關于 ThreadLocal 的原理分析,在這篇文章裡,我們詳細讨論:​​《Java | ThreadLocal 用法解析》​​,請關注!

2.3 消息發送

  • 問:Handler#post(Runnable) 是如何執行的?(位元組、小米)
  • 問:Handler#sendMessage() 和 Handler#postDelay() 的差別?(位元組)
  • 問:多個 Handler 發消息時,消息隊列如何保證線程安全?
  • 問:為什麼 MessageQueue 不設定消息上限?

消息發送的 API 非常多,最終它們都會調用到​

​Handler#sendMessageAtTime(Message msg, long uptimeMillis)​

​​,内部會交給​

​MessageQueue#enqueueMessage(Message msg, long when)​

​處理,梳理如下:

Android面試必問的 Handler 知識點

消息發送調用鍊

Android面試必問的 Handler 知識點

消息入隊關鍵源碼

小結一下:

  • 每個消息的處理時間​

    ​(when)​

    ​不一樣(SystemClock.uptimeMillis() + delayMill)
  • 消息入隊時,根據消息的處理時間​

    ​(when)​

    ​做插入排序,隊頭的消息就是最需要執行的消息
  • 當消息隊列為空時(無消息時線程會阻塞),消息入隊需要喚醒線程
  • 當消息隊列不為空時(一般不需要喚醒),隻有當開啟同步屏障後第一個異步消息需要喚醒(開啟同步屏障時會在隊首插入一個占位消息,此時消息隊列不為空,但是線程可能是阻塞的),關于同步屏障的内容見第 3 節

2.4 消息擷取

  • 問:消息隊列無消息會怎麼樣?為什麼 block 不會 ANR?
  • 問:Looper 死循環為什麼不會 ANR?(B站)
  • 問:Looper 死循環為什麼不阻塞主線程?
  • 問:Handler記憶體洩漏的原因?

上一節我們說到,消息入隊後 Looper 所線上程就會被喚醒(如果被阻塞),以繼續消息循環。在消息循環中,​

​Looper.loop()​

​會死循環從 MessageQueue 擷取隊首的消息,因為消息已經按照處理時間​

​(when)​

​​排序,是以每次擷取的都是​

​when​

​最小的消息:

【圖】 loop next

至于 Looper 死循環為什麼不會 ANR?

消息隊列中無消息怎麼處理 block
nativePollOnce值為-1表示無限等待,讓出cpu時間片給其線程,本線程等待
0表示無須等待直接傳回
nativePollOnce -> epoll(linux) ->linux層的messagequeue      
msg -> 5s -> ANRmsg

ANR:
5秒内沒有響應輸入事件,比如按鍵、螢幕觸摸
10秒内沒有處理廣播
本質:消息隊列中其他消息耗時,按鍵或廣播消息沒有及時處理

根本原因不是線程在睡眠,而是消息隊列被其他耗時消息阻塞,導緻按鍵或廣播消息沒有及時處理
      
Handler記憶體洩漏的原因
MessageQueue持有Message,Message持有activity
delay多久,message就會持有activity多久
方法:靜态内部類、弱引用      

取到一個消息時,如果when還不到,則有限等待(nextPollTimeoutMills)nativePoll()

如果消息隊列沒有消息,則無限等待nativePoll(-1,),而消息入隊時,會執行nativeWake()

quit也會nativeWake,喚醒Looper所線上程 => messagequeue傳回null => Looper退出

2.5 消息分發

  • 問:Message.callback 與 Handler.callback 哪個優先?
  • 問:Handler.callback 和 handlemessage() 都存在,但 callback 傳回 true,handleMessage() 還會執行麼?(位元組、小米)

擷取需要執行的消息之後,将調用​

​msg.target.dispatchMessage(msg);​

​處理消息,具體如下:

【圖】

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        // 1. 設定了Message.Callback(Runnable)
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            // 2. 設定了 Handler.Callback(Callback )
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 3. 未設定 Handler.Callback 或 傳回 false
        handleMessage(msg);
    }
}
public interface Callback {
    public boolean handleMessage(Message msg);
}      

可以看到,除了在​

​Handler#handleMessage(...)​

​​中處理消息外,Handler 機制還提供了兩個 Callback 來增加消息處理的靈活性。具體來說,若設定了​

​Message.Callback​

​​則優先執行,否則判斷​

​Handler.Callback​

​​的傳回結果,如果傳回​

​false​

​​,則最後分發到​

​Handler.handleMessage(...)​

2.6 終止消息循環

quit:
mQuitting = true
removeAllMessage()
nativeWake() 喚醒,程式從nativePollOnce(-1)開始執行

主線程Looper不允許退出 quit() 抛異常 mQuitAllowed = false
ActivityThead#main looper.loop() 之後抛異常 
原因:是handler驅動的機制,所有的事件都需要Handler處理,例如LAUNCH_ACTIVITY等      

3. Handler 同步屏障機制

同步屏障(SyncBarrier)是 Handler 用來篩選高低優先級消息的機制,即:當開啟同步屏障時,高優先級的異步消息優先處理。

3.1 開啟同步屏障

3.2 關閉同步屏障

3.3 同步屏障下的消息循環

4. IdleHandler 機制

  • 問:IdleHandler 是什麼?怎麼使用,能解決什麼問題

5. Handler 應用場景

4.1 HandlerThread

Handler 都是在 Looper 所線上程建立的,但是有時候我們需要在其他線程中建立 Looper 所線上程的 Handler,就需要考慮同步問題,使用 HandlerThread 可以簡化這種同步處理:

既然涉及多個線程的通信,會有同步的問題,Android為了簡化Handler的建立過程,提供了HandlerThread類

wait - notifyAll - 避免prepare之前調用getLooper()

【重點 鎖的機制】

4.2 IntentService

處理完 service 自動停止 記憶體釋放

4.3 Fragment 生命周期管理

繼續閱讀