Android Handler機制5之Message簡介與消息對象對象池

Android Handler機制系列文章整體內容如下:

本片文章的主要內容下:

  • 1、Message和MessageQueue類注釋
  • 2、獲取Message成員變量解析
  • 3、獲取Message對象
  • 4、Message的消息對象池和無參的obtain()方法
  • 5、obtain()有參函數解析
  • 6、Message的 淺拷貝

一、 Message和MessageQueue類注釋

為了讓大家更好的理解谷歌團隊設計這個兩個類Message和MessageQueue的意圖,我們還是從這個兩個類的類注釋開始

(一) Message.java

Message

Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.
While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.

翻譯一下:

定義一個可以發送給Handler的描述和任意數據對象的消息。此對象包含兩個額外的int字段和一個額外的對象字段,這樣就可以使用在很多情況下不用做分配工作。
盡管Message的構造器是公開的,但是獲取Message對象的最好方法是調用Message.obtain()或者Handler.obtainMessage(),這樣是從一個可回收的對象池中獲取Message對象。

至此Java層面Handler機制中最重要的四個類大家有了一個初步印象。下面咱們源碼跟蹤一下

二、獲取Message成員變量解析

(一) 成員變量 what

Message用一個標志來區分不同消息的身份,不同的Handler使用相同的消息不會弄混,一般使用16進制形式來表示,閱讀起來比較容易

代碼在Message.java 39行

    /**
     * User-defined message code so that the recipient can identify 
     * what this message is about. Each {@link Handler} has its own name-space
     * for message codes, so you do not need to worry about yours conflicting
     * with other handlers.
     */
    public int what;

注釋翻譯:

用戶定義的Message的標識符用以分辨消息的內容。Hander擁有自己的消息代碼的命名空間,因此你不用擔心與其他的Handler沖突。

(二) 成員變量 arg1和arg2

arg1和arg2都是Message類的可選變量,可以用來存放兩個整數值,不用訪問obj對象就能讀取的變量。

代碼在Message.java 46行

    /**
     * arg1 and arg2 are lower-cost alternatives to using
     * {@link #setData(Bundle) setData()} if you only need to store a
     * few integer values.
     */
    public int arg1; 

    /**
     * arg1 and arg2 are lower-cost alternatives to using
     * {@link #setData(Bundle) setData()} if you only need to store a
     * few integer values.
     */
    public int arg2;

因為兩個注釋一樣,我就不重復翻譯了,翻譯如下:

如果你僅僅是保存幾個整形的數值,相對于使用setData()方法,使用arg1和arg2是較低成本的替代方案。

(三) 成員變量 obj

obj 用來保存對象,接受纖細后取出獲得傳送的對象。

代碼在Message.java 65行

    /**
     * An arbitrary object to send to the recipient.  When using
     * {@link Messenger} to send the message across processes this can only
     * be non-null if it contains a Parcelable of a framework class (not one
     * implemented by the application).   For other data transfer use
     * {@link #setData}.
     * 
     * <p>Note that Parcelable objects here are not supported prior to
     * the {@link android.os.Build.VERSION_CODES#FROYO} release.
     */
    public Object obj;

注釋翻譯:

  • 將一個獨立的對象發送給接收者。當使用Messenger去送法消息,并且這個對象包含Parcelable類的時候,它必須是非空的。對于其他數據的傳輸,建議使用setData()方法
  • 請注意,在Android系統版本FROYO(2.2)之前不支持Parcelable對象。

(四) 其他成員變量

其他變量我就不一一解釋了,大家就直接看注釋吧

    // 回復跨進程的Messenger 
    public Messenger replyTo;

    // Messager發送這的Uid
    public int sendingUid = -1;

    // 正在使用的標志值 表示當前Message 正處于使用狀態,當Message處于消息隊列中、處于消息池中或者Handler正在處理Message的時候,它就處于使用狀態。
    /*package*/ static final int FLAG_IN_USE = 1 << 0;

    // 異步標志值 表示當前Message是異步的。
    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;

    // 消息標志值 在調用copyFrom()方法時,該常量將會被設置,其值其實和FLAG_IN_USE一樣
    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;

    // 消息標志,上面三個常量 FLAG 用在這里
    /*package*/ int flags;

    // 用于存儲發送消息的時間點,以毫秒為單位
    /*package*/ long when;

    // 用于存儲比較復雜的數據
    /*package*/ Bundle data;
    
    // 用于存儲發送當前Message的Handler對象,前面提到過Handler其實和Message相互持有引用的
    /*package*/ Handler target;
    
    // 用于存儲將會執行的Runnable對象,前面提到過除了handlerMessage(Message msg)方法,你也可以使用Runnable執行操作,要注意的是這種方法并不會創建新的線程。
    /*package*/ Runnable callback;
 
    // 指向下一個Message,也就是線程池其實是一個鏈表結構
    /*package*/ Message next;

    // 該靜態變量僅僅是為了給同步塊提供一個鎖而已
    private static final Object sPoolSync = new Object();

    //該靜態的Message是整個線程池鏈表的頭部,通過它才能夠逐個取出對象池的Message
    private static Message sPool;

    // 該靜態變量用于記錄對象池中的Message的數量,也就是鏈表的長度
    private static int sPoolSize = 0;
  
    // 設置了對象池中的Message的最大數量,也就是鏈表的最大長度
    private static final int MAX_POOL_SIZE = 50;

     //該版本系統是否支持回收標志位
    private static boolean gCheckRecycle = true;

三、獲取Message對象

(一)、Message構造函數

如果想獲取Message對象,大家第一印象肯定是找Message的構造函數,那我們就來看下Message的構造函數。代碼在Message.java 475行

    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }

發現代碼里面什么都沒有

那我們看下注釋,簡單翻譯一下:

構造函數,但是獲取Message的首選方法是通過Message.obtain()來調用

其實在上面解釋Message的注釋時也是這樣說的,說明Android官方團隊是推薦使用Message.obtain()方法來獲取Message對象的,那我們就來看下Message.obtain()

(二)、Message.obtain()方法

我們來看下Message.java 類的結構圖

Message結構圖.png

Message.居然有8個obtain函數
為了方便后續的跟蹤,也將這8個方法編號,分別如下

  • ① public static Message obtain()
  • ② public static Message obtain(Message orig)
  • ③ public static Message obtain(Handler h)
  • ④ public static Message obtain(Handler h, Runnable callback)
  • ⑤ public static Message obtain(Handler h, int what)
  • ⑥ public static Message obtain(Handler h, int what, Object obj)
  • ⑦ public static Message obtain(Handler h, int what, int arg1, int arg2)
  • ⑧ public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)

這里我們也像Handler一樣,分為兩大類

  • 無參的obtain()方法
  • 有參的obtain()方法

在講解無參的obtain()的時候很有必要先了會涉及一個概念“Message對象池”,所以我們就合并一起講解了

四、Message的消息對象池和無參的obtain()方法

先來看一下下面 無參的obtain()方法的代碼

1、① public static Message obtain(Message orig)

代碼在Message.java 122行

    /**
     * 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) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                 // flags為移除使用標志
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

老規矩,先看下注釋,翻譯如下:

從全局的pool返回一個實例化的Message對象。這樣可以避免我們重新創建冗余的對象。

等等上面提到了一個名詞pool,我們通常理解為"池",我們看到源代碼的里面有一個變量是"sPool",那么"sPool",這里面就涉及到了Message的設計原理了,在Message里面是有一個"對象池",下面我們就詳細了解下

2、sPool

代碼在Message.java 111行

   private static Message sPool;
  • 好像也沒什么嘛?就是一個Message對象而已,所以sPool默認是null。
  • 這時候我們再來看上面public static Message obtain()方法,我們發現public static Message obtain()就是直接new了Message 直接返回而已。好像很簡單的樣子,大家心里肯定感覺"咱們是不是忽略了什么?"
  • 是的,既然官方是不推薦使用new Message的,因為這樣可能會重新創建冗余的對象。所以我們推測大部分情況下sPool是不為null的。那我們就反過來看下,來全局找下sPool什么時候被賦值的

我們發現除了public static Message obtain()里面的if (sPool != null) {}里面外,還有recycleUnchecked給sPool賦值了。那我們就來看下這個方法

3、recycleUnchecked()

代碼在Message.java 291行

   /**
     * 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) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
4、消息對象池的理解

為了讓大家更好的理解,我把recycleUnchecked()和obtain()合在一起,省略一些不重要的代碼
代碼如下:

void recycleUnchecked() {
                ...
        if (sPoolSize < MAX_POOL_SIZE) {
                // 第一步
                next = sPool;
                 // 第二步
                sPool = this;
                 // 第三步
                sPoolSize++;
                 ...
         }
    }

public static Message obtain() {
    synchronized (sPoolSync) {
        //第一步
        if (sPool != null) {
            // 第二步
            Message m = sPool;
            // 第三步
            sPool = m.next;
            // 第四步
            m.next = null;
            // 第五步
            m.flags = 0; 
            // 第六步
            sPoolSize--;
            return m;
        }
    }
}
4.1、recycleUnchecked()的理解

假設消息對象池為空,從new message開始,到這個message被取出使用后,準備回收 先來看recycleUnchecked()方法

  • 第一步,next=sPool,因為消息對象池為空,所以此時sPool為null,同時next也為null。
  • 第二步,spool = this,將當前這個message作為消息對象池中下一個被復用的對象。
  • 第三步,sPoolSize++,默認為0,此時為1,將消息對象池的數量+1,這個數量依然是全系統共共享的。

這時候假設又調用了,這個方法,之前的原來的第一個Message對象我假設定位以為message1,依舊走到上面的循環。

  • 第一步,next=sPool,因為消息對象池為message1,所以此時sPool為message1,同時next也為message1。
  • 第二步,sPool = this,將當前這個message作為消息對象池中下一個被復用的對象。
  • 第三步,sPoolSize++,此時為1,將消息對象池的數量+1,sPoolSize為2,這個數量依然是全系統共共享的。

以此類推,直到sPoolSize=50(MAX_POOL_SIZE = 50)

4.2、obtain()的理解

假設上面已經回收了一個Message對象,又從這里獲取一個message,看看會發生什么?

  • 第一步,判斷sPool是否為空,如果消息對象池為空,則直接new Message并返回
  • 第二步,Message m = sPool,將消息對象池中的對象取出來,為m。
  • 第三步,sPool = m.next,將消息對象池中的下一個可以復用的Message對象(m.next)賦值為消息對象池中的當前對象。(如果消息對象池就之前就一個,則此時sPool=null)
  • 第四步,將m.next置為null,因為之前已經把這個對象取出來了,所以無所謂了。
  • 第五步,m.flags = 0,設置m的標記位,標記位正在被使用
  • 第六步,sPoolSize--,因為已經把m取出了,這時候要把消息對象池的容量減一。
4.3、深入理解消息對象池

上面的過程主要討論只有一個message的情況,詳細解釋一下sPool和next,將sPool看成一個指針,通過next來將對象組成一個鏈表,因為每次只需要從池子里拿出一個對象,所以不需要關心池子里具體有多少個對象,而是拿出當前這個sPool所指向的這個對象就可以了,sPool從思路上理解就是通過左右移動來完成復用和回收

4.3.1、obtain()復用

如下圖:


obtain()復用1.png

當移動Obtain()的時候,讓sPool=next,因此第一個message.next就等于第二個message,從上圖看相當于指針向后移動了一位,隨后會將第一個message.next的值置為空。如下圖:

obtain()復用2.png

現在這個鏈表看上去就斷了,如果in-use這個message使用完畢了,怎么回到鏈表中?這就是recycleUnchecked() – 回收了

4.3.2、recycleUnchecked()回收

這時候在看下recycleUnchecked()里面的代碼,next = sPool,將當前sPool所指向的message對象賦值給in-use的next,然后sPool=this,將sPool指向第一個message對象。如下圖:

ecycleUnchecked()回收.png

這樣,就將鏈表恢復了,而且不管是復用還是回收大歐式保證線程同步的,所以始終會形成一條鏈式結構。

如下圖

回收.png

五、obtain()有參函數解析

(一)、② public static Message obtain(Message orig)

代碼在Message.java 142行

   /**
     * Same as {@link #obtain()}, but copies the values of an existing
     * message (including its target) into the new one.
     * @param orig Original message to copy.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Message orig) {
        Message m = obtain();
        m.what = orig.what;
        m.arg1 = orig.arg1;
        m.arg2 = orig.arg2;
        m.obj = orig.obj;
        m.replyTo = orig.replyTo;
        m.sendingUid = orig.sendingUid;
        if (orig.data != null) {
            m.data = new Bundle(orig.data);
        }
        m.target = orig.target;
        m.callback = orig.callback;

        return m;
    }

先翻譯注釋:

和obtain()一樣,但是將message的所有內容復制一份到新的消息中。

看代碼我們知道首先調用obtain()從消息對象池中獲取一個Message對象m,然后把orig中的所有屬性賦值給m。

(二)、③ public static Message obtain(Handler h)

代碼在Message.java 164行

     /**
     * 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;
    }

先翻譯注釋:

和obtain()一樣,但是成員變量中的target的值用以指定的值(入參)來替換。

代碼很簡單就是調用obtain()從消息對象池中獲取一個Message對象m,然后將m的target重新賦值而已。

(三)、④ public static Message obtain(Handler h, Runnable callback)

代碼在Message.java 178行

    /**
     * Same as {@link #obtain(Handler)}, but assigns a callback Runnable on
     * the Message that is returned.
     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
     * @param callback Runnable that will execute when the message is handled.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h, Runnable callback) {
        Message m = obtain();
        m.target = h;
        m.callback = callback;

        return m;
    }

先翻譯注釋:

和obtain()一樣,但是成員變量中的target的值用以指定的值(入參)來替換,并且添加一個回調的Runnable

代碼很簡單就是調用obtain()從消息對象池中獲取一個Message對象m,然后將m的target和m的callback重新賦值而已。

(四)、⑤ public static Message obtain(Handler h, int what)

代碼在Message.java 193行

    /**
     * Same as {@link #obtain()}, but sets the values for both <em>target</em> and
     * <em>what</em> members on the Message.
     * @param h  Value to assign to the <em>target</em> member.
     * @param what  Value to assign to the <em>what</em> member.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what) {
        Message m = obtain();
        m.target = h;
        m.what = what;

        return m;
    }

先翻譯注釋:

和obtain()一樣,但是重置了成員變量target和what的值。

代碼很簡單就是調用obtain()從消息對象池中獲取一個Message對象m,然后將m的target和m的what重新賦值而已。

(五)、public static Message obtain(Handler h, int what, Object obj)

代碼在Message.java 209行

    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>
     * members.
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param obj  The <em>object</em> method to set.
     * @return  A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.obj = obj;

        return m;
    }

先翻譯注釋:

和obtain()一樣,但是重置了成員變量target、what、obj的值。

代碼很簡單就是調用obtain()從消息對象池中獲取一個Message對象m,然后將m的target、what和obj這三個成員變量重新賦值而已。

(六)、⑦ public static Message obtain(Handler h, int what, int arg1, int arg2)

代碼在Message.java 228行

    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, 
     * <em>arg1</em>, and <em>arg2</em> members.
     * 
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param arg1  The <em>arg1</em> value to set.
     * @param arg2  The <em>arg2</em> value to set.
     * @return  A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what, int arg1, int arg2) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;

        return m;
    }

先翻譯注釋:

和obtain()一樣,但是重置了成員變量target、what、arg1、arg2的值。

代碼很簡單就是調用obtain()從消息對象池中獲取一個Message對象m,然后將m的target、what、arg1、arg2這四個成員變量重新賦值而已。

(七)、⑧ public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)

代碼在Message.java 249行

    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, 
     * <em>arg1</em>, <em>arg2</em>, and <em>obj</em> members.
     * 
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param arg1  The <em>arg1</em> value to set.
     * @param arg2  The <em>arg2</em> value to set.
     * @param obj  The <em>obj</em> value to set.
     * @return  A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what, 
            int arg1, int arg2, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        m.obj = obj;

        return m;
    }

先翻譯注釋:

和obtain()一樣,但是重置了成員變量target、what、arg1、arg2、obj的值。

代碼很簡單就是調用obtain()從消息對象池中獲取一個Message對象m,然后將m的target、what、arg1、arg2、obj這五個成員變量重新賦值而已。

(八)、總結

我們發現 上面有參的obtain()方法里面第一行代碼都是 Message m = obtain();,所以有參的obtain()的方法的本質都是調用無參的obtain()方法,只不過有參的obtain()可以通過入參來重置一些成員變量的值而已

六、Message的 淺拷貝

Message的淺拷貝 就是copyFrom(Message o)函數
代碼在Message.java 320行

    /**
     * Make this message like o.  Performs a shallow copy of the data field.
     * Does not copy the linked list fields, nor the timestamp or
     * target/callback of the original message.
     */
    public void copyFrom(Message o) {
        this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;
        this.what = o.what;
        this.arg1 = o.arg1;
        this.arg2 = o.arg2;
        this.obj = o.obj;
        this.replyTo = o.replyTo;
        this.sendingUid = o.sendingUid;

        if (o.data != null) {
            this.data = (Bundle) o.data.clone();
        } else {
            this.data = null;
        }
    }

先翻譯注釋:

把這個Message做成像o一樣。執行數據字段的淺拷貝,不復制字段連接,也不賦值目標的Handler和callback回調。

其實從本質上看就是從一個消息體復制到另一個消息體。有時候你可能需要用到Message的拷貝功能,也就是說拷貝一個和Message 一模一樣的B,這時候你可以使用copyFrom(Message o) 拷貝一個Message對象。

有時候你可能需要用到Message的拷貝功能,也就是說拷貝一個和Message A一模一樣的B,這時候你可以使用copyFrom(Message o)方法來淺拷貝一個Message對象。從代碼中可以看出大部分數據確實都是淺拷貝,但是對于data這個Bundle類型的成員變量卻進行了深拷貝,所以說該方法是一個淺拷貝方法感覺也不是很貼切。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容