回轉壽司你一定吃過!——Android訊息機制(構造)

Taylor發表於2019-03-04

這是“Android訊息機制”系列的第一篇文章,系列文章目錄如下:

  1. 回轉壽司你一定吃過!——Android訊息機制(構造)
  2. 回轉壽司你一定吃過!——Android訊息機制(分發)
  3. 回轉壽司你一定吃過!——Android訊息機制(處理)

訊息機制的故事

壽司陳放在壽司碟上,壽司碟按先後順序被排成佇列送上傳送帶傳送帶被啟動後,壽司挨個呈現到你面前,你有三種享用壽司的方法。

將Android概念帶入後,就變成了Android訊息機制的故事:

  • 壽司碟 ---> 訊息(Message)
  • 佇列 ---> 訊息佇列(MessageQueue)
  • 傳送帶 ---> 訊息泵 (Looper)
  • 壽司 ---> 你關心的資料
  • 享用壽司方法 ---> 處理資料方式

暫未找到 Handler 在此場景中對應的實體。它是一個更抽象的概念,它即可以生產壽司,又把壽司送上傳送帶,還定義了怎麼享用壽司。暫且稱它為訊息處理器吧。

如果打算自己開一家回轉壽司店,下面的問題很關鍵:

  1. 如何生產壽司(如何構造訊息)
  2. 如何分發壽司(如何分發訊息)

讓我們帶著這兩個問題,去分析一下訊息機制原始碼。 (ps: 下文中的 粗斜體字 表示引導原始碼閱讀的內心戲)

如何構造訊息

壽司碟是重複利用的,享用完壽司後,它被清洗,然後被存放起來,以便再次利用。沒有哪個老闆會在用餐後把壽司碟銷燬,下次要用就買新的,這樣代價太大。 同樣道理,構造訊息物件代價也很大,它是否也像壽司碟一樣可以複用?如果是,那訊息存放在哪裡? 讓我們以Handler.obtainMessage()為切入點一探究竟:

public class Handler {
   ...
   public final Message obtainMessage(){
       return Message.obtain(this);
   }
   ...
}

public final class Message implements Parcelable {
  ...
  /**
    * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
    * @param h  Handler to assign to the returned Message object's <em>target</em> member.
    * @return A Message object from the global pool.
    */
  public static Message obtain(Handler h) {
     Message m = obtain();
     m.target = h;
     return m;
  }
  ...
}
複製程式碼
  • 其中Message.target是一個Handler型別的成員變數。為啥Message要記錄構造它的Handler物件? 好問題!但這個問題的解答需要等到分析訊息處理的時候才能解答,先留個懸念。
  • 構造訊息呼叫鏈的終點是Message.obtain(),原始碼如下:
public final class Message implements Parcelable {
    //省略了非關鍵程式碼
    ...
    // sometimes we store linked lists of these things
    //指向訊息鏈上下一個訊息的引用
    /*package*/ Message next;

    //訊息鏈頭部引用,它是靜態的,可以被所有訊息物件共享
    private static Message sPool;
    //訊息鏈長度
    private static int sPoolSize = 0;

    ...
    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                //1. 定義指向訊息鏈頭部引用
                Message m = sPool;
                //2. 定義新的訊息鏈頭部
                sPool = m.next;
                //3. 斷鏈
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                //返回訊息鏈頭部訊息
                return m;
            }
        }
        //如果訊息鏈為空則新建訊息
        return new Message();
    }
    ...
}
複製程式碼
  • 如果對資料結構中的連結串列還有映像,obtain()就是在取連結串列頭。圖示如下:
    1

2

3

  • 訊息池是用連結串列結構實現的。那 Message一定有一個指向後續結點的“指標” ,果不其然,在其成員變數中找到Message next;

  • 訊息池頭指標sPool是一個Message型別的靜態變數,這表示所有Message都共享這一個訊息池。

  • obtain()是從訊息池中拿訊息,那一定還有一個方法是往池裡填訊息,在Message類中搜尋sPool使用的地方,找到如下這個方法:

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    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 = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            //限制訊息池大小
            if (sPoolSize < MAX_POOL_SIZE) {
                //1. 回收的訊息接入訊息鏈
                next = sPool;
                //2. 回收的訊息成為訊息鏈新頭部
                sPool = this;
                sPoolSize++;
            }
        }
    }
複製程式碼
  • 正如猜想的那樣recycleUnchecked()會將當前訊息插入到訊息鏈頭部。圖示如下
    1

2

  • 讀到這裡,我們知道“訊息從池中來最終又回到池中去”,那到底訊息是在什麼時候才會被回收到訊息池呢? 好問題!這個問題要等分析完訊息分發才能解答。但現在我們可以大膽的猜測一下:承載壽司的碟子會在壽司被享用完之後被廚房回收,那訊息是不是再被處理完之後就被回收了?

總結

Android訊息機制中的“構造訊息”部分講完了,總結一下:訊息的生命週期會經歷“建立-回收-再利用”,所有訊息共享一個連結串列結構的訊息池,它用於存放被回收的訊息。

故事還沒有結束,下一篇會繼續講解分發訊息

相關文章