消息傳遞和同步屏障機制全面解析

一、消息機制原理

Handler消息機制老生常談了,必備八股之一。但是每次看都有新收獲,故好好總結(jié)一下Handler相關(guān)知識。

1.1 基本概念

1、Handler

用于發(fā)送和處理消息的類,有多種重載的構(gòu)造方法,通過一系列sendXXXpostXXX方法來發(fā)送消息到消息隊列,然后通過實現(xiàn)Handler.Callback接口或重寫handleMessage方法處理消息

2、MessageQueue

消息隊列,它是一個鏈表結(jié)構(gòu),用以存放handler發(fā)送的消息,實現(xiàn)了獲取消息的方法next()和移除消息及消息處理回調(diào)的方法(removeXXX系列方法)

3、Message

消息,承載一些基本數(shù)據(jù),消息隊列存放對象。維護了一個消息對象池,可以復(fù)用消息,避免創(chuàng)建太多消息對象占用過多內(nèi)存,導(dǎo)致APP卡頓。

消息分類:


在這里插入圖片描述

4、Looper

消息機制的靈魂,用以不斷調(diào)度消息對象并且分發(fā)給handler處理。Looper是同線程綁定的,不同線程的Looper不一樣,通過ThreadLocal實現(xiàn)線程隔離。

1.2 消息機制主流程

1、發(fā)送消息

在這里插入圖片描述

可以使用sendMessage(以及一系列 sendXXX的消息發(fā)送方法)和post方法發(fā)送即時同步消息,或通過sendXXXDelayed和postDelayed發(fā)送延遲同步消息。

如果是通過sendXXX方法發(fā)送即時或延時消息,最終都會輾轉(zhuǎn)調(diào)用到sendMessageAtTime(@NonNull Message msg, long uptimeMillis)方法,然后調(diào)用enqueueMessage方法。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;// ① 設(shè)置處理該消息的handler對象
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        // ② 設(shè)置消息類型,同步或異步
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // ③ 交由消息隊列的入隊方法
        return queue.enqueueMessage(msg, uptimeMillis);
    }

該方法主要有3個作用,注釋中的①②③分別說明了。

2、消息入隊

消息入隊最終是靠消息隊列的恩queueMessage方法完成,其代碼如下

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

            // ⑤
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

注釋中標明了5個注意點,??一一說明下:

① 消息對象必須指定target,也就是處理消息的handler對象;而且message對象的flagFLAG_IN_USE。否則將拋出異常。

②設(shè)置消息對象標志FLAG_IN_USE和時間,創(chuàng)建喚醒字段,用于標記是否需要喚醒消息隊列

③如果當(dāng)前消息隊列沒有消息或要入隊的消息when值小于對列頭消息when值,則將新消息插入到鏈表頭部。設(shè)置needWeak,它又由mBlocked變量決定,mBlocked的設(shè)置是在next()方法中,簡單來說消息隊列僅有延時消息或空隊列時,mBlockedtrue

④不滿足③的情況下,從消息鏈表頭開始遍歷,將新消息插入到第一個when值大于新消息when值的消息節(jié)點前方。

例如當(dāng)前消息隊里:100 - 30 -20 -10(數(shù)字表示消息的when值)

graph LR
A((100))
B((30))
C((20))
D((10))
A --- B --- C --- D

現(xiàn)要插入一個新消息50,那么插入后的隊列情況是:

graph LR
A((100))
B((30))
C((20))
D((10))
E((50))
A --- E --- B --- C --- D

⑤是否需要喚醒,需要則調(diào)用native方法喚醒

總之,入隊方法就是讓所有消息根據(jù)when的大小盡量有序排列,when越小則越位于消息鏈表頭部

3、消息出隊

Looper的loop()在一個死循環(huán)中不斷獲取消息,獲取到消息就分發(fā)給handler處理,獲取消息通過MessageQueue#next()方法,這個方法邏輯較多且都比較重要,下面會詳細說明。

 Message next() {
        ......
        // ①
        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;
        }
    }

pendingIdleHandlerCount表示IdleHandler的數(shù)量。

nextPollTimeoutMillis表示消息隊列休眠的時間,是個阻塞方法。 -1表示無限阻塞,0表示不阻塞

③ 實現(xiàn)阻塞的native方法,可通過nativeWake方法喚醒

④ 針對同步屏障機制的處理,前文已經(jīng)說了普通消息在入隊前一定會設(shè)置target屬性,唯獨有種方式不會,即postSyncBarrier方法發(fā)出的同步屏障消息是不會設(shè)置target屬性的,同步屏障相關(guān)內(nèi)容后面會詳細介紹,這里只要了解普通的同步消息不會走到這步即可。

⑤ 對于同步消息,從此步開始真正去獲取消息對象。首先明確下代碼里的幾個對象含義:mMessage始終表示消息鏈表頭部,p表示當(dāng)前節(jié)點,prevMsg表示p節(jié)點的前一個節(jié)點。

  • 對于即時消息,設(shè)置mBlocked=false,表示不阻塞。同步消息的prevMsg始終為null,所以從頭結(jié)點開始遍歷,獲取當(dāng)前節(jié)點并返回。

  • 對于延時消息,計算延時時間,然后走到⑥,若now < mMessages.when表示還沒到延時消息執(zhí)行時間,然后會走到if(pendingIdleHandlerCount <= 0)中,設(shè)置mBlocked=true,然后開始下次循環(huán),又走到③處,nextPollTimeoutMillis不等于0,于是阻塞。

⑥ 用于計算IdleHandler個數(shù),初始化IdleHandler數(shù)組。IdleHandler是用于在消息隊列空閑時處理一些任務(wù),適用于一些不緊急非高優(yōu)的任務(wù),后面也會詳細介紹。

⑦ 重置pendingIdleHandlerCountnextPollTimeoutMillis

4、消息分發(fā)

前文說了Looper的loop方法不斷獲取消息并分發(fā),分發(fā)的關(guān)鍵代碼就是

public static void loop() {
                ......
        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
            //②
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;
                        ......
            try {
                // ③
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ......
            // ④
            msg.recycleUnchecked();
        }
    }

① 獲取Looper對象,如果為空的話拋出異常。

② 可以通過Looper#setMessageLogging方法設(shè)置打印器,用來輸出一些開發(fā)者需要的信息,通常在性能監(jiān)控上需要獲取這些信息來評估優(yōu)化效果。

③ 分發(fā)消息給handler處理,target就是在消息入隊時設(shè)置的handler對象。

④ 回收消息對象

步驟③將消息分發(fā)給了對應(yīng)handler,看下dispatchMessage方法的實現(xiàn)

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

代碼很清晰,首先如果設(shè)置msg.callback,就調(diào)用handleCallback方法。那么msg.callback在哪里設(shè)置的呢?找到賦值的地方,發(fā)現(xiàn)postpostDelayed方法

public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

傳入了getPostMessage方法,繼續(xù)看該方法

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

現(xiàn)在明了了,正是這里設(shè)置了msg.callback,并且值就是post的參數(shù),一個runnable對象。

看下handleCallback代碼

private static void handleCallback(Message message) {
        message.callback.run();
    }

其實就是執(zhí)行了post傳入的runnable參數(shù)的run方法。

如果不是通過post方式發(fā)送的消息,就會走到else邏輯里。首先判斷是否實現(xiàn)了Handler.Callback接口,可在handler的構(gòu)造函數(shù)傳入,設(shè)置了則調(diào)用Handler.Callback接口的handleMessage方法。

否則調(diào)用HandlerhandleMessage方法,它是一個空方法,需要開發(fā)者重寫來實現(xiàn)業(yè)務(wù)邏輯。

總結(jié):消息的分發(fā)執(zhí)行順序就是post#run方法 -> Handler.Callback.handlerMessage方法 -> Handler#handlerMessage方法

至此,Handler消息的發(fā)送、入隊出隊、以及分發(fā)執(zhí)行的全流程就闡述完畢了,路徑還是很清晰的。但是依然遺留了一些問題,比如同步屏障、IdleHandler等,所以我們繼續(xù)(:dog:)。

二、同步屏障

我們知道無論是應(yīng)用啟動還是屏幕刷新都需要完整繪制整個頁面內(nèi)容,目前大多數(shù)手機的屏幕刷新率為60Hz,也就是耳熟能詳?shù)?6ms刷新一次屏幕。那么問題來了,如果主線程的消息隊列待執(zhí)行的消息非常多,怎么能保證繪制頁面的消息優(yōu)先得到執(zhí)行,來盡力保證不卡頓呢。

前文分析了整個消息傳遞處理機制,有一個可疑地方,就是在取消息時。2個疑點

  • 消息的target屬性為null
  • 消息被設(shè)置為了異步消息
                ......                              
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // *
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }

                if (msg != null) {
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        ......
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

在標*處,發(fā)現(xiàn)有個循環(huán)不斷過濾掉同步消息,發(fā)現(xiàn)進入條件是target對象為null,而正常情況下入隊的消息都會設(shè)置target。

從應(yīng)用啟動入手,頁面啟動過程不詳述了,大體調(diào)用鏈路是ViewRootImpl#setView -> ViewRootImpl#requestLayout -> ViewRootImpl#scheduleTraversals

看下scheduleTraversals方法代碼

 void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // ①
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

調(diào)用了消息隊列的postSyncBarrier方法,進去看看

        /**
        * @hide  
        */
        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;
        }
    }

① 發(fā)現(xiàn)創(chuàng)建了Message對象,但沒有設(shè)置target屬性。通過前面對handler的分析知道,loop方法分發(fā)給handler執(zhí)行完后會回收message對象,即msg.recycleUnchecked();,它會將message對象的所有屬性置空。

② 這一步跟普通的消息入隊目的一樣,就是把這個同步屏障消息按照when值大小插入到鏈表,when越大越靠近鏈表尾部。由于同步屏障消息設(shè)置的when是系統(tǒng)啟動以來的時間,非常長,所以一般來說同步屏障消息基本都插入在尾部。

第一個問題什么消息的target是null,那就是postSyncBarrier發(fā)送的同步屏障消息

設(shè)置同步屏障后代碼繼續(xù)執(zhí)行,執(zhí)行 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

一直深入postCallback查看,發(fā)現(xiàn)執(zhí)行到了postCallbackDelayedInternal方法

private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        ......  

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                // *
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

創(chuàng)建了真正繪制頁面的消息對象,并且調(diào)用setAsynchronous()將消息設(shè)置為了異步*

所以第二個問題什么時候設(shè)置消息為異步也知道了。

總結(jié):對于異步消息,Looper會遍歷消息隊列找到異步消息執(zhí)行,確保像刷新屏幕等高優(yōu)任務(wù)及時得到執(zhí)行。同步消息得不到處理,這就是為什么叫同步屏障的原因。當(dāng)使用了同步屏障,記得通過removeSyncBarrier移除,不然同步消息不能正常執(zhí)行。

當(dāng)然,正常情況開發(fā)者也不能手動發(fā)送和移除同步屏障,因為它們都被hide注釋了。****不過了解這一機制和其中蘊含的編程思維還是很有裨益的

三、IdleHandler

IdleHandler提供了一種在消息隊列空閑時執(zhí)行的某些操作的手段,適用于執(zhí)行一些不重要且低優(yōu)先級的任務(wù)。它的使用也很簡單調(diào)用MessageQueue#addIdleHandler方法將任務(wù)添加到消息隊列,然后隊列空閑時會自動執(zhí)行,可通過removeIdleHandler方法或自動回收。

消息隊列通過一個ArrayList來儲存添加的IdleHandler任務(wù)。

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
// 臨時儲存IdleHandler任務(wù)
private IdleHandler[] mPendingIdleHandlers;

調(diào)用IdleHandler任務(wù)的位置在MessageQueue#next()方法中,無關(guān)代碼已省略

Message next() {
        // ①
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            ......
            synchronized (this) {
                // 省略部分為獲取消息對象的過程
                .....
                  
                // 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);
                    }
                }
            }
            // ⑤
            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;
        }
    }

① 每次Looper調(diào)用next方法時,先將IdleHandler臨時數(shù)組的大小pendingIdleHandlerCount重置為 -1。

② 首次運行時pendingIdleHandlerCount < 0肯定成立,如果當(dāng)前消息隊列為空或者只有延時消息時,認為此時隊列空閑可以執(zhí)行IdleHandler任務(wù)了。令pendingIdleHandlerCount為已添加的IdleHandler任務(wù)個數(shù)。

mPendingIdleHandlers是一個數(shù)組,首次執(zhí)行時可定為空,所以初始化數(shù)組,數(shù)組大小最小為4。并且將mIdleHandlers列表中的任務(wù)復(fù)制到這個臨時數(shù)組。

④ 循環(huán)臨時數(shù)組執(zhí)行IdleHandler任務(wù),任務(wù)從mPendingIdleHandlers數(shù)組中取出后,會置空,釋放對handler對象的引用。然后調(diào)用queueIdle()真正執(zhí)行IdleHandler任務(wù)。

queueIdle()是一個接口方法,需要自己實現(xiàn)業(yè)務(wù)邏輯。另外它的返回值決定是否要自動刪除該IdleHandler任務(wù),返回true該任務(wù)執(zhí)行后將不會被刪除

⑤ 重置mPendingIdleHandlers = 0,開啟下次循環(huán)。

四、消息對象池

消息是數(shù)據(jù)的載體,我們知道創(chuàng)建消息對象是一般不提倡去new一個對象,而是調(diào)用Message的一系列obtain的重載方法,原因就是因為可以復(fù)用已創(chuàng)建的Message對象,避免創(chuàng)建過多對象占據(jù)大量內(nèi)存。既然是復(fù)用,那么一定存在某種數(shù)據(jù)結(jié)構(gòu)去保存對象,這就是消息對象池,使用的是鏈表結(jié)構(gòu)。

3.1 創(chuàng)建Message對象

消息對象池有幾個重要屬性,分別是:

        // 同步對象
        public static final Object sPoolSync = new Object();
        // 鏈表頭節(jié)點
    private static Message sPool;
        // 消息池大?。ㄦ湵黹L度,表示消息個數(shù))
    private static int sPoolSize = 0;
        // 消息池最大容量
    private static final int MAX_POOL_SIZE = 50;

看下obtain方法

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

代碼很簡潔,獲取消息對象是同步操作,從頭節(jié)點開始,如果頭結(jié)點不為空,取得頭結(jié)點。然后指針后移,并且消息池大小減1。否則的才通過new方式創(chuàng)建新對象。

3.2 回收Message對象

可以調(diào)用recycle方法回收消息對象

public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

如果消息標記了FLAG_IN_USE標志,不可回收。然后真正回收的方法是recycleUnchecked();

void recycleUnchecked() {
        // 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 = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

可見回收消息,首先就是將其成員變量全部重置為初始值,然后在消息池大小不超過限制容量時,讓將要被回收節(jié)點的next指向頭結(jié)點,再把頭指針移到當(dāng)前節(jié)點,容量加1。

五、總結(jié)

本文從Handler消息機制出發(fā),分析了消息從發(fā)送、調(diào)度和分發(fā)處理的全過程。在此過程中,發(fā)現(xiàn)涉及到了同步屏障、IdleHandler等知識點,并對其做了分析和說明。有些東西可能在平時開發(fā)中用不上,例如消息屏障,但其蘊含的編程思想也是十分值得學(xué)習(xí)借鑒的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容