Android Handler源碼之消息發送 消息發送

消息發送

創建消息

創建消息使用下面的代碼:

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兩種方案,

  1. send方案發送消息
  • sendMessage(Message) 立即發送Message到消息隊列
  • sendMessageAtFrontOfQueue(Message) 立即發送Message到隊列,而且是放在隊列的最前面
  • sendMessageAtTime(Message,long) 設置時間,發送Message到隊列
  • sendMessageDelayed(Message,long) 延時若干毫秒後,發送Message到隊列
  1. 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)中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章