Android中Message對象複用原理

Android 中 Message的應用

Message 在Android中主要是在 消息循環機制 中使用,即配合 Handler,LooperMessageQueue來進行線程切換,線程間傳遞數據;
以及配合Handler在IPC中傳遞數據; 這裏不對這些進行展開,它不是我們關注的重點.

我們在代碼中,被建議(網上或者前輩或看註釋)用以下的方式來使用 Message,並且被告知,這樣會提高性能.

Message msg = Message.obtain();

/**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain();

嗯! 根據官方的文檔,這樣確實能夠提高性能,將避免在大多數情況下創建新的對象!

下面我們來看它是如何實現高效的.

Message對象複用的實現

Message能提高效率的原因是,將使用完的Message清除附帶的數據後,添加到複用池中,當我們需要使用它時,直接在複用池中取出對象使用,而不需要重新new創建對象. 而複用池本質上就是一個單向鏈表,爲了更好的理解,Message複用池的實現,我們先來看下,簡單的單向鏈表實現.

public class Linked<T> {
    // 鏈表的頭節點
    private Node<T> head;
    // 鏈表的長度
    private int length;
    // 數據插入頭部
    public void insertHead(T data) {
        Node<T> n = new Node<>();
        n.data = data;
        // 將所添加的節點的下一個節點指向頭結點
        n.next = head;
        // 將頭結點指向黨員元素
        head = n;
        // 鏈表長度加1
        length++;
    }
    // 移除頭部,並取出數據
    public T removeHead() {
        // 拿到原頭結點
        Node<T> n = head;
        // 將頭結點設置爲下一個節點
        head = n.next;
        // 原頭節點的下一個節點置空
        n.next = null;
        // 鏈表長度減1
        length--;
        // 返回原頭結點數據
        return n.data;
    }
    public int getLength() {
        return length;
    }
    // 節點類
    static class Node<T> {
        T data; // 節點存放的元素
        Node<T> next; // 下一個節點的引用
    }
}

上面實現了單向鏈表的插入頭部,和移除頭部功能.
相信大家,對這種單向鏈表的實現,都十分熟悉,這裏不再詳細講解,如果看不懂,請自行復習數據結構相關知識點.

下面我們帶着上面的知識,來看Message中源碼的實現.

// 只顯示我們需要關注的代碼
public final class Message implements Parcelable {
    ...
    // 下一個節點的引用
    Message next;
    // 這裏起到類鎖的功能,相當於 Message.class
    private static final Object sPoolSync = new Object();
    // 可以類比爲 頭結點
    private static Message sPool;
    // 鏈表的長度
    private static int sPoolSize = 0;
    // 鏈表的最大長度
    private static final int MAX_POOL_SIZE = 50;
    // 獲取Message對象,類比單向鏈表的removeHead操作
    public static Message obtain() {
        // 同步鎖, 這裏相當於鎖住 Message.class, 起到類鎖的作用, 每一個Message實例都是同一把鎖
        synchronized (sPoolSync) {
            // 鏈表中有可複用的Message,直接拿到頭結點,然後將頭結點指向下一個元素
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        // 鏈表中,沒有可複用的message,直接new
        return new Message();
    }
    // 類比單向鏈表的insertHead操作
    void recycleUnchecked() {
        // 清空數據操作
        obj = null;
        ...
        // 同步鎖
        synchronized (sPoolSync) {
            // 如果鏈表長度小於50,將當前結點,加入鏈表頭部
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
    ...
}

從上面的分析,我們來梳理一下整體的流程思想.

  • Message 使用一個靜態變量,來起到頭結點的作用.

靜態變量屬於類變量,內存中只會存在一份引用,正好能起到 頭結點的作用

  • Message 將自身作爲Node節點,存儲下一個節點的引用,和自身數據
  • obtain()方法相當於鏈表中移除首元素,並返回該元素的操作

從複用池中獲取Message避免了 new創建的消耗.

  • recycleUnchecked()方法相當於鏈表中將節點加入到頭部的操作

添加到複用池之前,會先將Message中的數據清空.

  • Message添加了線程安全的操作
  • Message複用池最多保存50個使用完待複用的Message

50個可能是考慮到內存開銷和時間開銷的平衡, 不是將數據無限制的添加到複用池.


Message將自身作爲節點, 使用一個靜態變量作爲頭結點,讓Message自身形成一個鏈表,而鏈表中存的是 已經清空數據的Message對象, 以這種方式起到複用的效果!

疑問

從代碼中可以看出, Message中,將 private static final Object sPoolSync = new Object();作爲鎖標誌,來起到類鎖的作用.

它能起到類鎖的作用是因爲,static修飾的變量在類加載的初始化階段就將被創建,final使得引用不可改變,從而達到 內存獨一份的效果,進而起到和類鎖同樣的作用.

這裏有個疑問, 爲何不直接使用類鎖來加鎖呢? 使用上訴方式,反而需要new一個Object對象,不是增加開銷嗎?

// why not ???
synchronized (Message.class) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }

有答案的同學, 歡迎在評論區中指出!

引用

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