Handler 消息機制二:Message 源碼分析,帶你從源碼層面去理解複用機制

該篇主要介紹的是Handler 消息機制中的Message 類。Message 源碼分析,帶你從源碼層面去解讀Handler 機制中的Message 類,瞭解Message 類是如何實現消息複用的?這裏面涉及到的是什麼設計模式?瞭解 Message 類的正確使用姿勢 。

Message 類即是消息的載體,將要傳遞的數據打包進Message 對象進行發送,在Handler 的 handleMessage(Message msg) 方法中接收發送的Message 對象

消息的獲取


問:在Handler 的使用過程中,Message有幾種創建方式?哪種效果更好,爲什麼?

new一個Message實例(不推薦)

使用 Message 默認的構造函數可以非常簡單的創造出一個Message 實例。

Message message = new Message();

雖然Message的構造函數是公共的,我們可以通過new 關鍵字來自行創建一個Message 對象,但是系統更推薦我們使用 {Message.obtain()}或{Handler.obtainMessage()}相關的方法獲取對象。在Message 的類註釋和構造函數註釋上均已說明。

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

通過Message的obtain相關的靜態方法(推薦)

Message的obtain相關的靜態方法

通過Handler的obtain 相關的方法(推薦)

android.os.Handler#obtainMessage()
android.os.Handler#obtainMessage(int)
android.os.Handler#obtainMessage(int, java.lang.Object)
android.os.Handler#obtainMessage(int, int, int)
android.os.Handler#obtainMessage(int, int, int, java.lang.Object)

而當我們使用Handler 獲取一個Message 對象時,其內部調用的同樣也是Message 的相關方法。以android.os.Handler#obtainMessage()方法爲例:

/**
 * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
 * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
 *  If you don't want that facility, just call Message.obtain() instead.
 */
public final Message obtainMessage()
{
    return Message.obtain(this);
}

當我們在創建一個Message 時,系統推薦使用{Message.obtain()}或{Handler#obtainMessage.obtainMessage()} 系列的方法獲取一個Message 對象,不過不管使用哪種形式的方法,最後都會執行到 android.os.Message#obtain() 這個方法,而不同參數的重載方法無非是給我們在使用的過程中提供一些便捷而已。

結構


// 用戶定義的消息碼,以便收件人可以識別此消息的含義
public int what;

// 系統給我們提供的兩個用來做數據傳遞的簡單類型
public int arg1;
public int arg2;

 // 要發送給接收者的數據對象。
public Object obj;

// 觸發時間,在執行入隊操作時根據提供的時間來對消息進行排序、插隊
/*package*/ long when;

// 目標Handler,即哪個Handler對此消息進行負責,發送消息和處理響應
/*package*/ Handler target;

// Message 是一個消息鏈表,持有下一個節點的引用
/*package*/ Message next;

// 鎖對象,用來維持線程同步的
public static final Object sPoolSync = new Object();
// 對象緩存池,用於存放回收的Message,使用享元模式來節省資源開銷。其實現方式採用的是鏈表的形式。
private static Message sPool;
// 當前緩存的可用Message 數量
private static int sPoolSize = 0;
// 最大緩存對象數
private static final int MAX_POOL_SIZE = 50;

複用機制(享元模式)


Message 內部是如何進行優化的呢?

Message 內部有一個全局的緩存池 sPool,用於保存回收的Message 對象。它是一個鏈表的結構,其 next 屬性指向下一個Message 緩存對象,以這樣的方式把所有緩存的Message 對象都串在了一起。這種緩存池的實現方式也正是享元模式的一種。

obtain() 方法內部,會先從緩存池中獲取Message 對象,如果沒有則再去創建一個新消息返回。在消息轉發完成後,調用 recycleUnchecked() 方法對使用的消息進行資源釋放、回收,插入到緩存池中以備再用,節約因頻繁創建對象帶來的內存開銷。

從緩存池獲取消息

/**
 1. Return a new Message instance from the global pool. Allows us to
 2. 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;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

緩存池(sPool)本質上是一個消息鏈表,當調用obtain()方法時,檢查緩存池中是否有可用的Message 對象

  1. 有可用的Message 對象時,即 sPool !=null ,從緩存池中取出一個Message 對象。
    a. 把sPool 賦值給一個的對象;
    b. 將持有的next 對象指向 sPool,成爲緩存池新的鏈表頭。
    c. 把新的message 的 next 置爲null , 因爲message 還持有下一個節點的引用,也就意味着把新的 Message 斷鏈,形成一個獨立的消息對象。
    d. 緩存池的可用數量減1
  2. 無可用的Message 對象時,即 sPool ==null ,new 一個新的Message實例對象返回

消息回收

void recycleUnchecked() {
    // 1.清除所有的特徵信息。
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    // 2.插入到緩存池中進行回收,循環使用
    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            // 把當前對象當做對象池的鏈表頭
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

該方法的目的就是回收Message 對象,以供循環使用。在處理排隊的消息時,recycleUnchecked() 方法由MessageQueue和Looper內部使用。在Loop的loop() 方法中,在消息發送完畢後,會調用 msg.recycleUnchecked() 來回收消息。

recycleUnchecked() 方法內部可以分爲2個步驟:

  1. 清除Message 的屬性信息。因爲message 攜帶了很多特定的信息(比如消息標識、執行時間、攜帶數據、目標Handler等等),而這些信息是屬於具體消息的,對於下一個消息來說並不適用,所以在回收之前需要先把這些特定信息清掉。
  2. 添加到回收的對象緩存池中。這裏是把要回收的對象放到緩存鏈表的頭部。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章