天天看點

Android消息機制一、介紹二、輪詢三、事件入隊四、事件的處理Over。

一、介紹

(1)Message:

首先需要知道的是Android程式是由事件來驅動的,事件的具體形式,也就是可以代表一個事件的資料結構,就是Messager,它可以攜帶的資訊包括了事件類型,事件發生的時間,事件描述,還可以攜帶額外的資料,可以設定處理事件的回調方法等,它的元素就這樣代表了一件需要Android程式處理的具體事件。

(2)MessageQueue:

首先需要知道的是這是一個隊列資料結構,先進先出,而且是一個連結清單,從哪裡看出呢?Meseager中有一個指向Meseager 類型的next成員變量,這很明确了吧,MessageQueue就是一個節點為Message的單向連結清單。總的來說,就是當一個事件傳遞到Android程式中的時候,這個事件會先添加到MessageQueue這個事件隊列中,之後程式再從MessageQueue中一個一個地取出Message來處理事件。當然,先添加的先處理。

(3)Looper:

上面說到,App程式會從MessageQueue中取出Message來處理事件,那麼它是怎麼取的呢?就是通過Looper來取的,總的來說,Looper就是這個MeseagerQueue的輪詢機制,通過Loop不斷的死循環地從meassageQueue中取出事件,說到這裡,讀者或許會有疑問了,死循環?我去,這家夥不就卡死線程了嗎?别急,這個東西後面再展開說。

(4)Handler:

這個東西可以認為是事件的總指揮,我們可以通過它來發送事件給程式來處理,當程式需要處理Message事件的時候,也是通過它來處理事件的,具體的後面再展開講解。

二、輪詢

從Loop.loop方法開始輪詢,點進去看。

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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }
           

看一些關鍵的代碼。首先

變量me指向了目前線程的loop,之後

final MessageQueue queue = me.mQueue;

           

通過目前線程的loop對象,拿到了目前線程中的事件隊列,并用一個MessageQueue類型的變量指向了它。

接下來是一個for死循環,說明loop内部是在不停的執行取出事件的操作的。

Message msg = queue.next(); // might block
           

在循環裡面,調用了queue.next()方法來取出消息隊列分鐘下一個需要處理的事件,之後有這麼一句代碼

target變量是一個Handler類型的變量,它就是這個事件對應的句柄,然後調用了這個Handler對象的dispatchMessage方法來讓事件被Handler處理掉

這裡關鍵的點是queue.next(); // might block,它是輪詢過程中第一個關鍵點,進一步看一下它

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.
        final long ptr = mPtr;
        if (ptr == ) {
            return null;
        }

        int pendingIdleHandlerCount = -; // - only during first iteration
        int nextPollTimeoutMillis = ;
        for (;;) {
            if (nextPollTimeoutMillis != ) {
                Binder.flushPendingCommands();
            }

            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;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    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.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a 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 = -;
                }

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

                // 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 < 
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= ) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

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

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

                boolean keep = false;
                try {
                    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  so we do not run them again.
            pendingIdleHandlerCount = ;

            // 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 = ;
        }
    }
           

先看一下這個方法裡很關鍵的一些點,首先看Message msg = mMessages;,這裡出現了一個全局變量mMessages,這個變量十分關鍵,消息隊列是一個連結清單結構的隊列,而這個mMessages就是這個連結清單的頭結點,簡而言之就是說這個mMessages變量指向的Messager對象,就是這個線程下一個需要處理的事件。

nativePollOnce(ptr, nextPollTimeoutMillis);這句代碼是調用了native層的方法,這個方法十分關鍵,但是我們後面再看它。先看synchronized同步代碼塊裡做了什麼事情。

先看這一段代碼

if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a 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;
                    }
           

跟着代碼走,now是一個long類型,它的值是系統目前的時間,判斷目前系統時間和下一個需要被處理的事件的觸發時間,(1)如果目前事件還沒有達到下一個事件被觸發的事件,那麼把目前時間和下一個需要被處理的事件的觸發時間之間的時間差指派給nextPollTimeoutMillis變量,然後再回到循環中,調用nativePollOnce(ptr,nextPollTimeoutMillis);方法。(2)否則,也就是目前時間已經達到下一個事件被觸發的時間,先把mBlocked = false;,這個變量的含義是表明是否需要被阻塞,這個把它指派為false,表明不再需要别阻塞。并且把msg傳回,前面我們知道msg其實就是成員變量mMessage,也就是說這個輪詢的過程把mMessage傳回回去進行處理了,這裡再次驗證了mMessager是消息隊列的頭節點,也就是說mMessage變量指向的Message對象,就是消息隊列中下一個等待被執行的事件。

到這裡為止java層的輪詢邏輯基本已經介紹完了,如果細心的話其實這裡應該可以看出這裡可能存在一個問題,就是這裡是個for死循環,我們知道如果一個死循環裡不停的做邏輯操作的話,那麼會造成這個線程搶占了大量的計算機資源,會造成裝置卡頓。顯然,Android系統裡是不可能存在這樣的問題的,那麼Android是怎麼解決的呢,關鍵就在上面的那個nativePollOnce(ptr,nextPollTimeoutMillis);方法裡。這個方法将會進入到JNI層裡面,通過C++代碼來解決這個問題。

後面不再展示代碼了,展示代碼調用鍊其實是沒有什麼意義的,我們隻需要了解它的設計思想和關鍵的邏輯思想即可,當你了解了它的設計思想和關鍵邏輯之後,再去了解代碼就是水到渠成的事情,和看圖填字差不多了。

在JNI層中,建立了一個管道pipe,然後用兩個檔案描述符fd分别指向了讀端和寫端的句柄,但是,這個管道并不是用來傳輸關鍵資料的,它隻是用來阻塞線程,節省系統資源用的,其實當寫端往管道中寫入資料的時候,隻是寫入了一個位元組的資料,具體是怎麼做的呢,接着往下看。

隻有管道還不夠,因為如果你又使用一個死循環來不停的讀取讀端資料,那麼顯然的又造成了一個死循環,這樣和節省系統資源的目的相違背了,這時候,就需要用到一個關鍵的東西——epoll,它可以同時監聽多個檔案描述符的變化,當被監聽的檔案描述符都沒有發生變化時,也就是epoll正在執行監聽的時候,它會把目前線程給阻塞掉。當被監聽的檔案描述符發生變化時,也就是說當寫端往管道中寫出資料之後,epoll會喚醒目前線程,以便線程可以對資料進行處理,當然,這裡其實主要利用的是epoll的可以阻塞和喚醒線程的特性。同時需要注意的是,當寫端往管道中寫入資料以喚醒線程的時候,它其實隻寫入了一個位元組的資料,然後當線程喚醒之後,讀端會把這一個位元組的資料從管道中讀取出來,并且沒有對這個位元組的資料做任何處理,不要忘了上面說的,真正需要被處理的事件是Loop中mMessage這個成員變量指向的事件對象。

這裡可以引入另外一個問題,系統利用了epoll阻塞了線程,那麼阻塞是什麼一個概念呢?要解釋這個問題,首先需要知道的是linux對系統裡的多個線程是采用了排程算法來排序執行的,也就是說,linux内部一般都會同時存在着多個線程,其實這些所有的線程并不是同時被運作的,linux核心會給需要被執行的線程輪流配置設定被執行的時間片,舉個簡單的例子,比如現在有ABCDE這5個線程需要被執行,假設CPU隻有一個核心,那麼5個線程就是先執行A,執行一個時間片的時間,然後再執行線程B一個時間片,接下來再執行線程C一個時間片,一直到D,E,然後再回來執行A,一直如此循環,這是這些多個任務間的切換非常迅速,是以看起來系統内的多個任務是同時被執行的。當然,時間片的配置設定遠不止那麼簡單,核心會考慮線程的優先級(權重數值越小,優先級越高)等因素來計算出時間片,當然一般來說,優先級越高的獲得的時間片就越多,然後根據時間片來配置設定CPU資源,比如,有ABC三個線程,A線程配置設定了100個時間片,B線程配置設定了110個時間片,C線程配置設定了50個時間片,那麼他們的執行順序是,B線程先執行10個時間片,這時候B和A都隻剩下100個時間片,然後B和A輪流執行一個時間片,當B和A的時間片被執行到隻剩下50個的時候,ABC都是隻剩下50個時間片,這時候ABC這三個線程才開始輪流被執行時間片。

介紹了核心中任務排程和時間片的概念之後,當線程被阻塞的時候,線程會告訴系統核心,我暫時不再需要CPU資源了,這樣核心就可以把它原來占用的CPU資源用來執行其他任務,其中這又涉及到了使用者态和核心态的切換。一般來說,系統中運作的程序和線程是應用所有的,是以為了系統安全,不可能随便給以這些程序線程作業系統内部的權限的,這種由使用者建立的程序線程被稱為使用者态,而當線程需要被阻塞和喚醒的時候,這跟系統内部的任務排程有關,是以這個過程中,會需要從使用者态切換到核心态中,隻有核心态才有權利去修改系統内部,其實如果頻繁的在使用者态和核心态中切換的話,這也是一筆很大的系統資源開銷,是以,除了監聽讀端這種由外部喚醒的方式外,當消息隊列内部的待處理事件達到設定的被處理時間時,需要有内部喚醒的機制,顯然的内部喚醒也不可能采用死循環的方式,即使可以采用間隙地阻塞喚醒的方式來一定程度減少系統資源開銷,但是這種死循環的方式也隻是治标不治本罷了,而且别忘了,頻繁的從使用者态切換到核心态再切換到使用者态也是一筆不小的開銷。還記得上面提到的nativePollOnce(ptr,nextPollTimeoutMillis);這個方法嗎,裡面有一個參數nextPollTimeoutMillis,這個參數就是為了解決這個問題的,在使用epoll監聽的時候,會傳入一個long型參數,這個參數的值就是nextPollTimeoutMillis,當nextPollTimeoutMillis的時間過去後,即使被監聽的讀端檔案描述符沒有發生變化,線程也會從阻塞狀态中喚醒,上面說過,nextPollTimeoutMillis的值就是目前時間距離下一個需要被處理的事件的執行時間的時間差,這樣,總結一下,消息隊列機制使用了管道和epoll來阻塞線程,以便線程不需要空轉來占用CPU資源,而喚醒的時候會分兩種情況,一是當外部喚醒時,可以向管道的讀端随便寫入資料(不一定隻能是一個位元組,讀端會一次性把管道内的資料全部讀取出來,當然,寫端寫資料的時候就隻寫了一個位元組),這時候epoll監聽到讀端變化,就會喚醒線程。二是當内部喚醒時,也是就沒有外部因素通過寫端向管道寫入資料時,因為epoll在設定監聽wait的時候,傳入了一個nextPollTimeoutMillis的long型參數,是以epoll會在nextPollTimeoutMillis毫秒後自動喚醒線程,即使這時候讀端并沒有監聽到資料。

說了那麼多,總結一下輪詢過程。首先,Looper中有一個死循環不斷的從消息隊列中取Messager,消息隊列是一個連結清單,Looper持有這個這個連結清單的頭節點,由成員變量mMessager指向它,每次循環完成後會處理掉頭節點的Messager,頭節點再指向下一個節點,其次,死循環為什麼不會造成線程大量占用CPU,因為死循環裡會調用到一個系統機制讓其阻塞住,阻塞是什麼意思?為什麼可以節省CPU資源?看上文。。。阻塞對應着喚醒機制,什麼時候喚醒呢?有幾種情況可以喚醒,(1)當阻塞的預設時間達到後,自動喚醒線程。(2)當往管道中(結合epoll)寫入資料時,會喚醒線程。線程喚醒後,則會去處理mMessager指向的事件對象,也就是消息隊列中的頭節點。那麼這裡有一個問題點上面我沒提到的,消息隊列是一個連結清單,那麼它是怎麼入隊的呢?隊列裡的順序是怎麼确定的呢?請看下一節。

三、事件入隊

常用的兩個事件入隊接口(1)Handler.post(Runnable r);(2)Handler.post(Message msg);

這兩個接口最終會調用到Handler.enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis);

差別在于post中參入的是一個Runnable,Handler會用一個Message對象把将其包裝起來,代碼如下:

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
           
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
           

queue是從Looper中擷取到的消息隊列,從這裡把消息傳入消息隊列中,然後來到MessageQueue中看它的boolean enqueueMessage(Message msg, long when)方法

boolean enqueueMessage(Message msg, long when) {
        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.");
        }

        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 ==  || 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;
    }
           

以下是關鍵代碼,一句一句來看一下都做了什麼事情,直接寫在代碼注釋裡比較直覺。

//事件的觸發時間,用來說明這個事件希望多久以後才會處理的,還記得post中可以選擇傳入一個long型參///數嗎,一般可以用來做延時定時操作,
            //這個時間代表的是真實流逝時間,不是手機時間,從MessageQueue的next方法中也可以看出,從now變量和
            //msg.when來比較的,再看now變量,是通過final long now = SystemClock.uptimeMillis();這句話擷取的
            //這就說明了是用真實流逝時間來做時間的參照系的
            msg.when = when;

            //還記得mMessages嗎?消息隊列的頭節點,這裡建立一個引用指向它
            Message p = mMessages;

            //這個标志是記錄是否需要馬上喚醒線程的
            boolean needWake;

            //如果還沒有頭節點,就以這個參入的事件作為頭節點
            //在這裡我們看到,if裡的這三種情況下,從needWake=mBlocked;這句話可以看出如果Looper綁定的線程當///前是阻塞的話,都需要喚醒它,為什麼呢?下面分别解釋一下這三種情況
            //1.p == null 
            //這種情況下,說明之前的消息隊列是沒有頭節點的,這個時候如果線程是在阻塞中的話,肯定是無限期阻///塞的,還記得epoll時候參入的時間參數嗎?如果連一個待處理的事件都沒有,那麼怎麼可能确定下一次自//動喚醒線程的時間呢?是以,這時候肯定是無限期阻塞直到有人主動喚醒線程的。是以這時候需要喚醒它
            //這裡應該有個疑問,喚醒了線程之後,不是會直接處理mMessage頭節點的事件嗎?還是看MessageQueue的n//ext方法中的代碼:
            //if (msg != null) {
            //        if (now < msg.when) {
            //            // Next message is not ready.  Set a timeout to wake up when it is ready.
            //           nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
            //        } else {
            //msg是和mMessage一樣指向的是隊列的頭節點,nextPollTimeoutMillis是預設的阻塞時長,也就是說如果///發現頭節點事件的觸發時間還沒有達到,就會讓線程預設阻塞頭節點事件的時間距離現在的時間的時間差
            //2.when == 0
            //同樣的,當事件的觸發時間為0的時候,表示這個時間要馬上觸發,是以這個時間插入到隊列的頭中,接下//來發生的事情和第一點是一樣的。
            //3.when < p.when
            //這個時候隊列不為空,但是新加入的事件比隊列中頭節點的事件需要更早的被處理,那麼這個時候新加入///的事件就需要插隊到目前的頭節點之前,也就是說新加入的事件作為消息隊列中新的頭節點。接下來的
            //處理和第一點是一樣的。
            if (p == null || when ==  || 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);
            }
           

四、事件的處理

從nativePollOnce往回看,MessageQueue.next方法傳回一個Message對象,這個Message對象就是需要被處理的事件,再看是誰調用了MessageQueue.next,終于回到了起點Looper.loop方法,再看一下它關鍵的代碼。同樣的,内容直接寫在代碼注釋上。

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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            //線程先阻塞,被喚醒後傳回消息隊列的頭節點Message
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            //target是一個Handler對象,也就是說事件的處理通過Handler.dispatchMessage方法來分發
            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }
           

代碼注釋說到事件的處理通過Handler.dispatchMessage方法來分發。我們看一下這個方法

/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
           

第一步,判斷是否有msg.callback,有的話就執行,這個callback是一個Runable接口,還記得Handler.post方法嗎,參入的是一個Runnable對象,callback就是這個Runnable對象。如果第一步沒有被執行,到了第二步可以看到,除了可以給Message事件設定callback之外,也可以對Handler對象設定回調mCallback,如果給Handler對象設定了回調,就會讓這個mCallback來處理事件。如果第二步也沒有被執行,那麼第三步,最後一步,就會讓這個Handler對象的handleMessage方法去處理事件,我們可以通過重寫handleMessage方法來自定義處理事件的流程。

Over。