在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的緩存回收機制,同時也學習其設計模式,這就是我們所說的享元模式,避免創建過多的對象,提高性能。