全面剖析Android訊息機制原始碼

JeromeLiee發表於2019-04-15

在Android應用中,訊息機制可謂是處於舉足輕重的地步,因為UI是Android的整個門面展示,而UI的展示是交由訊息機制來處理。Android不允許在子執行緒中進行UI處理,因為這樣會引發多執行緒的安全問題,而解決這個問題則需要做加鎖等操作,這樣會導致效率低下,造成UI不流暢等問題,這是萬萬不可接受的。

說到Android訊息機制的用途,你可能會想到子執行緒和主執行緒的通訊、延遲傳送一個訊息或執行一個Runnable等,但你有沒有想過,它是如何實現子執行緒和主執行緒的通訊?子執行緒和子執行緒之間是否能通過訊息機制來進行通訊?延遲傳送或執行的內部原理又是如何實現的?另外你可能聽過這樣的問題——主執行緒在Looper.loop()中開啟了一個死迴圈,為什麼不會造成ANR(Application Not Responding)?從MessageQueue中取出訊息時可能會阻塞,為什麼該阻塞也不會造成ANR?這些問題歸根結底就是原理問題,在看完本篇文章後都會茅塞頓開,so follow me!

Android訊息機制的簡單圖解

全面剖析Android訊息機制原始碼

訊息的傳送到處理可以大致分為5個步驟,分別是初始化準備工作傳送訊息訊息入隊Looper迴圈和訊息出隊,以及訊息處理,我們一步一步來看。

1. 初始化準備工作

平時我們在使用Handler傳送訊息時,只需要建立一個Handler物件,然後呼叫相應的傳送方法即可,使用起來特別簡單。但其實在建立Handler物件之前,主執行緒已經做了一些準備工作,其中就有MessageQueueLooper的建立初始化,並且將它們存放在主執行緒的私有記憶體中。接下來從原始碼中分析,首先來看Handler的構造方法:

1.1 Handler中的初始化工作

// 構造方法1
public Handler() {
    this(null, false);
}

// 構造方法2
public Handler(Callback callback) {
    this(callback, false);
}

// 構造方法3
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;
}

// 構造方法4
public Handler(Looper looper) {
    this(looper, null, false);
}

// 構造方法5
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

// 構造方法6
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
複製程式碼

有6個構造方法,我們先主要看構造方法1構造方法3,其餘構造方法會在後面講解。其中,構造方法1呼叫了構造方法3,然後在構造方法3中,注意mLooper = Looper.myLooper()這行程式碼,獲取了一個Looper物件,然後接下來就對該Looper物件進行了null判斷,如果為null則丟擲RunTime異常

Can't create handler inside thread xxx that has not called Looper.prepare() 因為沒有呼叫Looper.preapre()方法,所以在xxx這個執行緒中不能建立Handler物件

你會想哎這不對啊?我平時建立Handler時也沒遇到過啊。其實前面說過了,主執行緒早已幫我們做了這些初始化的準備工作了,具體的程式碼需要去Looper類裡看看。

1.2 Looper的初始化工作

首先看下Looper類的構造方法

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
複製程式碼

Looper的構造方法裡,建立了一個MessageQueue物件,獲取了當前的Thread物件。但該構造方法是私有的,如何建立Looper物件呢?其實在上一小結中的Runtime異常中已經告訴了答案,即呼叫Looper.prepare()方法:

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
複製程式碼

其中prepare()方法呼叫了prepare(boolean quitAllowed)方法,而該方法裡也只有3行程式碼。首先判斷當前執行緒是否已經建立了Looper物件,如果是則拋異常:

Only one Looper may be created per thread

否則建立一個,並且將其存放到當前執行緒的私有記憶體中。如果你對ThreadLocal不太熟悉且想進一步瞭解的話,可以閱讀 Java之ThreadLocal詳解 這篇文章。

prepare()方法的作用就是在當前執行緒中建立一個Looper物件,並且建立關聯一個MessageQueue物件,然後通過ThreadLocal將這個關聯了MessageQueue物件的Looper物件存放到當前執行緒的私有記憶體中,請記住,這是實現執行緒間通訊的根本。文章後面會將這塊同整個訊息機制串聯起來,屆時就會很清楚地理解了整個訊息機制邏輯。

另外,主執行緒的初始化Looper物件的方法如下,基本上和prepare()方法大同小異:

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
複製程式碼

該方法是在ActivityThread類中的main()方法中呼叫,這是應用的入口方法,啟動時便會呼叫。所以說主執行緒的訊息傳送不需要手動呼叫Looper.prepare()方法,因為主執行緒早就做了這些準備工作。

// ActivityThread類,此方法為應用程式的入口方法
public static void main(String[] args) {
    ...省略部分程式碼
    // 建立初始化Looper
    Looper.prepareMainLooper();

    ...省略部分程式碼

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    ...省略部分程式碼
    // 開啟訊息迴圈
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製程式碼

注意到該方法中倒數第二行呼叫了Looper.loop()方法,它是一個死迴圈,會一直呼叫訊息佇列MessageQueuenext()方法獲取Message,然後交由Handler處理。此處先知道其作用即可,後面第4章節會詳細介紹Looper.loop()方法。

1.3 訊息機制的初始化準備工作小結

現在我們來整理下,一個完整的訊息機制的初始化準備工作基本上有以下3個步驟:

  1. 呼叫Looper.prepare()方法,建立一個關聯了MessageQueueLooper物件,並通過ThreadLocal將其存放在當前執行緒的私有記憶體中,這是保證多執行緒間通訊的根本;
  2. 建立一個Handler物件;
  3. 呼叫Looper.loop()方法,開啟死迴圈從MessageQueue中獲取訊息,該方法的呼叫時機也可以放在步驟2之前。

以上便是訊息機制的初始化準備工作,接下來便可以進行傳送訊息的操作了。

2. 傳送訊息

初始化準備過程已經完成了,接下來就可以傳送訊息。在傳送一條訊息時,我們可以呼叫Handler的以下send方法來實現:

2.1 傳送訊息原始碼分析

// 傳送方法1.傳送一條訊息
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

// 傳送方法2.傳送一條延遲處理的訊息
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

// 傳送方法3.傳送一條空訊息
public final boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
}

// 傳送方法4.傳送一條延遲處理的空訊息
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}
複製程式碼

也可以呼叫post方法來投遞一個Runnable,但其本質上也是傳送了一條訊息:

// 傳送方法5.投遞一個Runnable
public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

// 傳送方法6.投遞一個延遲處理的Runnable
public final boolean postDelayed(Runnable r, long delayMillis){
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

// 將Runnable轉為Message
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
複製程式碼

方法5方法6雖然是投遞一個Runnable,但實質上是通過getPostMessage(Runnable r)方法,將Runnable封裝到了Messagecallback變數中,最終也是傳送了一個Message

上面6種傳送訊息的方法,其中

方法1內部呼叫了方法2方法3呼叫了方法4,而方法4內部也呼叫了方法2方法5方法6內部也是呼叫了方法2

可以看到send方法或post方法最終都指向了方法2,那麼接下來就分析方法2——sendMessageDelayed(Message msg, long delayMillis):

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    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);
}

// 訊息入隊
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // MessageQueue的訊息入隊
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼

可以看到,sendMessageDelayed(Message msg, long delayMillis)方法內部呼叫了sendMessageAtTime(Message msg, long uptimeMillis)方法,其中引數uptimeMillis是一個時間參考,用來表示什麼時候該Message會被執行。

一條延遲處理的訊息,其對應的執行時間uptimeMillis等於開機執行時間SystemClock.uptimeMillis()加上延遲執行的時間delayMillis(非延遲訊息的delayMillis值為0),最終呼叫enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法。

接下來在enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法中,會將當前Handler物件封裝至Messagetarget變數,注意此處,後面在第五章節訊息處理時會再回顧這行程式碼。最後呼叫MessageQueueenqueueMessage(Message msg, long when)方法中,進行訊息入隊操作。至此,Handler中的訊息傳送過程已經完成了。

2.2 傳送訊息過程小結

傳送訊息的過程還是比較簡單的,簡單整理如下:

  1. 通過post系列方法或send系列方法傳送一個訊息Message
  2. 如果是延遲訊息,則該訊息的執行時間=開機執行時間+延遲執行時間,否則執行時間=開機執行時間
  3. 最後將當前Handler物件封裝至Messagetarget中,再呼叫MessageQueueenqueueMessage(Message msg, long when)方法進行入隊操作。

3. 訊息入隊

訊息傳送完畢,接下來就是訊息入隊操作,對應的程式碼是MessageQueueenqueueMessage()方法:

3.1 訊息入隊原始碼分析

boolean enqueueMessage(Message msg, long when) {
    ...省略部分程式碼

    synchronized (this) {
        ...省略部分程式碼

        msg.markInUse();
        msg.when = when;
        // 獲取Message佇列的頭部
        // 注意:此佇列實質上是一個單向連結串列,目的是為了更方便地插入和移除訊息
        Message p = mMessages;
        boolean needWake;
        // 滿足以下3個條件之一,便會將當前Message設為佇列頭:
        // 1.佇列頭為空,即該佇列為空;
        // 2.when為0,該值可以手動賦值,一般我們用不到;
        // 3.當前要入隊的訊息執行的時間早於佇列頭
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            // 一個新的佇列頭,如果當前佇列阻塞則喚醒,mBocked為true表示佇列是阻塞狀態
            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.
            // 一般來說不需要喚醒佇列的阻塞狀態,除非佇列頭是一個同步屏障(barrier),且當前的Message是非同步的,則根據阻塞狀態決定是否需要喚醒佇列
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            // 該迴圈的目的是按照when從小到大的順序,找到Message的位置
            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.
        // mPtr是native層的MessageQueue的引用地址,是在MessageQueue的構造方法裡初始化的
        // 這樣便可以將native層和java層的物件關聯起來
        // 如果needWake=true,則通過nativeWake(mPtr)方法喚醒阻塞中的佇列,喚醒之後的操作,將在下節訊息出隊中講解
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
複製程式碼

訊息入隊的操作還是相對來說比較簡單的,即:

如果當前訊息佇列為空,或插入的Message執行時間when早於佇列頭的Message,則將其置為訊息佇列首部,並且將佇列從阻塞狀態中喚醒; 否則按照Message的執行時間排序,將該Message插入到佇列中。

注意到needWake = mBlocked && p.target == null && msg.isAsynchronous()這行程式碼,涉及到訊息機制的同步屏障,這裡簡單講解一下。

3.2 同步屏障(Sync Barrier)

在UI執行緒中,其主要目的就是保證及時有效地重新整理UI。假設現在需要重新整理UI,但主執行緒的訊息佇列中還存在其它的訊息,那麼就需要保證優先執行UI重新整理的訊息,遮蔽其它非UI相關的,同步屏障就起到了這樣的作用。

3.2.1 原始碼分析

一般來說我們傳送訊息時,最終會在HandlerenqueueMessage()方法中將當前Handler物件封裝至Messagetarget中,但同步屏障訊息是沒有Handler的,可以呼叫MessageQueuepostSyncBarrier()來傳送一個訊息屏障:

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}
複製程式碼

可以看到內部並沒有設定給Message設定Handler,而且依舊是按照訊息的執行時間when來排序插入到佇列中。移除同步屏障呼叫MessageQueueremoveSyncBarrier(int token)方法即可,其內部原始碼就不貼出來了,感興趣可自行檢視。

3.2.1 同步屏障和同步、非同步訊息

一般我們傳送的訊息是同步(synchronous)的,有兩種方式可以設定傳送非同步訊息:

  • 一是通過Handler構造方法3構造方法6,將構造引數async設為true即可。通過這種方式,傳送的所有訊息都是非同步的。
  • 另一種是呼叫MessagesetAsynchronous(boolean async)方法設定為true。通過這種方式,當前傳送的訊息是非同步的。

同步屏障的作用就是遮蔽訊息佇列中該同步屏障之後的所有同步訊息,只處理非同步訊息,保證非同步訊息優先執行,其具體程式碼邏輯見4.2 訊息出隊

3.2.3 同步屏障的應用

同步屏障用於UI繪製,在ViewRootImpl類的scheduleTraversals()方法中呼叫:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // UI繪製之前設定一個同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 傳送繪製的訊息,保證優先執行mTraversalRunnable
        // 最終會將該Runnable物件封裝至Message中,並設定該Message為非同步訊息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
複製程式碼

當優先執行了mTraversalRunnable,呼叫其run()方法後,run()方法內部會呼叫doTraversal()方法,該方法內移除了之前設定的同步屏障,然後執行UI繪製操作方法performTraversals()

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除之前設定的同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        // 進行UI繪製
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}
複製程式碼

4. Looper迴圈和訊息出隊

在1.3小節的訊息機制初始化準備小節中,我們提到了Looper.loop()呼叫,其作用是開啟一個訊息迴圈,然後從MessageQueue佇列中取出訊息交由Handler處理。把它放到現在來講是因為loop()方法和訊息出隊next()操作緊密相連,我們先看loop()方法內的實現:

4.1 Looper迴圈

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    ...省略部分程式碼

    for (;;) {
        // 當訊息佇列中沒有訊息或延遲執行訊息時,MessageQueue的next()方法會阻塞
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        ...省略部分程式碼

        try {
            // 進行訊息處理
            // 此target便是Handler#enqueueMessage(MessageQueue, Message, long)方法中第一行程式碼 msg.target = this
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        
        ...省略部分程式碼
        
        // Message回收
        msg.recycleUnchecked();
    }
}
複製程式碼

該方法內部實現還是比較簡單的:首先做了一些檢驗工作,然後開啟一個死迴圈。在死迴圈中呼叫MessageQueuenext()方法獲取訊息,如果有則交由其封裝的Handler處理(其處理邏輯見5. 訊息處理),沒有則阻塞。具體的阻塞和訊息出隊,都在MessageQueuenext()方法實現,進去看一看吧。

4.2 訊息出隊next()

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    // 在3.1 訊息入隊原始碼分析章節中,我們知道了mPtr是native層的MessageQueue的引用地址
    // 通過這個引用地址,可以將native層和java層關聯起來
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    // 用於統計當前閒置Handler數量
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    // 阻塞的時長
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 實現阻塞,阻塞時長為nextPollTimeoutMillis,Looper.loop()方法中的might block就是來自這裡
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            // msg.target == null表示該Message是一個屏障(barrier)。
            // 如果是屏障,則跳過該屏障之後所有的同步訊息,只執行非同步訊息
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                // 從佇列中找出下一個非同步Message
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    // 該Message執行時間還未到,所以需要設定阻塞時長
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    // 取出需要執行的Message
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                // 如果當前佇列沒有訊息,則將nextPollTimeoutMillis設為-1
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // 訊息佇列為空或Message未到執行時間時,則開始處理IdleHandler
            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                // 執行IdleHandler中的queueIdle()方法
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}
複製程式碼

next()方法程式碼內部也是一個死迴圈,程式碼比較長,我們分成兩部分邏輯來分析:一部分是前半段查詢獲取訊息的邏輯,另一部分是為後半段處理IdleHandler的邏輯。

4.2.1 查詢獲取訊息

在死迴圈內,首先判斷佇列頭是否為訊息屏障,是則找出下一個非同步的訊息,否則取佇列頭訊息。然後判斷取出的訊息執行時間when

如果執行時間沒到,則設定阻塞時長,等下次迴圈時進行阻塞,否則取出該訊息並立刻返回。

阻塞的程式碼為nativePollOnce(ptr, nextPollTimeoutMillis),這是一個native方法,nextPollTimeoutMillis表示延遲時長:

  • nextPollTimeoutMillis=0:首次執行next()方法的死迴圈時,呼叫nativePollOnce(ptr, nextPollTimeoutMillis)方法,會立刻返回不會阻塞,然後繼續執行後面的程式碼;
  • nextPollTimeoutMillis=-1:當佇列為空時,nativePollOnce(ptr, nextPollTimeoutMillis)會一直阻塞,除非有訊息入隊則觸發喚醒;
  • nextPollTimeoutMillis>0:阻塞nextPollTimeoutMillis毫秒,在這期間如果有新的訊息入隊則可能觸發喚醒(新的訊息執行時間早於nextPollTimeoutMillis則會喚醒)。

喚醒的操作由第3節訊息入隊的nativeWake(mPtr)方法實現。入隊喚醒和出隊阻塞的方法都是native方法,由Linuxepoll機制實現,感興趣可閱讀《深入理解Android 卷III》第二章 深入理解Java Binder和MessageQueue 這篇文章中的2.3小節。

4.2.2 處理IdleHandler

當訊息佇列為空或Message未到執行時間時,則處理IdleHandlerIdleHandler可用於訊息佇列閒置時的處理,例如ActivityThread中的GcIdler,用於觸發主執行緒中的GC垃圾回收,當主執行緒沒有訊息處理時,就會有可能觸發GC

// ActivityThread類中的GcIdler內部類
final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        doGcIfNeeded();
        return false;
    }
}
複製程式碼

4.3 Looper迴圈和訊息出隊小結

在這一章節中,Looper.loop()迴圈方法主要是呼叫MessageQueuenext()方法獲取Message,然後交由對應的Hanlder處理。

MessageQueue佇列如果為空,則一直阻塞,等待下次訊息入隊喚醒佇列;不為空時,當訊息的執行時間未到,則進行nextPollTimeoutMillis>0時長的阻塞,直到阻塞時間結束,或有新的訊息入隊,且其執行時間早於當前阻塞的訊息執行時間,則喚醒佇列。

接下來則看最後一個步驟,關於訊息的處理邏輯。

5. 訊息處理

Looper.loop()方法中,從MessageQueue中獲取到一條不為空的訊息時,呼叫了msg.target.dispatchMessage(msg)進行訊息分發處理,此時又回到了Handler中,看下dispatchMessage(Message msg)方法:

// Handler.java

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}
複製程式碼

首先會判斷msg.callback是否為null,這個callback就是封裝的Runnable物件,即Hanlder.post系列方法投遞的Runnable。如果不為空,則執行Runnablerun()方法。

否則,則判斷mCallback是否為null。這個mCallback是什麼東西呢?可以回顧下Handler的構造方法,其中構造方法2、3、5、6都有一個構造引數Callback,這個一個介面:

public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}
複製程式碼

如果Callback介面的方法handleMessage(Message msg)返回為true,則不再繼續分發訊息,否則呼叫HandlerhandlerMessage(Message msg)方法,這是一個空方法,一般選擇在Handler的子類實現:

public void handleMessage(Message msg) {
}
複製程式碼

一句話總結訊息處理的邏輯:

  • 如果是post系列方法,則執行其Runnablerun()方法;
  • 否則判斷Handler構造方法裡傳入的Callback是否返回為ture
    • true則訊息處理結束;
    • false則繼續分發給HandlerhandleMessage(Message msg),然後結束。

6. Handler移除訊息原始碼分析

當需要移除一個MessageRunnable時,呼叫Handler對應的remove方法即可,其內部呼叫的是MessageQueue對應的remove方法。我們選擇Handler.removeMessages(int what)這個方法來分析,其它移除邏輯基本一致。

// Handler.java

// 移除訊息佇列中所有滿足Message.what=what的訊息
public final void removeMessages(int what) {
    mQueue.removeMessages(this, what, null);
}

// MessageQueue.java

// 上面Handler的remove方法呼叫的是該方法
void removeMessages(Handler h, int what, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        // 此while迴圈移除回收了從佇列頭開始,連續滿足移除條件的訊息
        while (p != null && p.target == h && p.what == what
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        // 否則在此while迴圈中移除回收之後的訊息
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && n.what == what
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}
複製程式碼

主要是用了兩個while迴圈來移除訊息,第一個移除前面連續滿足移除條件的訊息,後面則依次判斷移除滿足條件的訊息。

注意這裡面有個暗坑,如果佇列裡有延遲執行的訊息,其中有通過sendDelay傳送的what=0的訊息,也有通過postDelay投遞的Runnable,如果呼叫Handler.removeMessages(0)方法來移除what=0的所有訊息,很不幸你會發現,佇列中的所有Runnable封裝的訊息也會被移除。原因是封裝RunnableMessage,其what預設為0,正好滿足移除what=0訊息的邏輯,所以定義what時需要注意,避免定義為0。

7. 訊息傳送到處理完整過程

一個完整的訊息機制從開始到結束的細節差不多就分析完了,現在我們將整個過程串起來,簡要回顧一番:

  1. 初始化準備:手動呼叫Looper.prepare()方法初始化建立一個Looper物件,其內部會同時建立了一個與之關聯的MessageQueue物件,然後通過ThreadLocal將該Looper物件存放至當前執行緒的私有記憶體中。接著手動建立一個Handler,用於傳送和處理訊息,可以通過構造方法傳入之前建立的Looper;也可以不傳,則會使用當前執行緒私有記憶體中存放的Looper物件。接著手動呼叫Looper.loop()方法,開啟一個死迴圈,會一直呼叫MessageQueuenext()方法獲取訊息。
  2. 傳送訊息:手動呼叫send系列方法或post方法,最終會將訊息的延遲時間加上當前開機後的時長,作為該訊息的執行時間;進入sendMessageAtTime()方法,將當前Handler封裝至Message中,然後呼叫MessageQueueenqueueMessage()方法進行入隊操作。
  3. 訊息入隊:按照上步中的執行時間排序,將訊息插入到MessageQueue佇列中,如果佇列為空,或者該訊息的執行時間早於佇列中的所有訊息執行時間,則喚醒佇列的阻塞狀態
  4. 訊息出隊:由於初始化準備工作中已經開啟了Looper迴圈,所以當MessageQueue中有訊息到了需要執行的時候,則會通過next()方法返回一個Message進行訊息分發。在next()方法中,如果佇列為空或者佇列中的訊息執行時間都未到,則會導致死迴圈進入阻塞狀態。
  5. 訊息處理:如果是post系列的方法,則呼叫其Runnable物件的run()方法,否則判斷Handler構造方法傳入的Callback介面實現方法handleMessage()是否返回true,是則結束訊息處理,否則再交由HandlerdispatchMessage()方法進行最後的處理。

8. Q & A

現在可以回答文章開頭的問題了:

  1. Q: 主執行緒和子執行緒之間是如何實現通訊的?

    A: 在主執行緒建立的Handler關聯了主執行緒私有的LooperMessageQueue,然後Handler在子執行緒傳送的Message進入到了主執行緒的MessageQueue,最終在主執行緒裡通過Looper.loop()方法從MessageQueue中獲取Message,交由Handler處理。

  2. Q: 子執行緒和子執行緒之間能否通過訊息機制來通訊?

    A: 能。需要在接收訊息的子執行緒裡,建立Handler之前需要手動呼叫Looper.prepare(),之後呼叫Looper.loop()方法,這樣便可以在另一個子執行緒中傳送訊息到該子執行緒了。

  3. Q: 延遲傳送或執行的內部原理又是如何實現的?

    A: 延遲的訊息會將開機執行時間加上延遲時間所得到的時間作為訊息的執行時間,進入訊息佇列後按照執行時間來排序插入佇列中,出隊時會通過nativePollOnce()方法在底層實現阻塞狀態,阻塞時長為訊息執行時間減去當前開機時長的差值,待阻塞狀態結束後便會讓該訊息出隊,並且交由Handler來分發處理。

  4. Q: 主執行緒在Looper.loop()中開啟了一個死迴圈,為什麼不會造成ANR?從MessageQueue中取出訊息時可能會阻塞,為什麼該阻塞也不會造成ANR

    A: 首先,ANR是因為輸入事件得不到及時處理,此外還有ServeiceBroadcast等,我們統一稱之為訊息事件。當訊息事件傳送了卻在規定的時間內無法得到處理,就會產生ANR現象。主執行緒呼叫Looper.loop()方法開啟一個死迴圈,其目的就是用於分發處理這些訊息事件,所以自然不會造成ANR,除非有其它訊息事件做了耗時操作,才會有可能導致ANR發生。

    MessageQueue中取出訊息時可能會阻塞,什麼情況下會阻塞呢?佇列為空或沒有需要及時處理的訊息時,才會發生阻塞,這是為了節約CUP資源不讓它空轉。如果你此時輸入一個訊息時間,阻塞狀態就會被喚醒,該事件會進行入隊出隊分發處理操作,也就談不上不及時處理,自然不會導致ANR發生。

9. 結束語

Android訊息機制原始碼分析基本上已經結束了,由於技術水平有限及時間倉促,難免會有錯誤之處,還懇請指點出來,共同學習進步!

相關文章