Android Message解析

沈敏傑發表於2017-12-19

在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物件池其實是通過連結串列的結構組合起來的池。

Paste_Image.png

上面有三個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的快取回收機制,同時也學習其設計模式,這就是我們所說的享元模式,避免建立過多的物件,提高效能。

相關文章