消息發送
創建消息
創建消息使用下面的代碼:
Message msg = Message.obtain(); // 實例化消息對象
msg.what = 1; // 消息標識
msg.obj = "AA"; // 消息內容存放
handler.sendMessage(msg);
創建消息,獲取Message對象,最好的方法是調用Message.obtain(),而不是直接通過new Message()的方式,因爲Message.obtain()是從一個可回收的對象池中獲取Message對象,避免了重複創建。
關於Message的源碼分析可以看下這篇文章:
Messgae源碼分析
發送消息
查看一下Handler的類結構圖:
Handler發送消息主要有sendMessage和post兩種方案,
- send方案發送消息
- sendMessage(Message) 立即發送Message到消息隊列
- sendMessageAtFrontOfQueue(Message) 立即發送Message到隊列,而且是放在隊列的最前面
- sendMessageAtTime(Message,long) 設置時間,發送Message到隊列
- sendMessageDelayed(Message,long) 延時若干毫秒後,發送Message到隊列
- post方案
- post(Runnable) 立即發送Message到消息隊列
- postAtFrontOfQueue(Runnable) 立即發送Message到隊列,而且是放在隊列的最前面
- postAtTime(Runnable,long) 設置時間,發送Message到隊列
- postDelayed(Runnable,long) 在延時若干毫秒後,發送Message到隊列
下面先從send方案中的第一個sendMessage() 開始源碼跟蹤。
send方式
sendMessage()
發送消息在執行了handler.sendMessage(msg)之後,我們往下追蹤源碼。
調用了下面的方法:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
繼續調用了sendMessageDelayed(msg, 0)方法:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
發現繼續調用了sendMessageAtTime()方法,繼續往下看:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// 1
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
// 2
return enqueueMessage(queue, msg, uptimeMillis);
}
這個方法主要做了兩件事,
- 1、獲取消息隊列,並對該消息隊列做非空判斷,如果爲null,直接返回。
- 2、調用了enqueueMessage()方法,第一步獲取的消息隊列也作爲了參數。
enqueueMessage()
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 1
msg.target = this;
// 2
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 3
return queue.enqueueMessage(msg, uptimeMillis);
}
主要做了三件事:
- 1、msg.target = this,設置了msg的target變量,將target指向自己。查看Message.java的源碼,target實際上是Handler對象。
- 2、mAsynchronous是控制着Handler發送同步異步消息的,在這裏先簡單提一下異步的作用,這裏的異步主要是針對Message中的障柵(Barrier),當出現障柵(Barrier)時,同步消息會被阻塞,而異步消息不會被阻塞。後面會有單獨的文章介紹Handler的同步消息和異步消息。這裏mAsynchronous爲false,mAsynchronous是控制着Handler發送異步消息的,在Handler初始化時,在Handler構造方法裏,傳了false:
public Handler() {
this(null, false);
}
在下面Handler的構造方法裏,將mAsynchronous設置爲false。
public Handler(Callback callback, boolean async) {
......省略代碼
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
3、調用MessageQueue的enqueueMessage()方法。
繼續看enqueueMessage()方法。
boolean enqueueMessage(Message msg, long when) {
// 1 判斷msg的target變量是否爲null,如果爲null則拋出異常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 2 判斷msg的標誌位,此時msg應該是要入隊,msg的標誌位應該是還未被使用,如果是已使用狀態,很明顯是有問題的,直接拋出異常
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// 3 加入同步鎖
synchronized (this) {
// 4 判斷消息隊列是否是關閉狀態,如果是關閉狀態,return false消息入隊失敗,並且回收消息
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;
}
// 5 設置msg的when,修改msg的標誌位爲已使用狀態
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 6 主要是用來判斷要入隊的msg是否位於隊列頭部。第一個msg時,mMessages爲null,所以該msg應該位於頭部
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 把msg的下一個元素設置爲p,p是mMessages,mMessages此
//時爲null,所以第一個message的next指向了null
msg.next = p;
// 把msg設置爲鏈表的頭部元素
mMessages = msg;
// needWake需要根據mBlocked的情況考慮是否觸發,如果有阻
// 塞,則需要喚醒,讓它立刻去拿消息處理
needWake = mBlocked;
} else { // 7 如果上面三個條件都不滿足則說明要把msg插入到中間的位置
// 8 ,如果頭部元素不是障柵(barrier)或者異步消息,而且還是插入
// 中間的位置,我們是不喚醒消息隊列的
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 9 不斷遍歷消息隊列,根據when的比較找到合適的插入
// Message的位置
for (;;) {
// 設置前一個message指針
prev = p;
// 設置下一個message指針
p = p.next;
// 10
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 11
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
// 12
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
- 第一步,判斷msg的target變量是否爲null,如果爲null則拋出異常。
- 第二步,判斷msg的標誌位,此時msg應該是要入隊,msg的標誌位應該是還未被使用,如果是已使用狀態,很明顯是有問題的,直接拋出異常。
- 第三步,加入同步鎖。
- 第四步,判斷消息隊列是否是關閉狀態,如果是關閉狀態,return false消息入隊失敗,並且回收消息。
- 第五步,設置msg的when,修改msg的標誌位爲已使用狀態。
- 第六步,主要是用來判斷要入隊的msg是否位於隊列頭部。如果p==null則說明msg是消息隊列的第一個消息;when == 0 表示立即執行;when< p.when 表示 msg的執行時間早與鏈表中的頭部元素的時間,所以上面三個條件,哪個條件成立,都要把msg設置成消息隊列中鏈表的頭部是元素。
- 第七步,如果上面三個條件都不滿足則說明要把msg插入到中間的位置。
- 第八步,如果頭部元素不是障柵(barrier)或者異步消息,而且還是插入中間的位置,我們是不喚醒消息隊列的。
- 第九步,進入一個死循環,mMessages是第一個message,prev = p,將p的值賦值給prev,前面p指mMessage,所以此處prev指向了mMessage,即隊列中的第一個message。p = p.next,p指向了隊列中的第二個message,以此類推,實現了消息指針的移動。
- 第十步,跳出循環條件,p==null,說明沒有下一個元素,即消息隊列到末尾了。p!=null&&when < p.when 說明當前需要入隊的這個message的執行時間是小於小於隊列中p指向的message的執行時間的,也就是說這個需要入隊的message比p指向的message先執行,說明這個位置剛好是這個入隊的message的,這樣就跳出循環。
- 第十一步,跳出循環後,主要做了兩件事,第一,將入隊的這個msg的next指向循環中獲取到的應該排在這個消息之後的message。第二,將msg前面的message.next指向了msg。這樣msg就完成了入隊。
- 第十二步,如果需要喚醒,則喚醒,調用native的方法。
整個流程可以用下面的圖片表示:
總結:遍歷消息隊列中的所有消息,根據when的比較,找到合適的message的入隊位置。
sendMessageAtFrontOfQueue()
查看源碼:
public final boolean sendMessageAtFrontOfQueue(Message msg) {
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, 0);
}
和sendMessageAtTime()大體上一樣,唯一的區別是該方法在調用enqueueMessage()方法時,最後一個參數是0。
查看MessageQueue的enqueueMessage()方法發現,當when==0時,會一直走if語句的內容,msg會一直插在消息隊列的頭部。
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{
......省略代碼
}
sendEmptyMessage
sendEmptyMessage()方法,調用了sendEmptyMessageDelayed()方法
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
sendEmptyMessageDelayed()方法,內阻組裝了一個僅有what的Message,然後調用sendMessageDelayed(),從這不開始就跟sendMessage()往後的流程相同。
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
sendEmptyMessageAtTime
查看源碼,直接調用了sendEmptyMessageAtTime()。
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageAtTime(msg, uptimeMillis);
}
以上就是發送消息的send方案,最終都會走向了enqueueMessage()方法。
post方式
查看post方式的源碼,可以看到,在方法內部,還是調用了sendMessageDelayed()方法,最終也會走到上面的send流程,最終調用enqueueMessage()方法。這其中有個getPostMessage()方法。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
查看getPostMessage()的源碼
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
這個方法主要做了兩件事:
- 通過Message.obtain()從消息對象池中獲取一個空的messgae
- 將這個空的message的callback變量指向Runnable
所以我們知道boolean post(Runnable r)方法的內置也是通過Message.obtain()來獲取一個Message對象m,然後僅僅把m的callback指向參數r而已。最後最終通過調用send方案的某個流程最終調用到boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)。
總結
Handler的發送消息,無論是通過send方案還是pos方案最終都會做走到 boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)中。