在android的訊息機制中,Message其充當著資訊載體的一個角色,通俗的來說,我們看作訊息機制就是個工廠的流水線,message就是流水線上的產品,messageQueue就是流水線的傳送帶。之前做面試官的時候,經常會問面試者關於message的問題,如:
1.聊一下你對Message的瞭解。 2.如何獲取message物件 3.message的複用(如果以上問題能答對,加分)
在下面我帶著這三個問題,從這段程式碼開始逐一解析。
/**
* 建立一個handler
*/
Handler handler = new Handler();
/**
* 模擬開始
*/
private void doSth() {
//開啟個執行緒,處理複雜的業務業務
new Thread(new Runnable() {
@Override
public void run() {
//模擬很複雜的業務,需要1000ms進行操作的業務
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
@Override
public void run() {
//在這裡可以更新ui
mTv.setText("在這個點我更新了:" + System.currentTimeMillis());
}
});
}
}).start();
}
複製程式碼
我們建立了一個handler,在doSth()中開啟執行緒模擬處理複雜業務,最後通過handler的post返回結果進行UI操作(子執行緒不能進行操作UI,後話),我們先從handler的post開始看起,
Handler.java:
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*
* @param r The Runnable that will be executed.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean post(Runnable r) {
//通過getPostMessage獲取了message,再往下看
return sendMessageDelayed(getPostMessage(r), 0);
}
複製程式碼
在post中,我們傳進一個Runnable引數,我們發現有一個getPostMessage(r)函式,我們先從getPostMessage()下手。
Handler.java:
private static Message getPostMessage(Runnable r) {
//在這裡,獲取一個message,把我們的任務封裝進message
Message m = Message.obtain();
m.callback = r;
return m;
}
複製程式碼
從getPostMessage函式可得,我們把引數Runnable封裝進去message的callback變數中,在這裡埋伏一個很重要的概念,在Handler的原始碼中,是如何獲取message物件的。顧名思義,在getPostMessage中,我們就是為了獲取把runnable封裝好的message。這樣,我們可以返回上一層,繼續看函式sendMessageDelayed(Message,long)。
Handler.java:
/**
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* {@link #handleMessage}, in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
//顧名思義的delay,也就是延遲,在上一層我們看到了post裡傳參是0,繼續往下看
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//在這裡判斷handler裡的佇列是否為空,如果為空,handler則不能進行訊息傳遞,因為生產線的傳送帶都沒有的話,還怎麼進行傳送
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
複製程式碼
在handler中,存在著sendMessageDelayed最終會用sendMessageAtTime,只是sendMessageDelayed中傳參為0,使得sendMessageAtTime這函式最大程度能複用,我們繼續往enqueueMessage函式看去。
Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//在message中放一個標記
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//在這裡把訊息放到佇列裡面去
return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼
在enqueueMessage函式中,我們發現有個入參queue,這個入參就是訊息佇列,也就是之前我所說的流水線的傳送帶,message需要通過傳messagequeue進行傳遞,我們繼續往下探索。
MessageQueue.java:
boolean enqueueMessage(Message msg, long when) {
//這裡通過之前的判斷,之前放的目標,還有這個訊息是否已經在使用了,都需要判斷
//還記得之前我們看到的Message是怎麼獲取的嗎?Message.obtain(),這裡需要判斷msg.isInUse,是否已經在使用這個訊息
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//這裡就是真正把message放到佇列裡面去,並且迴圈複用。
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製程式碼
enqueueMessage先判斷之前的target是否為空,以及這個message是否已使用,後面的程式碼則是把message放進佇列中,往下我們就不探究了,我們看到最終返回的結果return true.
我們看回來此段程式碼:
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*
* @param r The Runnable that will be executed.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean post(Runnable r) {
//通過getPostMessage獲取了message,再往下看
return sendMessageDelayed(getPostMessage(r), 0);
}
複製程式碼
@return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting.
我們一層一層往下探索,無非就是把這個這個執行UI操作的Runnable封裝成message,再將這個message放進我們的訊息佇列messagequeue中。在post如果返回true則成功新增進去訊息佇列,如果返回false則代表失敗。
這個流程相信大家也清晰了吧,現在我之前所說的問題,handler中如何獲取message物件的。
Handler.java:
private static Message getPostMessage(Runnable r) {
//在這裡,獲取一個message,把我們的任務封裝進message
Message m = Message.obtain();
m.callback = r;
return m;
}
複製程式碼
在這裡,為什麼Message不是通過new一個物件,而是通過其靜態函式obtain進行獲取? 我們通過其原始碼繼續探索:
Message.java:
/**
* 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;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
複製程式碼
Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases.
我們從註釋中看到pool這個詞,這個就是池,大家應該也聽過過執行緒池,物件池,沒錯,我們獲取的message物件優先在這個message池裡獲取,如果池裡沒有再new一個新的Message.
我們先了解一下,這裡面的sPool、next、sPoolSize到底是什麼東西。 Message.java:
//池裡的第一個物件
private static Message sPool;
//物件池的長度
private static int sPoolSize = 0;
//連線下一個message的成員變數
// sometimes we store linked lists of these things
/*package*/ Message next;
複製程式碼
在Message這個類中,存在著一個全域性變數sPool,sPoolSize則是物件池中的數量,還有一個成員變數next.我們得理清一下sPool跟next到底存在著什麼關係。在這先提出一個問題,我們看了那麼久的池,怎麼沒看到類似Map這樣的容器呢?Message物件池其實是通過連結串列的結構組合起來的池。
上面有三個message,分別為message1、message2、message3 他們的連線關係分別通過其成員變數next進行銜接,舉個例子:
message1.next=message2
message2.next=message3
......
複製程式碼
以此類推,那麼我們瞭解了message的next有什麼作用,那麼sPool呢? 我們注意到sPool是全域性變數,我們又看回obtain函式中,是怎麼樣獲取的。
Message.java:
/**
* 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也是從表頭獲取,sPool賦值給message,
//同時把其連線的next賦值給sPool(這樣,連線起來的message從第二個位置放到表頭上了),賦值後設定next為空
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
複製程式碼
我們看到原始碼中,先判斷sPool為不為空,為空程式碼這個池的數量為0,不能從池裡獲取到message.那如果不空,先將sPool賦值給message,再將這個message的下一個next賦值給sPool,賦值完後將message的next設為空,這不就是從表頭裡獲取資料,sPool就是表頭的第一個message。如: message1是表頭第一個元素,sPool也是表頭,指向message1。當message1從池中取出來時候,message1連線的message2(通過next),成為了表頭,同時sPool也指向新的表頭,sPoolSize的數量也相應的需要減少。
通過以上例子,我們瞭解message的結構,也明白了message如何獲取,別忘了我們的message除了在池裡獲取,還能通過建立一個新的例項,那麼,新的例項是怎麼放進池的,下面開始看看message的回收。
Message.java:
/**
* Return a Message instance to the global pool.
* <p>
* You MUST NOT touch the Message after calling this function because it has
* effectively been freed. It is an error to recycle a message that is currently
* enqueued or that is in the process of being delivered to a Handler.
* </p>
*/
public void recycle() {
//如果還在使用這個訊息,不能進行回收--通過flag進行標示
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
複製程式碼
Recycle()函式是怎樣呼叫的,暫且先不討論,我們先看看其回收的機制,先判斷這個message是否使用狀態,再呼叫recycleUnchecked(),我們重點看看這個函式。
Message.java
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
//這裡才是真正回收message的程式碼,把message中的狀態還原
// 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;
//如果message池的數量未超過最大容納量(預設最大50個message容量),將此message回收,以便後期複用(通過obtain)
//在程式碼中可知,message在recycle()中進行回收的
//假設池中的message數量為0時,sPool全域性變數為null
//當我們把第一個message放進去池的時候,sPool(這個時候還是null)賦值給next,而message本身賦值給全域性變數sPool,也就是每次回收的message都會插入表頭
//這樣一來就形成了連結串列的結構,也就是我們所說的物件池
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
複製程式碼
我們看到原始碼中的最後幾行,如果池中現有的數量少於最大容納量,則可將message放進池中,我們又看到了頭疼的next跟sPool,我先舉個例子,腦補一下:
1.我有一個message1,我用完了,系統回收這個message1 2.現有的池,表頭是message2。
結合以上兩個條件再根據原始碼能得出: sPool跟message2都指向同一個地址,因為message2是表頭,那麼message1回收的時候,sPool賦值給了message1的next. 也就是說,message1成了新的表頭,同時池的數量sPoolSize相應的增加。
message的回收就是將其放到池的表頭,包括獲取message也是從表頭上獲取。
總結: Android的訊息機制都通過message這個載體進行傳遞訊息,如果每次我們都通過new這樣的方式獲取物件,那麼必然會造成記憶體佔用率高,降低效能。而通過對其原始碼的學習,瞭解message的快取回收機制,同時也學習其設計模式,這就是我們所說的享元模式,避免建立過多的物件,提高效能。