一文讀懂 Handler 機制全家桶

Handler 在整個 Android 開發體系中佔據着很重要的地位,對開發者來說起到的作用很明確,就是爲了實現線程切換或者是執行延時任務,稍微更高級一點的用法可能是爲了保證多個任務在執行時的有序性。由於 Android 系統中的主線程有特殊地位,所以像 EventBus 和 Retrofit 這類並非 Android 獨有的三方庫,都是通過 Handler 來實現對 Android 系統的特殊平臺支持。大部分開發者都已經對如何使用 Handler 很熟悉了,這裏就再來了解下其內部具體是如何實現的

本文基於 Android API 30(即 Android 11)的系統源碼進行講解

一、動手實現 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()方法就會被調用,將 intentstartId 包裝爲一個 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、Looper、MessageQueue、Thread 的對應關係

首先,Looper 中的 MessageQueue 和 Thread 兩個字段都屬於常量,且 Looper 實例是存在 ThreadLocal 中,這說明了 Looper 和 MessageQueue 之間是一對一應的關係,且一個 Thread 在其整個生命週期內都只會關聯到同一個 Looper 對象和同一個 MessageQueue 對象

public final class Looper {
 
   final MessageQueue mQueue;
   final Thread mThread;
   static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
   private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
}

Handler 中的 Looper 和 MessageQueue 兩個字段也都屬於常量,說明 Handler 對於 Looper 和 MessageQueue 都是一對一的關係。但是 Looper 和 MessageQueue 對於 Handler 卻可以是一對多的關係,例如,多個子線程內聲明的 Handler 都可以關聯到 mainLooper

public class Handler {
    
    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    
}

2、Handler 的同步機制

MessageQueue 在保存 Message 的時候,enqueueMessage方法內部已經加上了同步鎖,從而避免了多個線程同時發送消息導致競態問題。此外,next()方法內部也加上了同步鎖,所以也保障了 Looper 分發 Message 的有序性。最重要的一點是,Looper 總是由一個特定的線程來執行遍歷,所以在消費 Message 的時候也不存在競態

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            ···
        }
        return true;
    }


    @UnsupportedAppUsage
    Message next() {
        ···
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                ···
            }

            ···
        }
    }

3、Handler 如何發送同步消息

如果我們在子線程通過 Handler 向主線程發送了一個消息,希望等到消息執行完畢後子線程才繼續運行,這該如何實現?其實像這種涉及到多線程同步等待的問題,往往都是需要依賴於線程休眠+線程喚醒機制來實現的

Handler 本身就提供了一個runWithScissors方法可以用於實現這種功能,只是被隱藏了,我們無法直接調用到。runWithScissors首先會判斷目標線程是否就是當前線程,是的話則直接執行 Runnable,否則就需要使用到 BlockingRunnable

    /**
     * @hide
     */
    public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
        if (r == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout must be non-negative");
        }

        if (Looper.myLooper() == mLooper) {
            r.run();
            return true;
        }

        BlockingRunnable br = new BlockingRunnable(r);
        return br.postAndWait(this, timeout);
    }

BlockingRunnable 的邏輯也很簡單,在 Runnable 執行完前會通過調用 wait()方法來使發送者線程轉爲阻塞等待狀態,當任務執行完畢後再通過notifyAll()來喚醒發送者線程,從而實現了在 Runnable 被執行完之前發送者線程都會一直處於等待狀態

private static final class BlockingRunnable implements Runnable {
    
        private final Runnable mTask;
        //用於標記 mTask 是否已經執行完畢 
        private boolean mDone;

        public BlockingRunnable(Runnable task) {
            mTask = task;
        }

        @Override
        public void run() {
            try {
                mTask.run();
            } finally {
                synchronized (this) {
                    mDone = true;
                    notifyAll();
                }
            }
        }

        public boolean postAndWait(Handler handler, long timeout) {
            if (!handler.post(this)) {
                return false;
            }

            synchronized (this) {
                if (timeout > 0) {
                    final long expirationTime = SystemClock.uptimeMillis() + timeout;
                    while (!mDone) {
                        long delay = expirationTime - SystemClock.uptimeMillis();
                        if (delay <= 0) {
                            return false; // timeout
                        }
                        try {
                            //限時等待
                            wait(delay);
                        } catch (InterruptedException ex) {
                        }
                    }
                } else {
                    while (!mDone) {
                        try {
                            //無限期等待
                            wait();
                        } catch (InterruptedException ex) {
                        }
                    }
                }
            }
            return true;
        }
    }

雖然 runWithScissors 方法我們無法直接調用,但是我們也可以依靠這思路自己來實現 BlockingRunnable,折中實現這個功能。但這種方式並不安全,如果 Loop 意外退出循環導致該 Runnable 無法被執行的話,就會導致被暫停的線程一直無法被喚醒,需要謹慎使用

4、Handler 如何避免內存泄漏

當退出 Activity 時,如果 Handler 中還保存着待處理的延時消息的話,那麼就會導致內存泄漏,此時可以通過調用Handler.removeCallbacksAndMessages(null)來移除所有待處理的 Message

該方法會將消息隊列中所有 Message.obj 等於 token 的 Message 均給移除掉,如果 token 爲 null 的話則會移除所有 Message

    public final void removeCallbacksAndMessages(@Nullable Object token) {
        mQueue.removeCallbacksAndMessages(this, token);
    }

5、Message 如何複用

因爲 Android 系統本身就存在很多事件需要交由 Message 來交付給 mainLooper,所以 Message 的創建是很頻繁的。爲了減少 Message 頻繁重複創建的情況,Message 提供了 MessagePool 用於實現 Message 的緩存複用,以此來優化內存使用

當 Looper 消費了 Message 後會調用recycleUnchecked()方法將 Message 進行回收,在清除了各項資源後會緩存到 sPool 變量上,同時將之前緩存的 Message 置爲下一個節點 next,通過這種鏈表結構來緩存最多 50 個Message。這裏使用到的是享元設計模式

obtain()方法則會判斷當前是否有可用的緩存,有的話則將 sPool 從鏈表中移除後返回,否則就返回一個新的 Message 實例。所以我們在發送消息的時候應該儘量通過調用Message.obtain()或者Handler.obtainMessage()方法來獲取 Message 實例

public final class Message implements Parcelable {
    
    /** @hide */
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;
    
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    
    @UnsupportedAppUsage
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
    
}

6、Message 複用機制存在的問題

由於 Message 採用了緩存複用機制,從而導致了一個 Message 失效問題。當 handleMessage 方法被回調後,Message 攜帶的所有參數都會被清空,而如果外部的 handleMessage方法是使用了異步線程來處理 Message 的話,那麼異步線程只會得到一個空白的 Message

val handler = object : Handler() {
    override fun handleMessage(msg: Message) {
        handleMessageAsync(msg)
    }
}

fun handleMessageAsync(msg: Message) {
    thread {
        //只會得到一個空白的 Message 對象
        println(msg.obj)
    }
}

7、Message 如何提高優先級

Handler 包含一個 sendMessageAtFrontOfQueue方法可以用於提高 Message 的處理優先級。該方法爲 Message 設定的時間戳是 0,使得 Message 可以直接插入到 MessageQueue 的頭部,從而做到優先處理。但官方並不推薦使用這個方法,因爲最極端的情況下可能會使得其它 Message 一直得不到處理或者其它意想不到的情況

    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
        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, 0);
    }

8、檢測 Looper 分發 Message 的效率

Looper 在進行 Loop 循環時,會通過 Observer 向外回調每個 Message 的回調事件。且如果設定了 slowDispatchThresholdMsslowDeliveryThresholdMs 這兩個閾值的話,則會對 Message 的分發時機分發耗時進行監測,存在異常情況的話就會打印 Log。該機制可以用於實現應用性能監測,發現潛在的 Message 處理異常情況,但可惜監測方法被系統隱藏了

    public static void loop() {
        final Looper me = myLooper();
        ···
        for (;;) {
            Message msg = queue.next(); // might block
            ···
            //用於向外回調通知 Message 的分發事件
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            //如果Looper分發Message的時間晚於預定時間且超出這個閾值,則認爲Looper分發過慢
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            //如果向外分發出去的Message的處理時間超出這個閾值,則認爲外部處理過慢
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            //開始分發 Message 的時間
            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            //Message 分發結束的時間
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                //開始分發 Message 
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    //完成 Message 的分發,且沒有拋出異常
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    //分發 Message 時拋出了異常
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        //如果 Message 的分發時間晚於預定時間,且間隔超出10毫秒,則認爲屬於延遲交付
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            ···
        }
    }

9、主線程 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");
    }
    
}

10、主線程 Looper 什麼時候退出循環

當 ActivityThread 內部的 Handler 收到了 EXIT_APPLICATION 消息後,就會退出 Looper 循環

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case EXIT_APPLICATION:
                    if (mInitialApplication != null) {
                        mInitialApplication.onTerminate();
                    }
                    Looper.myLooper().quit();
                    break;
            }
        }

11、主線程 Looper.loop() 爲什麼不會導致 ANR

這個問題在網上很常見,我第一次看到時就覺得這種問題很奇怪,主線程憑啥會 ANR?這個問題感覺本身就是特意爲了來誤導人

看以下例子。doSomeThing()方法是放在 for 循環這個死循環的後邊,對於該方法來說,主線程的確是被阻塞住了,導致該方法一直無法得到執行。可是對於應用來說,應用在主線程內的所有操作其實都是被放在了 for 循環之內,一直有得到執行,是個死循環也無所謂,所以對於應用來說主線程並沒有被阻塞,自然不會導致 ANR。此外,當 MessageQueue 中當前沒有消息需要處理時,也會依靠 epoll 機制掛起主線程,避免了其一直佔用 CPU 資源

    public static void main(String[] args) {
        for (; ; ) {
            //主線程執行....
        }
        doSomeThing();
    }

所以在 ActivityThread 的 main 方法中,在開啓了消息循環之後,並沒有聲明什麼有意義的代碼。正常來說應用是不會退出 loop 循環的,如果能夠跳出循環,也只會導致直接就拋出異常

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

所以說,loop 循環本身不會導致 ANR,會出現 ANR 是因爲在 loop 循環之內 Message 處理時間過長

12、子線程一定無法彈 Toast 嗎

不一定,只能說是在子線程中無法直接彈出 Toast,但可以實現。因爲 Toast 的構造函數中會要求拿到一個 Looper 對象,如果構造參數沒有傳入不爲 null 的 Looper 實例的話,則嘗試使用調用者線程關聯的 Looper 對象,如果都獲取不到的話則會拋出異常

    public Toast(Context context) {
        this(context, null);
    }

    public Toast(@NonNull Context context, @Nullable Looper looper) {
        mContext = context;
        mToken = new Binder();
        looper = getLooper(looper);
        mHandler = new Handler(looper);
        ···
    }

    private Looper getLooper(@Nullable Looper looper) {
        if (looper != null) {
            return looper;
        }
        //Looper.myLooper() 爲 null 的話就會直接拋出異常
        return checkNotNull(Looper.myLooper(),
                "Can't toast on a thread that has not called Looper.prepare()");
    }

爲了在子線程彈 Toast,就需要主動爲子線程創建 Looper 對象及開啓 loop 循環。但這種方法會導致子線程一直無法退出循環,需要通過Looper.myLooper().quit()來主動退出循環

    inner class TestThread : Thread() {

        override fun run() {
            Looper.prepare()
            Toast.makeText(
                this@MainActivity,
                "Hello: " + Thread.currentThread().name,
                Toast.LENGTH_SHORT
            ).show()
            Looper.loop()
        }

    }

13、子線程一定無法更新 UI?主線程就一定可以?

在子線程能夠彈出 Toast 就已經說明了子線程也是可以更新 UI 的,Android 系統只是限制了必須在同個線程內進行 ViewRootImpl 的創建和更新這兩個操作,而不是要求必須在主線程進行

如果使用不當的話,即使在主線程更新 UI 也可能會導致應用崩潰。例如,在子線程先通過 show+hide 來觸發 ViewRootImpl 的創建,然後在主線程再來嘗試顯示該 Dialog,此時就會發現程序直接崩潰了

class MainActivity : AppCompatActivity() {

    private lateinit var alertDialog: AlertDialog

    private val thread = object : Thread("hello") {
        override fun run() {
            Looper.prepare()
            Handler().post {
                alertDialog =
                    AlertDialog.Builder(this@MainActivity).setMessage(Thread.currentThread().name)
                        .create()
                alertDialog.show()
                alertDialog.hide()
            }
            Looper.loop()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn_test.setOnClickListener {
            alertDialog.show()
        }
        thread.start()
    }

}

    E/AndroidRuntime: FATAL EXCEPTION: main
    Process: github.leavesc.test, PID: 5243
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6892)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048)
        at android.view.View.requestLayout(View.java:19781)
        at android.view.View.setFlags(View.java:11478)
        at android.view.View.setVisibility(View.java:8069)
        at android.app.Dialog.show(Dialog.java:293)

ViewRootImpl 在初始化的時候會將當前線程保存到 mThread,在後續進行 UI 更新的時候就會調用checkThread()方法進行線程檢查,如果發現存在多線程調用則直接拋出以上的異常信息

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
            
     final Thread mThread;       
            
     public ViewRootImpl(Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
        mThread = Thread.currentThread();
        ···
    }       
            
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
            
}

14、爲什麼 UI 體系要採用單線程模型

其實這很好理解,就是爲了提高運行效率和降低實現難度。如果允許多線程併發訪問 UI 的話,爲了避免競態,很多即使只是小範圍的局部刷新操作(例如,TextView.setText)都勢必需要加上同步鎖,這無疑會加大 UI 刷新操作的“成本”,降低了整個應用的運行效率。而且會導致 Android 的 UI 體系在實現時就被迫需要對多線程環境進行“防禦”,即使開發者一直是使用同個線程來更新 UI,這就加大了系統的實現難度

所以,最爲簡單高效的方式就是採用單線程模型來訪問 UI

15、如何跨線程下發任務

通常情況下,兩個線程之間的通信是比較麻煩的,需要做很多線程同步操作。而依靠 Looper 的特性,我們就可以用比較簡單的方式來實現跨線程下發任務

看以下代碼,從 TestThread 運行後彈出的線程名可以知道, Toast 是在 Thread_1 被彈出來的。如果將 Thread_2 想像成主線程的話,那麼以下代碼就相當於從主線程向子線程下發耗時任務了,這個實現思路就相當於 Android 提供的 HandlerThread 類

    inner class TestThread : Thread("Thread_1") {

        override fun run() {
            Looper.prepare()
            val looper = Looper.myLooper()
            object : Thread("Thread_2") {
                override fun run() {
                    val handler = Handler(looper!!)
                    handler.post {
                        //輸出結果是:Thread_1
                        Toast.makeText(
                            this@MainActivity,
                            Thread.currentThread().name,
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
            }.start()
            Looper.loop()
        }

    }

16、如何判斷當前是不是主線程

通過 Looper 來判斷

        if (Looper.myLooper() == Looper.getMainLooper()) {
            //是主線程
        }

        if (Looper.getMainLooper().isCurrentThread){
            //是主線程
        }

17、如何全局捕獲主線程異常

比較臥槽的一個做法就是通過嵌套 Loop 循環來實現。向主線程 Loop 發送 一個 Runnable,在 Runnable 裏死循環執行 Loop 循環,這就會使得主線程消息隊列中的所有任務都會被交由該 Runnable 來調用,只要加上 try catch 後就可以捕獲主線程的任意異常了,做到主線程永不崩潰

        Handler(Looper.getMainLooper()).post {
            while (true) {
                try {
                    Looper.loop()
                } catch (throwable: Throwable) {
                    throwable.printStackTrace()
                    Log.e("TAG", throwable.message ?: "")
                }
            }
        }

六、文章推薦

三方庫源碼筆記(1)-EventBus 源碼詳解

三方庫源碼筆記(2)-EventBus 自己實現一個?

三方庫源碼筆記(3)-ARouter 源碼詳解

三方庫源碼筆記(4)-ARouter 自己實現一個?

三方庫源碼筆記(5)-LeakCanary 源碼詳解

三方庫源碼筆記(6)-LeakCanary 擴展閱讀

三方庫源碼筆記(7)-超詳細的 Retrofit 源碼解析

三方庫源碼筆記(8)-Retrofit 與 LiveData 的結合使用

三方庫源碼筆記(9)-超詳細的 Glide 源碼詳解

三方庫源碼筆記(10)-Glide 你可能不知道的知識點

三方庫源碼筆記(11)-OkHttp 源碼詳解

三方庫源碼筆記(12)-OkHttp / Retrofit 開發調試利器

三方庫源碼筆記(13)-可能是全網第一篇 Coil 的源碼分析文章

一個人走得快,一羣人走得遠,寫了文章就只有自己看那得有多孤單,只希望對你有所幫助😂😂😂

查看更多文章請點擊關注:字節數組

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章