這是“Android訊息機制”系列的第一篇文章,系列文章目錄如下:
訊息機制的故事
壽司
陳放在壽司碟
上,壽司碟
按先後順序被排成佇列
送上傳送帶
。傳送帶
被啟動後,壽司
挨個呈現到你面前,你有三種享用壽司的方法。
將Android概念帶入後,就變成了Android訊息機制的故事:
- 壽司碟 --->
訊息(Message)
- 佇列 --->
訊息佇列(MessageQueue)
- 傳送帶 --->
訊息泵 (Looper)
- 壽司 ---> 你關心的資料
- 享用壽司方法 ---> 處理資料方式
暫未找到 Handler
在此場景中對應的實體。它是一個更抽象的概念,它即可以生產壽司,又把壽司送上傳送帶,還定義了怎麼享用壽司。暫且稱它為訊息處理器
吧。
如果打算自己開一家回轉壽司店,下面的問題很關鍵:
- 如何生產壽司(如何構造訊息)
- 如何分發壽司(如何分發訊息)
讓我們帶著這兩個問題,去分析一下訊息機制原始碼。 (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()
就是在取連結串列頭。圖示如下:
-
訊息池是用連結串列結構實現的。那
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()
會將當前訊息插入到訊息鏈頭部。圖示如下
- 讀到這裡,我們知道“訊息從池中來最終又回到池中去”,那到底訊息是在什麼時候才會被回收到訊息池呢? 好問題!這個問題要等分析完訊息分發才能解答。但現在我們可以大膽的猜測一下:承載壽司的碟子會在壽司被享用完之後被廚房回收,那訊息是不是再被處理完之後就被回收了?
總結
Android訊息機制中的“構造訊息”部分講完了,總結一下:訊息的生命週期會經歷“建立-回收-再利用”,所有訊息共享一個連結串列結構的訊息池,它用於存放被回收的訊息。
故事還沒有結束,下一篇會繼續講解分發訊息