Android消息處理機制

上次文章談到ThreadLocal的原理。今天接著談談Android的消息處理機制。主要使用了四個類,Looper,Message,MessageQueue,Handler。先簡單介紹一個這個四個類的作用,再從源碼角度去分析。

  • Looper
    從名字可以猜測,他是不斷循環(huán)。思考一個問題,在我們new Thread創(chuàng)建一個線程并start之后,在執(zhí)行完中間的Runnable體之后,線程便結束他的工作,等待被回收。為什么Android主線程能一直運行,并且等待著處理UI事件和其他消息。我們猜測由于主線程被某種機制阻塞,使得主線程不能退出,一直在執(zhí)行狀態(tài)。而Looper就提供了這種作用。
  • Message
    被傳遞的消息體
  • MessageQueue
    存儲消息的隊列
  • Handler
    處理消息的處理器

Looper

image.png

有幾個主要的變量,sThreadLocal,sMainLooper,mQueue,mThread


image.png

image.png
  1. prepare()方法往sThreadLocal這個類靜態(tài)變量里面放置了一個Looper對象,在set之前先判斷是否有值,如果有則拋出異常。從ThreadLocal的學習我們知道,ThreadLocal的set()和get()方法會區(qū)分線程,也就是說線程和Looper對象會一一對應。

  2. Looper的構造函數(shù)需要一個quitAllowed參數(shù),在構造函數(shù)里,將該參數(shù)傳給了MessageQueue,并實例化該對象賦值給mQueue變量。mThread變量表示的是,實例化Looper時,即調用Looper.prepare()方法時所在的線程。

  3. 同樣prepareMainLooper()則是做了更多一步操作,設置
    sMainLooper,myLooper()是從sThreadLocal里面取值。為什么調用myLooper()得到sMainLooper呢。因為prepareMainLooper()方法是系統(tǒng)調用的,系統(tǒng)會在應用啟動時調用設置sMainLooper這個靜態(tài)變量。

image.png

Looper還有兩個重要的方法,loop()以及quit()。

  1. loop()方法會取到當前線程對應的Looper對象,然后用for()循環(huán)不斷從Looper對象的mQueue里面取出Message對象,dispatchMessage這些對象。在msg為空時,跳出循環(huán)。結束loop方法。
  2. quit()方法則是調用mQueue的quit方法。

Message

image.png

幾個常用的public變量:what,arg1,arg2,obj


image.png
  1. replyTo 和 sendingUid 是Messager使用時需要用到,暫時不需要關注。

  2. data的作用和obj類似,區(qū)別在于obj在跨進程通信時,只能存放系統(tǒng)的Parcelable對象,其他類型的數(shù)據(jù)可以通過data傳輸。

  3. target是一個Handler對象,在整個消息機制中需要使用到,后續(xù)再看。
    Message對象存儲的信息可以是存儲在arg1,arg2,obj或是data中,也可以是一個存儲在callback的Runnable對象。

  4. next,指向Message對象的引用,MessageQueue中會使用到。

  5. flags 消息的int型標志位,最后一位是否使用,倒數(shù)第二位是否為同步消息


    image.png

    Message提供了一系列簽名不同的obtain方法,其他需要傳參的方法都是在調用obtain()之后,對返回Message對象設置成員變量。obtain()方法會判斷sPool變量是否為空,如果為空就返回一個新的Message對象。如果不為空則返回sPool指向的Message對象,將該對象使用標志flags置為未使用,將next置為null(無法通過獲取到的message對象訪問下一個可使用的message),sPoolSize--,并且將sPool指向message.next。

image.png

recycle()方法用來回收被使用過的Message對象,在回收前會先檢查當前對象是否在使用,然后清除message所有字段,將使用標志置為被使用中,放入消息池中。

總結來說,obtain()是從message鏈表取出可被使用的message對象,這個鏈表是通過Message.next字段實現(xiàn)的,通過recycle()回收對象,obtain()再次使用,避免了新對象的生成。通過Message的sPool靜態(tài)字段,維護這個鏈表。

MessageQueue

MessageQueue是一個按時間排序的消息隊列。嚴格來說他并不是消息隊列,插入消息會按照時間排序插入而不是從隊尾插入,取出消息在設置同步障礙時,也不是從隊頭取出。


image.png

MessageQueue構造器使用了quitAllowed參數(shù),該參數(shù)只在quit()方法里面用到。從拋出的異常信息我們知道,主線程對應的MessageQueue對象不允許調用quit方法。只有主線程對應的MessageQueue傳遞了false,其他線程傳遞的應該都是true。MessageQueue對象的創(chuàng)建依賴于native的實現(xiàn)。在創(chuàng)建時候獲取了一個mPtr指針,在quit的時候,用該指針去釋放相關的資源。

Looper里面循環(huán)調用queue.next()獲取Message對象,分發(fā)Message。下面是MessageQueue的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.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                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 = -1;
                }

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

    ...

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

1.nativePollOnce的作用是阻塞nextPollTimeoutMillis毫秒的時間。

2.在msg.target為null時,返回第一個達到when的異步消息。msg.target一般不為null,只有通過postSyncBarrier插入同步障礙Message時,target才會為null,此時不處理同步消息,只處理異步消息。

3.在msg.target不為null時,取出第一個達到when的消息。

4.在mMessages里取不到新消息時,nextPollTimeoutMillis為-1,nativePollOnce會一直阻塞。此方法造成了looper里queue.next()的阻塞。直到有新消息到來,調用nativeWake(mPtr)。

5.在調用Looper.quit()時,mQuitting會導致next里面循環(huán)退出。

    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 == 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;
    }
  1. 插入消息時,會驗證target和isInUse,以及mQuitting。

  2. 將消息標志flag置為InUse。在Message.recycle()和MessageQueue.enqueueMessage(Message msg, long when)時,都會判斷該字段。

  3. 在鏈表為空,或when=0,或when小于鏈表里message第一個when,將新message插入頭部。喚起由nativePollOnce造成的阻塞。

  4. 在鏈表不為空。(當前被阻塞,上一條是同步障礙消息,當前是異步消息)->喚起阻塞。根據(jù)when插入鏈表,越大的時間,插入到越后面。整條鏈表是按時間排序的

image.png

hasMessages(...)方法根據(jù)里面的參數(shù),判斷鏈表里面是否有對應消息。這個消息包括兩種,正常的消息以及runnable消息。
removeMessages移除對應的某一種消息
removeCallbacksAndMessages則是移除復合條件的所有消息
removeAllMessagesLocked移除所有消息
removeAllFutureMessagesLocked移除當前時間的之后的所有消息。

Handler

handler是message.target所對應的對象。在Looper.loop()中,在通過MessageQueue.next()取到msg之后,調用msg.target.dispatchMessage(msg)。我們看看Handler的相關方法


image.png
    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();
    }
    ...
    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

1.Handler的構造器參數(shù)Callback會賦值給mCallback。根據(jù)當前線程獲取mLooper,以及mLooper里面的MessageQueue。如果沒有Looper則會報錯,這也就是常見的在子線程new Handler()會報錯,因為子線程默認沒有Looper。

2.在dispatchMessage時,先判斷當前message是不是runnable消息。如果是,則調用message.callback.run()執(zhí)行;如果不是,先嘗試使用mCallback.handleMessage()處理消息,如果mCallback為空,或者mCallback.handleMessage()返回false,調用Handler.handleMessage(Message msg)方法。該方法是空方法,handler子類自行實現(xiàn)。

3.Handler的obtainMessage系列方法直接調用Message的obtain的對應方法。post系列方法,是將runnable對象包裝成Message對象,調用MessageQueue.enqueueMessage()方法將消息放入待處理隊列中;
send系列方法是將Message對象調用MessageQueue.enqueueMessage()方法放入待處理隊列中。hasMessages和removeMessages方法是直接調用MessageQueue對應的方法。

4.Handler里面有個mAsynchronous成員變量,這個變量只有在調用Handler.createAsync靜態(tài)方法創(chuàng)建新Handler時候,賦值為true,其他時候都為false。該值為true時,在enqueueMessage的時候,會將Message對象設置成異步消息,也就是說,異步的Handler發(fā)送的消息,都是異步消息。

Android主線程啟動

 #ActivityThread類(挑選出部分代碼)
public final class ActivityThread extends ClientTransactionHandler {
    final Looper mLooper = Looper.myLooper();
    final H mH = new H();

    final Handler getHandler() {
        return mH;
    }

    class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        ...

        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    case BIND_APPLICATION: return "BIND_APPLICATION";
                    ...
                }
            }
            return Integer.toString(code);
        }
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case BIND_APPLICATION:
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case BIND_SERVICE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
                    handleBindService((BindServiceData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                ...
            }
            Object obj = msg.obj;
            if (obj instanceof SomeArgs) {
                ((SomeArgs) obj).recycle();
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }
    }
 
    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

1.ActivityThread.main()方法是android應用主線程的入口,該方法在ZygoteInit里被調用。

2.先調用Looper.prepareMainLooper(),創(chuàng)建一個和主線程對應的Looper。

3.創(chuàng)建ActivityThread實例,啟動相關的類。ActivityThread.mH 是一個繼承自Handler子類對象,從handleMessage方法,看到了“serviceCreate”,“serviceBind”等方法。這個類是用來處理來自系統(tǒng)的一些消息事件。

4.Looper.loop()循環(huán)從MessageQueue里取出消息,處理消息,阻塞主線程。

總結

1.Looper通過ThreadLocal機制,保證該對象的實例和線程一一對應。在一個新開啟的線程里,通過Looper.prepare(),創(chuàng)建實例。在實例化時,MessageQueue也被實例化出來,作為mQueue成員變量保存在Looper對象里。Looper,MessageQueue,Thread三者一一對應。

2.Looper.loop()開啟,Looper所在線程將一直從MessageQueue里面取出Message,調用Message.target對應的Handler對象去處理消息。

3.Handler的創(chuàng)建默認取當前線程的Looper,也可以傳入一個已有的Looper。Handler對象可以往MessageQueue里插入消息,等到了處理時間,該Handler對象便會去處理消息。Handler對象可以在任意線程post或sendMessage,最后會在Looper所在的線程處理消息。

參考:
【源碼分析】關于MessageQueue,這一篇就夠了!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容