2020Android面試重難點之Handler機制,含字節、京東、騰訊經典面試真題解析!


Handler 在整個 Android 開發體系中占據着很重要的地位,對開發者來說起到的作用很明確,就是為了實現線程切換或者是執行延時任務,稍微更高級一點的用法可能是為了保證多個任務在執行時的有序性。

由於 Android 系統中的主線程有特殊地位,所以像 EventBus 和 Retrofit 這類並非 Android 獨有的三方庫,都是通過 Handler 來實現對 Android 系統的特殊平台支持。大部分開發者都已經對如何使用 Handler 很熟悉了,這里就再來了解下其內部具體是如何實現的。

一、動手實現 Handler

本文不會一上來就直接介紹源碼,而是會先根據我們想要實現的效果來反推源碼,一步步來自己動手實現一個簡單的 Handler

1、Message

首先,我們需要有個載體來表示要執行的任務,就叫它 Message 吧,Message 應該有什么參數呢?

  • 需要有一個唯一標識,因為要執行的任務可能有多個,我們要分得清哪個是哪個,用個 Int 類型變量就足夠表示了
  • 需要能夠承載數據,需要發送的數據類型會有很多種可能,那就直接用一個 Object 類型變量來表示吧,由開發者自己在使用時再來強轉類型
  • 需要有一個 long 類型變量來表示任務的執行時間戳

所以,Message 類就應該至少包含以下幾個字段:

/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC */ public final class Message { //唯一標識 public int what; //數據 public Object obj; //時間戳 public long when; } 

2、MessageQueue

因為 Message 並不是發送了就能夠馬上被消費掉,所以就肯定要有個可以用來存放的地方,就叫它 MessageQueue 吧,即消息隊列。Message 可能需要延遲處理,那么 MessageQueue 在保存 Message 的時候就應該按照時間戳的大小來順序存放,時間戳小的 Message 放在隊列的頭部,在消費 Message 的時候就直接從隊列頭取值即可

那么用什么數據結構來存放 Message 比較好呢?

  • 用數組?不太合適,數組雖然在遍歷的時候會比較快,但需要預先就申請固定的內存空間,導致在插入數據和移除數據時可能需要移動大量數據。而 MessageQueue 可能隨時會收到數量不定、時間戳大小不定的 Message,消費完 Message 后還需要將該其移出隊列,所以使用數組並不合適
  • 用鏈表?好像可以,鏈表在插入數據和移除數據時只需要改變指針的引用即可,不需要移動數據,內存空間也只需要按需申請即可。雖然鏈表在隨機訪問的時候性能不高,但是對於 MessageQueue 而言無所謂,因為在消費 Message 的時候也只需要取隊列頭的值,並不需要隨機訪問

好了,既然決定用鏈表結構,那么 Message 就需要增加一個字段用於指向下一條消息才行

/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC */ public final class Message { //唯一標識 public int what; //數據 public Object obj; //時間戳 public long when; //下一個節點 public Message next; } 

MessageQueue 需要提供一個 enqueueMessage方法用來向鏈表插入 Message,由於存在多個線程同時向隊列發送消息的可能,所以方法內部還需要做下線程同步才行

/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC */ public class MessageQueue { //鏈表中的第一條消息 private Message mMessages; void enqueueMessage(Message msg, long when) { synchronized (this) { Message p = mMessages; //如果鏈表是空的,或者處於隊頭的消息的時間戳比 msg 要大,則將 msg 作為鏈表頭部 if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; } else { Message prev; //從鏈表頭向鏈表尾遍歷,尋找鏈表中第一條時間戳比 msg 大的消息,將 msg 插到該消息的前面 for (; ; ) { prev = p; p = p.next; if (p == null || when < p.when) { break; } } msg.next = p; prev.next = msg; } } } } 

此外,MessageQueue 要有一個可以獲取隊頭消息的方法才行,就叫做next()吧。外部有可能會隨時向 MessageQueue 發送 Message,next()方法內部就直接來開啟一個無限循環來反復取值吧。如果當前隊頭的消息可以直接處理的話(即消息的時間戳小於或等於當前時間),那么就直接返回隊頭消息。而如果隊頭消息的時間戳比當前時間還要大(即隊頭消息是一個延時消息),那么就計算當前時間和隊頭消息的時間戳的差值,計算 next() 方法需要阻塞等待的時間,調用 nativePollOnce()方法來等待一段時間后再繼續循環遍歷

    //用來標記 next() 方法是否正處於阻塞等待的狀態 private boolean mBlocked = false; Message next() { int nextPollTimeoutMillis = 0; for (; ; ) { nativePollOnce(nextPollTimeoutMillis); synchronized (this) { //當前時間 final long now = SystemClock.uptimeMillis(); Message msg = mMessages; if (msg != null) { if (now < msg.when) { //如果當前時間還未到達消息的的處理時間,那么就計算還需要等待的時間 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { //可以處理隊頭的消息了,第二條消息成為隊頭 mMessages = msg.next; msg.next = null; mBlocked = false; return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } mBlocked = true; } } } //將 next 方法的調用線程休眠指定時間 private void nativePollOnce(long nextPollTimeoutMillis) { } 

此時就需要考慮到一種情形:當 next()還處於阻塞狀態的時候,外部向消息隊列插入了一個可以立即處理或者是阻塞等待時間比較短的 Message。此時就需要喚醒休眠的線程,因此 enqueueMessage還需要再改動下,增加判斷是否需要喚醒next()方法的邏輯

    void enqueueMessage(Message msg, long when) { synchronized (this) { //用於標記是否需要喚醒 next 方法 boolean needWake = false; Message p = mMessages; //如果鏈表是空的,或者處於隊頭的消息的時間戳比 msg 要大,則將 msg 作為鏈表頭部 if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; //需要喚醒 needWake = mBlocked; } else { Message prev; //從鏈表頭向鏈表尾遍歷,尋找鏈表中第一條時間戳比 msg 大的消息,將 msg 插到該消息的前面 for (; ; ) { prev = p; p = p.next; if (p == null || when < p.when) { break; } } msg.next = p; prev.next = msg; } if (needWake) { //喚醒 next() 方法 nativeWake(); } } } //喚醒 next() 方法 private void nativeWake() { } 

3、Handler

既然存放消息的地方已經確定就是 MessageQueue 了,那么自然就還需要有一個類可以用來向 MessageQueue 發送消息了,就叫它 Handler 吧。Handler 可以實現哪些功能呢?

  • 希望除了可以發送 Message 類型的消息外還可以發送 Runnable 類型的消息。這個簡單,Handler 內部將 Runnable 包裝為 Message 即可
  • 希望可以發送延時消息,以此來執行延時任務。這個也簡單,用 Message 內部的 when 字段來標識希望任務執行時的時間戳即可
  • 希望可以實現線程切換,即從子線程發送的 Message 可以在主線程被執行,反過來也一樣。這個也不難,子線程可以向一個特定的 mainMessageQueue 發送消息,然后讓主線程負責循環從該隊列中取消息並執行即可,這樣不就實現了線程切換了嗎?

所以說,Message 的定義和發送是由 Handler 來完成的,但 Message 的分發則可以交由其他線程來完成

根據以上需求:Runnable 要能夠包裝為 Message 類型,Message 的處理邏輯要交由 Handler 來定義,所以 Message 就還需要增加兩個字段才行

/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC */ public final class Message { //唯一標識 public int what; //數據 public Object obj; //時間戳 public long when; //下一個節點 public Message next; //用於將 Runnable 包裝為 Message public Runnable callback; //指向 Message 的發送者,同時也是 Message 的最終處理者 public Handler target; } 

Handler 至少需要包含幾個方法:用於發送 Message 和 Runnable 的方法、用來處理消息的 handleMessage 方法、用於分發消息的 dispatchMessage方法

/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC */ public class Handler { private MessageQueue mQueue; public Handler(MessageQueue mQueue) { this.mQueue = mQueue; } public final void post(Runnable r) { sendMessageDelayed(getPostMessage(r), 0); } public final void postDelayed(Runnable r, long delayMillis) { sendMessageDelayed(getPostMessage(r), delayMillis); } public final void sendMessage(Message r) { sendMessageDelayed(r, 0); } public final void sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public void sendMessageAtTime(Message msg, long uptimeMillis) { msg.target = this; mQueue.enqueueMessage(msg, uptimeMillis); } private static Message getPostMessage(Runnable r) { Message m = new Message(); m.callback = r; return m; } //由外部來重寫該方法,以此來消費 Message public void handleMessage(Message msg) { } //用於分發消息 public void dispatchMessage(Message msg) { if (msg.callback != null) { msg.callback.run(); } else { handleMessage(msg); } } } 

之后,子線程就可以像這樣來使用 Handler 了:將子線程持有的 Handler 對象和主線程關聯的 mainMessageQueue 綁定在一起,主線程負責循環從 mainMessageQueue 取出 Message 后再來調用 Handler 的 dispatchMessage 方法,以此實現線程切換的目的

        Handler handler = new Handler(mainThreadMessageQueue) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: { String ob = (String) msg.obj; break; } case 2: { List<String> ob = (List<String>) msg.obj; break; } } } }; Message messageA = new Message(); messageA.what = 1; messageA.obj = "https://github.com/leavesC"; Message messageB = new Message(); messageB.what = 2; messageB.obj = new ArrayList<String>(); handler.sendMessage(messageA); handler.sendMessage(messageB); 

4、Looper

現在就再來想想怎么讓 Handler 拿到和主線程關聯的 MessageQueue,以及主線程怎么從 MessageQueue 獲取 Message 並回調 Handler。這之間就一定需要一個中轉器,就叫它 Looper 吧。Looper 具體需要實現什么功能呢?

  • 每個 Looper 對象應該都是對應一個獨有的 MessageQueue 實例和 Thread 實例,這樣子線程和主線程才可以互相發送 Message 交由對方線程處理
  • Looper 內部需要開啟一個無限循環,其關聯的線程就負責從 MessageQueue 循環獲取 Message 進行處理
  • 因為主線程較為特殊,所以和主線程關聯的 Looper 對象要能夠被子線程直接獲取到,可以考慮將其作為靜態變量存着

這樣,Looper 的大體框架就出來了。通過 ThreadLocal 來為不同的線程單獨維護一個 Looper 實例,每個線程通過 prepare()方法來初始化本線程獨有的 Looper 實例 ,再通過 myLooper()方法來獲取和當前線程關聯的 Looper 對象,和主線程關聯的 sMainLooper 作為靜態變量存在,方便子線程獲取

/** * @Author: leavesC * @Date: 2020/12/1 13:31 * @Desc: * GitHub:https://github.com/leavesC */ final class Looper { final MessageQueue mQueue; final Thread mThread; private static Looper sMainLooper; static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private Looper() { mQueue = new MessageQueue(); mThread = Thread.currentThread(); } public static void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); } public static void prepareMainLooper() { prepare(); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } } public static Looper myLooper() { return sThreadLocal.get(); } } 

Looper 還需要有一個用於循環從 MessageQueue 獲取消息並處理的方法,就叫它loop()吧。其作用也能簡單,就是循環從 MessageQueue 中取出 Message,然后將 Message 再反過來分發給 Handler 即可

    public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; for (; ; ) { Message msg = queue.next();//可能會阻塞 msg.target.dispatchMessage(msg); } } 

這樣,主線程就先通過調用prepareMainLooper()來完成 sMainLooper 的初始化,然后調用loop()開始向 mainMessageQueue 循環取值並進行處理,沒有消息的話主線程就暫時休眠着。子線程拿到 sMainLooper 后就以此來初始化 Handler,這樣子線程向 Handler 發送的消息就都會被存到 mainMessageQueue 中,最終在主線程被消費掉

5、做一個總結

這樣一步步走下來后,讀者對於 Message、MessageQueue、Handler、Looper 這四個類的定位就應該都很清晰了吧?不同線程之間就可以依靠拿到對方的 Looper 來實現消息的跨線程處理了

例如,對於以下代碼,即使 Handler 是在 otherThread 中進行初始化,但 handleMessage 方法最終是會在 mainThread 被調用執行的,

        Thread mainThread = new Thread() { @Override public void run() { //初始化 mainLooper Looper.prepareMainLooper(); //開啟循環 Looper.loop(); } }; Thread otherThread = new Thread() { @Override public void run() { Looper mainLooper = Looper.getMainLooper(); Handler handler = new Handler(mainLooper.mQueue) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: { String ob = (String) msg.obj; break; } case 2: { List<String> ob = (List<String>) msg.obj; break; } } } }; Message messageA = new Message(); messageA.what = 1; messageA.obj = "https://github.com/leavesC"; Message messageB = new Message(); messageB.what = 2; messageB.obj = new ArrayList<String>(); handler.sendMessage(messageA); handler.sendMessage(messageB); } }; 

再來做個簡單的總結:

  • Message:用來表示要執行的任務
  • Handler:子線程持有的 Handler 如果綁定到的是主線程的 MessageQueue 的話,那么子線程發送的 Message 就可以由主線程來消費,以此來實現線程切換,執行 UI 更新操作等目的
  • MessageQueue:即消息隊列,通過 Handler 發送的消息並非都是立即執行的,需要先按照 Message 的優先級高低(延時時間的長短)保存到 MessageQueue 中,之后再來依次執行
  • Looper:Looper 用於從 MessageQueue 中循環獲取 Message 並將之傳遞給消息處理者(即消息發送者 Handler 本身)來進行消費,每條 Message 都有個 target 變量用來指向 Handler,以此把 Message 和其處理者關聯起來。不同線程之間通過互相拿到對方的 Looper 對象,以此來實現跨線程發送消息

有了以上的認知基礎后,下面就來看看實際的源碼實現 ~ ~

二、Handler 源碼

1、Handler 如何初始化

Handler 的構造函數一共有七個,除去兩個已經廢棄的和三個隱藏的,實際上開發者可以使用的只有兩個。而不管是使用哪個構造函數,最終的目的都是為了完成 mLooper、mQueue、mCallback、mAsynchronous 這四個常量的初始化,同時也可以看出來 MessageQueue 是由 Looper 來完成初始化的,而且 Handler 對於 Looper 和 MessageQueue 都是一對一的關系,一旦初始化后就不可改變

大部分開發者使用的應該都是 Handler 的無參構造函數,而在 Android 11 中 Handler 的無參構造函數已經被標記為廢棄的了。Google 官方更推薦的做法是通過顯式傳入 Looper 對象來完成初始化,而非隱式使用當前線程關聯的 Looper

Handler 對於 Looper 和 MessageQueue 都是一對一的關系,但是 Looper 和 MessageQueue 對於 Handler 可以是一對多的關系,這個后面會講到

    @UnsupportedAppUsage final Looper mLooper; final MessageQueue mQueue; @UnsupportedAppUsage final Callback mCallback; final boolean mAsynchronous; //省略其它構造函數 /** * @hide */ public Handler(@Nullable Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } 

2、Looper 如何初始化

在初始化 Handler 時,如果外部調用的構造函數沒有傳入 Looper,那就會調用Looper.myLooper()來獲取和當前線程關聯的 Looper 對象,再從 Looper 中取 MessageQueue。如果獲取到的 Looper 對象為 null 就會拋出異常。根據異常信息 Can't create handler inside thread that has not called Looper.prepare() 可以看出來,在初始化 Handler 之前需要先調用 Looper.prepare()完成 Looper 的初始化

走進 Looper 類中可以看到,myLooper()方法是 Looper 類的靜態方法,其只是單純地從 sThreadLocal 變量中取值並返回而已。sThreadLocal 又是通過 prepare(boolean) 方法來進行初始化賦值的,且只能賦值一次,重復調用將拋出異常

我們知道,ThreadLocal 的特性就是可以為不同的線程分別維護單獨的一個變量實例,所以,不同的線程就會分別對應着不同的 Looper 對象,是一一對應的關系

    @UnsupportedAppUsage static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static @Nullable Looper myLooper() { return sThreadLocal.get(); } /** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { //只允許賦值一次 //如果重復賦值則拋出異常 throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } 

此外,Looper 類的構造函數也是私有的,會初始化兩個常量值:mQueue 和 mThread,這說明了 Looper 對於 MessageQueue 和 Thread 都是一一對應的關系,關聯之后不能改變

    @UnsupportedAppUsage final MessageQueue mQueue; final Thread mThread; private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } 

在日常開發中,我們在通過 Handler 來執行 UI 刷新操作時,經常使用的是 Handler 的無參構造函數,那么此時肯定就是使用了和主線程關聯的 Looper 對象,對應 Looper 類中的靜態變量 sMainLooper

    @UnsupportedAppUsage private static Looper sMainLooper; // guarded by Looper.class //被標記為廢棄的原因是因為 sMainLooper 會交由 Android 系統自動來完成初始化,外部不應該主動來初始化 @Deprecated public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } /** * Returns the application's main looper, which lives in the main thread of the application. */ public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } } 

prepareMainLooper()就用於為主線程初始化 Looper 對象,該方法又是由 ActivityThread 類的 main() 方法來調用的。該 main() 方法即 Java 程序的運行起始點,所以當應用啟動時系統就自動為我們在主線程做好了 mainLooper 的初始化,而且已經調用了Looper.loop()方法開啟了消息的循環處理,應用在使用過程中的各種交互邏輯(例如:屏幕的觸摸事件、列表的滑動等)就都是在這個循環里完成分發的

正是因為 Android 系統已經自動完成了主線程 Looper 的初始化,所以我們在主線程中才可以直接使用 Handler 的無參構造函數來完成 UI 相關事件的處理

public final class ActivityThread extends ClientTransactionHandler { public static void main(String[] args) { ··· Looper.prepareMainLooper(); ··· Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } } 

3、Handler 發送消息

Handler 用於發送消息的方法非常多,有十幾個,其中大部分最終調用到的都是 sendMessageAtTime() 方法。uptimeMillis 即 Message 具體要執行的時間戳,如果該時間戳比當前時間大,那么就意味着要執行的是延遲任務。如果為 mQueue 為 null,就會打印異常信息並直接返回,因為 Message 是需要交由 MessageQueue 來處理的

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } 

需要注意 msg.target = this 這句代碼,target 指向了發送消息的主體,即 Handler 對象本身,即由 Handler 對象發給 MessageQueue 的消息最后還是要交由 Handler 對象本身來處理

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } //將消息交由 MessageQueue 處理 return queue.enqueueMessage(msg, uptimeMillis); } 

4、MessageQueue

MessageQueue 通過 enqueueMessage 方法來接收消息

  • 因為存在多個線程同時往一個 MessageQueue 發送消息的可能,所以 enqueueMessage 內部肯定需要進行線程同步
  • 可以看出 MessageQueue 內部是以鏈表的結構來存儲 Message 的(Message.next),根據 Message 的時間戳大小來決定其在消息隊列中的位置
  • mMessages 代表的是消息隊列中的第一條消息。如果 mMessages 為空(消息隊列是空的),或者 mMessages 的時間戳要比新消息的時間戳大,則將新消息插入到消息隊列的頭部;如果 mMessages 不為空,則尋找消息列隊中第一條觸發時間比新消息晚的非空消息,將新消息插到該消息的前面

到此,一個按照時間戳大小進行排序的消息隊列就完成了,后邊要做的就是從消息隊列中依次取出消息進行處理了

    boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } synchronized (this) { ··· msg.markInUse(); msg.when = when; Message p = mMessages; //用於標記是否需要喚醒線程 boolean needWake; //如果鏈表是空的,或者處於隊頭的消息的時間戳比 msg 要大,則將 msg 作為鏈表頭部 //when == 0 說明 Handler 調用的是 sendMessageAtFrontOfQueue 方法,直接將 msg 插到隊列頭部 if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { //如果當前線程處於休眠狀態 + 隊頭消息是屏障消息 + msg 是異步消息 //那么就需要喚醒線程 needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; //從鏈表頭向鏈表尾遍歷,尋找鏈表中第一條時間戳比 msg 大的消息,將 msg 插到該消息的前面 for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { //如果在 msg 之前隊列中還有異步消息那么就不需要主動喚醒 //因為已經設定喚醒時間了 needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; } 

知道了 Message 是如何保存的了,再來看下 MessageQueue 是如何取出 Message 並回調給 Handler 的。在 MessageQueue 中讀取消息的操作對應的是next() 方法。next() 方法內部開啟了一個無限循環,如果消息隊列中沒有消息或者是隊頭消息還沒到可以處理的時間,該方法就會導致 Loop 線程休眠掛起,直到條件滿足后再重新遍歷消息

    @UnsupportedAppUsage Message next() { ··· for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //將 Loop 線程休眠掛起 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { //隊頭消息還未到處理時間,計算需要等待的時間 // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } ··· } ··· } ··· } } 

next() 方法又是通過 Looper 類的 loop() 方法來循環調用的,loop() 方法內也是一個無限循環,唯一跳出循環的條件就是 queue.next()方法返回為 null。因為 next() 方法可能會觸發阻塞操作,所以沒有消息需要處理時也會導致 loop() 方法被阻塞着,而當 MessageQueue 有了新的消息,Looper 就會及時地處理這條消息並調用 msg.target.dispatchMessage(msg) 方法將消息回傳給 Handler 進行處理

    /** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } ··· for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ··· msg.target.dispatchMessage(msg); ··· } } 

Handler 的dispatchMessage方法就是在向外部分發 Message 了。至此,Message 的整個分發流程就結束了

    /** * Handle system messages here. */ public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } 

5、消息屏障

Android 系統為了保證某些高優先級的 Message(異步消息) 能夠被盡快執行,采用了一種消息屏障(Barrier)機制。其大致流程是:先發送一個屏障消息到 MessageQueue 中,當 MessageQueue 遍歷到該屏障消息時,就會判斷當前隊列中是否存在異步消息,有的話則先跳過同步消息(開發者主動發送的都屬於同步消息),優先執行異步消息。這種機制就會使得在異步消息被執行完之前,同步消息都不會得到處理

Handler 的構造函數中的async參數就用於控制發送的 Message 是否屬於異步消息

    public class Handler { final boolean mAsynchronous; public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) { mAsynchronous = async; } private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { //設為異步消息 msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } } 

MessageQueue 在取隊頭消息的時候,如果判斷到隊頭消息就是屏障消息的話,那么就會向后遍歷找到第一條異步消息優先進行處理

    @UnsupportedAppUsage Message next() { for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { //target 為 null 即屬於屏障消息 // Stalled by a barrier. Find the next asynchronous message in the queue. //循環遍歷,找到屏障消息后面的第一條異步消息進行處理 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } } } } 

6、退出 Looper 循環

Looper 類本身做了方法限制,除了主線程外,子線程關聯的 MessageQueue 都支持退出 Loop 循環,即 quitAllowed 只有主線程才能是 false

public final class Looper { private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } } 

MessageQueue 支持兩種方式來退出 Loop:

  • safe 為 true,只移除所有尚未執行的消息,不移除時間戳等於當前時間的消息
  • safe 為 false,移除所有消息
    void quit(boolean safe) { if (!mQuitAllowed) { //MessageQueue 設置了不允許退出循環,直接拋出異常 throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { //避免重復調用 return; } mQuitting = true; if (safe) { //只移除所有尚未執行的消息,不移除時間戳等於當前時間的消息 removeAllFutureMessagesLocked(); } else { //移除所有消息 removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } } 

7、IdleHandler

IdleHandler 是 MessageQueue 的一個內部接口,可以用於在 Loop 線程處於空閑狀態的時候執行一些優先級不高的操作

    public static interface IdleHandler { boolean queueIdle(); } 

MessageQueue 在獲取隊頭消息時,如果發現當前沒有需要執行的 Message 的話,那么就會去遍歷 mIdleHandlers,依次執行 IdleHandler

    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); @UnsupportedAppUsage Message next() { ··· int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { ··· synchronized (this) { ··· //如果隊頭消息 mMessages 為 null 或者 mMessages 需要延遲處理 //那么就來執行 IdleHandler if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { //執行 IdleHandler //如果返回 false 的話說明之后不再需要執行,那就將其移除 keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } } } 

例如,ActivityThread 就向主線程 MessageQueue 添加了一個 GcIdler,用於在主線程空閑時嘗試去執行 GC 操作

public final class ActivityThread extends ClientTransactionHandler { @UnsupportedAppUsage void scheduleGcIdler() { if (!mGcIdlerScheduled) { mGcIdlerScheduled = true; //添加 IdleHandler Looper.myQueue().addIdleHandler(mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); } final class GcIdler implements MessageQueue.IdleHandler { @Override public final boolean queueIdle() { //嘗試 GC doGcIfNeeded(); purgePendingResources(); return false; } } } 

8、做一個總結

再來總結下以上的所有內容

  1. 每個 Handler 都會和一個 Looper 實例關聯在一起,可以在初始化 Handler 時通過構造函數主動傳入實例,否則就會默認使用和當前線程關聯的 Looper 對象

  2. 每個 Looper 都會和一個 MessageQueue 實例關聯在一起,每個線程都需要通過調用 Looper.prepare()方法來初始化本線程獨有的 Looper 實例,並通過調用Looper.loop()方法來使得本線程循環向 MessageQueue 取出消息並執行。Android 系統默認會為每個應用初始化和主線程關聯的 Looper 對象,並且默認就開啟了 loop 循環來處理主線程消息

  3. MessageQueue 按照鏈接結構來保存 Message,執行時間早(即時間戳小)的 Message 會排在鏈表的頭部,Looper 會循環從鏈表中取出 Message 並回調給 Handler,取值的過程可能會包含阻塞操作

  4. Message、Handler、Looper、MessageQueue 這四者就構成了一個生產者和消費者模式。Message 相當於產品,MessageQueue 相當於傳輸管道,Handler 相當於生產者,Looper 相當於消費者

  5. Handler 對於 Looper、Handler 對於 MessageQueue、Looper 對於 MessageQueue、Looper 對於 Thread ,這幾個之間都是一一對應的關系,在關聯后無法更改,但 Looper 對於 Handler、MessageQueue 對於 Handler 可以是一對多的關系

  6. Handler 能用於更新 UI 包含了一個隱性的前提條件:Handler 與主線程 Looper 關聯在了一起。在主線程中初始化的 Handler 會默認與主線程 Looper 關聯在一起,所以其 handleMessage(Message msg) 方法就會由主線程來調用。在子線程初始化的 Handler 如果也想執行 UI 更新操作的話,則需要主動獲取 mainLooper 來初始化 Handler

  7. 對於我們自己在子線程中創建的 Looper,當不再需要的時候我們應該主動退出循環,否則子線程將一直無法得到釋放。對於主線程 Loop 我們則不應該去主動退出,否則將導致應用崩潰

  8. 我們可以通過向 MessageQueue 添加 IdleHandler 的方式,來實現在 Loop 線程處於空閑狀態的時候執行一些優先級不高的任務。例如,假設我們有個需求是希望當主線程完成界面繪制等事件后再執行一些 UI 操作,那么就可以通過 IdleHandler 來實現,這可以避免拖慢用戶看到首屏頁面的速度

三、Handler 在系統中的應用

1、HandlerThread

HandlerThread 是 Android SDK 中和 Handler 在同個包下的一個類,從其名字就可以看出來它是一個線程,而且使用到了 Handler

其用法類似於以下代碼。通過 HandlerThread 內部的 Looper 對象來初始化 Handler,同時在 Handler 中聲明需要執行的耗時任務,主線程通過向 Handler 發送消息來觸發 HandlerThread 去執行耗時任務

class MainActivity : AppCompatActivity() { private val handlerThread = HandlerThread("I am HandlerThread") private val handler by lazy { object : Handler(handlerThread.looper) { override fun handleMessage(msg: Message) { Thread.sleep(2000) Log.e("MainActivity", "這里是子線程,可以用來執行耗時任務:" + Thread.currentThread().name) } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_test.setOnClickListener { handler.sendEmptyMessage(1) } handlerThread.start() } } 

HandlerThread 的源碼還是挺簡單的,只有一百多行

HandlerThread 是 Thread 的子類,其作用就是為了用來執行耗時任務,其 run()方法會自動為自己創建一個 Looper 對象並保存到 mLooper,之后就主動開啟消息循環,這樣 HandlerThread 就會來循環處理 Message 了

public class HandlerThread extends Thread { //線程優先級 int mPriority; //線程ID int mTid = -1; //當前線程持有的 Looper 對象 Looper mLooper; private @Nullable Handler mHandler; public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } public HandlerThread(String name, int priority) { super(name); mPriority = priority; } @Override public void run() { mTid = Process.myTid(); //觸發當前線程創建 Looper 對象 Looper.prepare(); synchronized (this) { //獲取 Looper 對象 mLooper = Looper.myLooper(); //喚醒所有處於等待狀態的線程 notifyAll(); } //設置線程優先級 Process.setThreadPriority(mPriority); onLooperPrepared(); //開啟消息循環 Looper.loop(); mTid = -1; } } 

此外,HandlerThread 還包含一個getLooper()方法用於獲取 Looper。當我們在外部調用handlerThread.start()啟動線程后,由於其run()方法的執行時機依然是不確定的,所以 getLooper()方法就必須等到 Looper 初始化完畢后才能返回,否則就會由於wait()方法而一直阻塞等待。當run()方法初始化 Looper 完成后,就會調用notifyAll()來喚醒所有處於等待狀態的線程。所以外部在使用 HandlerThread 前就記得必須先調用 start() 方法來啟動 HandlerThread

    //獲取與 HandlerThread 關聯的 Looper 對象 //因為 getLooper() 可能先於 run() 被執行 //所以當 mLooper 為 null 時調用者線程就需要阻塞等待 Looper 對象創建完畢 public Looper getLooper() { if (!isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; } 

HandlerThread 起到的作用就是方便了主線程和子線程之間的交互,主線程可以直接通過 Handler 來聲明耗時任務並交由子線程來執行。使用 HandlerThread 也方便在多個線程間共享,主線程和其它子線程都可以向 HandlerThread 下發任務,且 HandlerThread 可以保證多個任務執行時的有序性

2、IntentService

IntentService 是系統提供的 Service 子類,用於在后台串行執行耗時任務,在處理完所有任務后會自動停止,不必來手動調用 stopSelf() 方法。而且由於IntentService 是四大組件之一,擁有較高的優先級,不易被系統殺死,因此適合用於執行一些高優先級的異步任務

Google 官方以前也推薦開發者使用 IntentService,但是在 Android 11 中已經被標記為廢棄狀態了,但這也不妨礙我們來了解下其實現原理

IntentService 內部依靠 HandlerThread 來實現,其 onCreate()方法會創建一個 HandlerThread,拿到 Looper 對象來初始化 ServiceHandler。ServiceHandler 會將其接受到的每個 Message 都轉交由抽象方法 onHandleIntent來處理,子類就通過實現該方法來聲明耗時任務

public abstract class IntentService extends Service { private volatile Looper mServiceLooper; @UnsupportedAppUsage private volatile ServiceHandler mServiceHandler; private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } @Override public void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); //觸發 HandlerThread 創建 Looper 對象 thread.start(); //獲取 Looper 對象,構建可以向 HandlerThread 發送 Message 的 Handler mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @WorkerThread protected abstract void onHandleIntent(@Nullable Intent intent); } 

每次 start IntentService 時,onStart()方法就會被調用,將 intent 和 startId 包裝為一個 Message 對象后發送給mServiceHandler。需要特別注意的是 startId 這個參數,它用於唯一標識每次對 IntentService 發起的任務請求,每次回調 onStart() 方法時,startId 的值都是自動遞增的。IntentService 不應該在處理完一個 Message 之后就立即停止 IntentService,因為此時 MessageQueue 中可能還有待處理的任務還未取出來,所以如果當調用 stopSelf(int)方法時傳入的參數不等於當前最新的 startId 值的話,那么stopSelf(int) 方法就不會導致 IntentService 被停止,從而避免了將尚未處理的 Message 給遺漏了

    @Override public void onStart(@Nullable Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); } @Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; } 

四、Handler 在三方庫中的應用

1、EventBus

EventBus 的 Github 上有這么一句介紹:EventBus is a publish/subscribe event bus for Android and Java. 這說明了 EventBus 是普遍適用於 Java 環境的,只是對 Android 系統做了特殊的平台支持而已。EventBus 的四種消息發送策略包含了ThreadMode.MAIN 用於指定在主線程進行消息回調,其內部就是通過 Handler 來實現的

EventBusBuilder 會去嘗試獲取 MainLooper,如果拿得到的話就可以用來初始化 HandlerPoster,從而實現主線程回調

    MainThreadSupport getMainThreadSupport() { if (mainThreadSupport != null) { return mainThreadSupport; } else if (AndroidLogger.isAndroidLogAvailable()) { Object looperOrNull = getAndroidMainLooperOrNull(); return looperOrNull == null ? null : new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull); } else { return null; } } static Object getAndroidMainLooperOrNull() { try { return Looper.getMainLooper(); } catch (RuntimeException e) { // Not really a functional Android (e.g. "Stub!" maven dependencies) return null; } } 
public class HandlerPoster extends Handler implements Poster { protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) { super(looper); } @Override public void handleMessage(Message msg) { } } 

2、Retrofit

和 EventBus 一樣,Retrofit 的內部實現也不需要依賴於 Android 平台,而是可以用於任意的 Java 客戶端,Retrofit 只是對 Android 平台進行了特殊實現而已

在構建 Retrofit 對象的時候,我們可以選擇傳遞一個 Platform 對象用於標記調用方所處的平台

public static final class Builder { private final Platform platform; Builder(Platform platform) { this.platform = platform; } } 

Platform 類只具有一個唯一子類,即 Android 類。其主要邏輯就是重寫了父類的 defaultCallbackExecutor()方法,通過 Handler 來實現在主線程回調網絡請求結果

static final class Android extends Platform { @Override public Executor defaultCallbackExecutor() { return new MainThreadExecutor(); } static final class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) { handler.post(r); } } } 

五、面試環節

1.Handler

  • Handler Looper Message 關系是什么?

  • Messagequeue 的數據結構是什么?為什么要用這個數據結構?

  • 如何在子線程中創建 Handler?

  • Handler post 方法原理?

  • Android消息機制的原理及源碼解析

  • Android Handler 消息機制

由於篇幅有限,僅展示部分內容,所有的知識點 整理的詳細內容都放在了我的【GitHub】,有需要的朋友自取。

2.Activity 相關

  • 啟動模式以及使用場景?

  • onNewIntent()與onConfigurationChanged()

  • onSaveInstanceState()與onRestoreInstanceState()

  • Activity 到底是如何啟動的

  • 啟動模式以及使用場景

  • onSaveInstanceState及onRestoreInstanceState使用

  • onConfigurationChanged使用以及問題解決

  • Activity 啟動流程解析

3.Fragment

  • Fragment 生命周期和 Activity 對比

  • Fragment 之間如何進行通信

  • Fragment的startActivityForResult

  • Fragment重疊問題

  • Fragment 初探

  • Fragment 重疊, 如何通信

  • Fragment生命周期

由於篇幅有限,僅展示部分內容,所有的知識點 整理的詳細內容都放在了我的【GitHub】,有需要的朋友自取。

4.Service 相關

  • 進程保活

  • Service的運行線程(生命周期方法全部在主線程)

  • Service啟動方式以及如何停止

  • ServiceConnection里面的回調方法運行在哪個線程?

  • startService 和 bingService區別

  • 進程保活一般套路

  • 關於進程保活你需要知道的一切

5.Android布局優化

  • 什么情況下使用 ViewStub、include、merge?

  • 他們的原理是什么?

  • ViewStub、include、merge概念解析

  • Android布局優化之ViewStub、include、merge使用與源碼分析

6.BroadcastReceiver 相關

  • 注冊方式,優先級

  • 廣播類型,區別

  • 廣播的使用場景,原理

  • Android廣播動態靜態注冊

  • 常見使用以及流程解析

  • 廣播源碼解析

7.AsyncTask相關

  • AsyncTask是串行還是並行執行?

  • AsyncTask隨着安卓版本的變遷

  • AsyncTask完全解析

  • 串行還是並行

8.Android 事件分發機制

  • onTouch和onTouchEvent區別,調用順序

  • dispatchTouchEvent,onTouchEvent,onInterceptTouchEvent 方法順序以及使用場景

  • 滑動沖突,如何解決

  • 事件分發機制

  • 事件分發解析

  • dispatchTouchEvent,onTouchEvent,onInterceptTouchEvent方法的使用場景解析

由於篇幅有限,僅展示部分內容,所有的知識點 整理的詳細內容都放在了我的【GitHub】,有需要的朋友自取。

對於Android開發的朋友來說應該是非常完整的面試資料了,為了更好地整理每個模塊,我參考了很多網上的優質博文和項目,力求不漏掉每一個知識點。很多朋友靠着這些內容進行復習,拿到了BATJ等大廠的offer,這個資料也已經幫助了很多的安卓開發者,希望也能幫助到你。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM