Android 中 Message的應用
Message
在Android中主要是在 消息循環機制 中使用,即配合 Handler
,Looper
和MessageQueue
來進行線程切換,線程間傳遞數據;
以及配合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++;
}
}
有答案的同學, 歡迎在評論區中指出!