消息傳遞和同步屏障機(jī)制全面解析

一、消息機(jī)制原理

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

1.1 基本概念

1呀伙、Handler

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

2赠摇、MessageQueue

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

3掘鄙、Message

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

消息分類:


在這里插入圖片描述

4榨惠、Looper

消息機(jī)制的靈魂掉奄,用以不斷調(diào)度消息對(duì)象并且分發(fā)給handler處理狂芋。Looper是同線程綁定的珍剑,不同線程的Looper不一樣,通過(guò)ThreadLocal實(shí)現(xiàn)線程隔離塌衰。

1.2 消息機(jī)制主流程

1舔箭、發(fā)送消息

在這里插入圖片描述

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

如果是通過(guò)sendXXX方法發(fā)送即時(shí)或延時(shí)消息,最終都會(huì)輾轉(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對(duì)象
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        // ② 設(shè)置消息類型,同步或異步
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // ③ 交由消息隊(duì)列的入隊(duì)方法
        return queue.enqueueMessage(msg, uptimeMillis);
    }

該方法主要有3個(gè)作用溃卡,注釋中的①②③分別說(shuō)明了。

2、消息入隊(duì)

消息入隊(duì)最終是靠消息隊(duì)列的恩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;
    }

注釋中標(biāo)明了5個(gè)注意點(diǎn)俭缓,??一一說(shuō)明下:

① 消息對(duì)象必須指定target坷衍,也就是處理消息的handler對(duì)象;而且message對(duì)象的flagFLAG_IN_USE侈玄。否則將拋出異常爬橡。

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

③如果當(dāng)前消息隊(duì)列沒(méi)有消息或要入隊(duì)的消息when值小于對(duì)列頭消息when值伟恶,則將新消息插入到鏈表頭部。設(shè)置needWeak,它又由mBlocked變量決定俱尼,mBlocked的設(shè)置是在next()方法中,簡(jiǎn)單來(lái)說(shuō)消息隊(duì)列僅有延時(shí)消息或空隊(duì)列時(shí),mBlockedtrue

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

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

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

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

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

⑤是否需要喚醒牺蹄,需要?jiǎng)t調(diào)用native方法喚醒

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

3胞皱、消息出隊(duì)

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

 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表示消息隊(duì)列休眠的時(shí)間,是個(gè)阻塞方法晶疼。 -1表示無(wú)限阻塞酒贬,0表示不阻塞

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

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

⑤ 對(duì)于同步消息蒋情,從此步開始真正去獲取消息對(duì)象埠况。首先明確下代碼里的幾個(gè)對(duì)象含義:mMessage始終表示消息鏈表頭部,p表示當(dāng)前節(jié)點(diǎn)棵癣,prevMsg表示p節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)辕翰。

  • 對(duì)于即時(shí)消息,設(shè)置mBlocked=false狈谊,表示不阻塞喜命。同步消息的prevMsg始終為null沟沙,所以從頭結(jié)點(diǎn)開始遍歷,獲取當(dāng)前節(jié)點(diǎn)并返回壁榕。

  • 對(duì)于延時(shí)消息矛紫,計(jì)算延時(shí)時(shí)間,然后走到⑥牌里,若now < mMessages.when表示還沒(méi)到延時(shí)消息執(zhí)行時(shí)間颊咬,然后會(huì)走到if(pendingIdleHandlerCount <= 0)中,設(shè)置mBlocked=true牡辽,然后開始下次循環(huán)喳篇,又走到③處,nextPollTimeoutMillis不等于0态辛,于是阻塞麸澜。

⑥ 用于計(jì)算IdleHandler個(gè)數(shù),初始化IdleHandler數(shù)組因妙。IdleHandler是用于在消息隊(duì)列空閑時(shí)處理一些任務(wù)痰憎,適用于一些不緊急非高優(yōu)的任務(wù),后面也會(huì)詳細(xì)介紹攀涵。

⑦ 重置pendingIdleHandlerCountnextPollTimeoutMillis

4铣耘、消息分發(fā)

前文說(shuō)了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對(duì)象以故,如果為空的話拋出異常蜗细。

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

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

④ 回收消息對(duì)象

步驟③將消息分發(fā)給了對(duì)應(yīng)handler吊骤,看下dispatchMessage方法的實(shí)現(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ù)鹃祖,一個(gè)runnable對(duì)象。

看下handleCallback代碼

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

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

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

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

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

至此雅潭,Handler消息的發(fā)送揭厚、入隊(duì)出隊(duì)、以及分發(fā)執(zhí)行的全流程就闡述完畢了扶供,路徑還是很清晰的筛圆。但是依然遺留了一些問(wèn)題,比如同步屏障椿浓、IdleHandler等太援,所以我們繼續(xù)(:dog:)。

二扳碍、同步屏障

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

前文分析了整個(gè)消息傳遞處理機(jī)制,有一個(gè)可疑地方趁餐,就是在取消息時(shí)喷兼。2個(gè)疑點(diǎn)

  • 消息的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;
                }

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

從應(yīng)用啟動(dòng)入手喷面,頁(yè)面啟動(dòng)過(guò)程不詳述了星瘾,大體調(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)用了消息隊(duì)列的postSyncBarrier方法,進(jìn)去看看

        /**
        * @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對(duì)象惧辈,但沒(méi)有設(shè)置target屬性琳状。通過(guò)前面對(duì)handler的分析知道,loop方法分發(fā)給handler執(zhí)行完后會(huì)回收message對(duì)象盒齿,即msg.recycleUnchecked();念逞,它會(huì)將message對(duì)象的所有屬性置空困食。

② 這一步跟普通的消息入隊(duì)目的一樣,就是把這個(gè)同步屏障消息按照when值大小插入到鏈表翎承,when越大越靠近鏈表尾部硕盹。由于同步屏障消息設(shè)置的when是系統(tǒng)啟動(dòng)以來(lái)的時(shí)間,非常長(zhǎng)叨咖,所以一般來(lái)說(shuō)同步屏障消息基本都插入在尾部瘩例。

第一個(gè)問(wèn)題什么消息的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)建了真正繪制頁(yè)面的消息對(duì)象,并且調(diào)用setAsynchronous()將消息設(shè)置為了異步*

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

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

當(dāng)然牧嫉,正常情況開發(fā)者也不能手動(dòng)發(fā)送和移除同步屏障比勉,因?yàn)樗鼈兌急籬ide注釋了。****不過(guò)了解這一機(jī)制和其中蘊(yùn)含的編程思維還是很有裨益的

三驹止、IdleHandler

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

消息隊(duì)列通過(guò)一個(gè)ArrayList來(lái)儲(chǔ)存添加的IdleHandler任務(wù)抖仅。

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

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

Message next() {
        // ①
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            ......
            synchronized (this) {
                // 省略部分為獲取消息對(duì)象的過(guò)程
                .....
                  
                // 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方法時(shí),先將IdleHandler臨時(shí)數(shù)組的大小pendingIdleHandlerCount重置為 -1撤卢。

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

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

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

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

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

四、消息對(duì)象池

消息是數(shù)據(jù)的載體驯鳖,我們知道創(chuàng)建消息對(duì)象是一般不提倡去new一個(gè)對(duì)象闲询,而是調(diào)用Message的一系列obtain的重載方法,原因就是因?yàn)榭梢詮?fù)用已創(chuàng)建的Message對(duì)象浅辙,避免創(chuàng)建過(guò)多對(duì)象占據(jù)大量?jī)?nèi)存扭弧。既然是復(fù)用,那么一定存在某種數(shù)據(jù)結(jié)構(gòu)去保存對(duì)象记舆,這就是消息對(duì)象池鸽捻,使用的是鏈表結(jié)構(gòu)。

3.1 創(chuàng)建Message對(duì)象

消息對(duì)象池有幾個(gè)重要屬性泽腮,分別是:

        // 同步對(duì)象
        public static final Object sPoolSync = new Object();
        // 鏈表頭節(jié)點(diǎn)
    private static Message sPool;
        // 消息池大杏选(鏈表長(zhǎng)度,表示消息個(gè)數(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ǎn)潔诊赊,獲取消息對(duì)象是同步操作厚满,從頭節(jié)點(diǎn)開始,如果頭結(jié)點(diǎn)不為空碧磅,取得頭結(jié)點(diǎn)碘箍。然后指針后移,并且消息池大小減1鲸郊。否則的才通過(guò)new方式創(chuàng)建新對(duì)象丰榴。

3.2 回收Message對(duì)象

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

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

如果消息標(biāo)記了FLAG_IN_USE標(biāo)志,不可回收秆撮。然后真正回收的方法是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++;
            }
        }
    }

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

五姆涩、總結(jié)

本文從Handler消息機(jī)制出發(fā)挽拂,分析了消息從發(fā)送、調(diào)度和分發(fā)處理的全過(guò)程骨饿。在此過(guò)程中亏栈,發(fā)現(xiàn)涉及到了同步屏障、IdleHandler等知識(shí)點(diǎn)宏赘,并對(duì)其做了分析和說(shuō)明绒北。有些東西可能在平時(shí)開發(fā)中用不上,例如消息屏障察署,但其蘊(yùn)含的編程思想也是十分值得學(xué)習(xí)借鑒的闷游。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市贴汪,隨后出現(xiàn)的幾起案子脐往,更是在濱河造成了極大的恐慌,老刑警劉巖扳埂,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件业簿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡阳懂,警方通過(guò)查閱死者的電腦和手機(jī)梅尤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)岩调,“玉大人巷燥,你說(shuō)我怎么就攤上這事『耪恚” “怎么了矾湃?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)堕澄。 經(jīng)常有香客問(wèn)我邀跃,道長(zhǎng),這世上最難降的妖魔是什么蛙紫? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任拍屑,我火速辦了婚禮,結(jié)果婚禮上坑傅,老公的妹妹穿的比我還像新娘僵驰。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布蒜茴。 她就那樣靜靜地躺著星爪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粉私。 梳的紋絲不亂的頭發(fā)上顽腾,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音诺核,去河邊找鬼抄肖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛窖杀,可吹牛的內(nèi)容都是我干的漓摩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼入客,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼管毙!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起桌硫,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤夭咬,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鞍泉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肮帐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年咖驮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片训枢。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡托修,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恒界,到底是詐尸還是另有隱情睦刃,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布十酣,位于F島的核電站涩拙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏耸采。R本人自食惡果不足惜兴泥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望虾宇。 院中可真熱鬧搓彻,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至稀轨,卻和暖如春扼脐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背靶端。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工谎势, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杨名。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓脏榆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親台谍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子须喂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容