全面剖析Android消息機制源碼


在Android應用中,消息機制可謂是處於舉足輕重的地步,由於UI是Android的整個門面展現,而UI的展現是交由消息機制來處理。Android不容許在子線程中進行UI處理,由於這樣會引起多線程的安全問題,而解決這個問題則須要作加鎖等操做,這樣會致使效率低下,形成UI不流暢等問題,這是萬萬不可接受的。java

說到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異常app

Can't create handler inside thread xxx that has not called Looper.prepare() 由於沒有調用Looper.preapre()方法,因此在xxx這個線程中不能建立Handler對象less

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

1.2 Looper的初始化工做

首先看下Looper類的構造方法async

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

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

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對象,若是是則拋異常:oop

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的構造方法,其中構造方法二、三、五、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消息機制源碼分析基本上已經結束了,因爲技術水平有限及時間倉促,不免會有錯誤之處,還懇請指點出來,共同窗習進步!

相關文章
相關標籤/搜索