Handler(三)

Android知識總結(jié)

同步屏障

大家經(jīng)過上面的學習應(yīng)該知道,線程的消息都是放到同一個MessageQueue里面,取消息的時候是互斥取消息钦购,而且只能從頭部取消息牺六,而添加消息是按照消息的執(zhí)行的先后順序進行的排序丝蹭,那么問題來了秤掌,同一個時間范圍內(nèi)的消息汉操,如果它是需要立刻執(zhí)行的再来,那我們怎么辦,按照常規(guī)的辦法客情,我們需要等到隊列輪詢到我自己的時候才能執(zhí)行哦其弊,那豈不是黃花菜都涼了。所以膀斋,我們需要給緊急需要執(zhí)行的消息一個綠色通道梭伐,這個綠色通道就是同步屏障的概念。

一仰担、 target 為何物

Message 類:

    @UnsupportedAppUsage
    /*package*/ Handler target;

從這里可以知道糊识,Message 是持有 Handler 的, 所謂的 target 即為 Handler 對象摔蓝。
我們可以通過 Handle 發(fā)送消息的時候(如調(diào)用Handler#sendMessage()等 )赂苗,最終都是會調(diào)用 Handler#enqueueMessage()讓消息入隊,最終找到target 贮尉。

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

當我們發(fā)送一個消息的時候拌滋,msg.target 就會被賦值為this, 而 this 即為我們的 Handler 對象猜谚。因此败砂,通過這種方式傳進來的消息的 target 肯定也就不為 null赌渣,并且 mAsynchronous 默認為 false否灾,也就是說我們一般發(fā)送的消息都為同步消息

二线罕、 異步消息

設(shè)置異步消息有兩種方式:
一種是直接設(shè)置消息為異步的

Message msg = mMyHandler.obtainMessage();
msg.setAsynchronous(true);
mMyHandler.sendMessage(msg);

還有一個需要用到 Handler 的一個構(gòu)造方法

    @UnsupportedAppUsage
    public Handler(boolean async) {
        this(null, async);
    }

使用

Handler mMyHandler = new Handler(true);
Message msg = mHandler.obtainMessage();
mMyHandler.sendMessage(msg);

但需要注意的是,通過上面兩種方式來發(fā)送的消息還不是異步消息谷朝,因為它們最終還是會進入 enqueueMessage()斜姥,仍然會給 target 賦值 鸿竖,導致 target 不為null。

三铸敏、同步屏障

發(fā)送異步消息的關(guān)鍵就是要消息開啟一個同步屏障缚忧。屏障的意思即為阻礙,顧名思義杈笔,同步屏障就是阻礙同步消息搔谴,只讓異步消息通過。
MessageQueue#postSyncBarrier()

 @UnsupportedAppUsage
    @TestApi
    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++;
            //從消息池中獲取Message
            final Message msg = Message.obtain();
            msg.markInUse();
            //初始化Message對象的時候桩撮,并沒有給target賦值,因此 target==null
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    //如果開啟同步屏障的時間(假設(shè)記為T)T不為0峰弹,且當前的同步消息里有時間小于T店量,則prev也不為null
                    prev = p;
                    p = p.next;
                }
            }
            //根據(jù)prev是不是為null,將 msg 按照時間順序插入到 消息隊列(鏈表)的合適位置
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

可以看到鞠呈,Message 對象初始化的時候并沒有給 target 賦值融师,因此,target == null的 來源就找到了蚁吝。上面消息的插入也做了相應(yīng)的注釋旱爆。這樣,一條target == null 的消息就進入了消息隊列窘茁。

如果對消息機制有所了解的話怀伦,應(yīng)該知道消息的最終處理是在消息輪詢器Looper#loop()中,而loop()循環(huán)中會調(diào)用MessageQueue#next()從消息隊列中進行取消息山林。

    @UnsupportedAppUsage
    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 1.如果nextPollTimeoutMillis=-1房待,一直阻塞不會超時。
        // 2.如果nextPollTimeoutMillis=0驼抹,不會阻塞桑孩,立即返回。
        // 3.如果nextPollTimeoutMillis>0框冀,最長阻塞nextPollTimeoutMillis毫秒(超時)
        //   如果期間有程序喚醒會立即返回流椒。
        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) {
                    // 如果target==null,那么它就是屏障明也,需要循環(huán)遍歷宣虾,一直往后找到第一個異步的消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());//確定是異步消息
                }
                if (msg != null) {
                    //如果有消息需要處理惯裕,先判斷時間有沒有到,如果沒到的話設(shè)置一下阻塞時間
                    //場景如常用的postDelay
                    if (now < msg.when) {
                        //計算出離執(zhí)行時間還有多久賦值給nextPollTimeoutMillis安岂,
                        //表示nativePollOnce方法要等待nextPollTimeoutMillis時長后返回
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 獲取到消息
                        mBlocked = false;
                        //鏈表操作轻猖,獲取msg并且刪除該節(jié)點
                        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 {
                    // 沒有消息,nextPollTimeoutMillis復位
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
                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);
            }
            ////開始順序執(zhí)行所有IdleHandler
            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(); //具體執(zhí)行
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                //根據(jù)queueIdle()方法返回值決定是否移除該IdleHandler
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }

從上面可以看出域那,當消息隊列開啟同步屏障的時候(即標識為 msg.target == null )咙边,消息機制在處理消息的時
候,優(yōu)先處理異步消息次员。這樣败许,同步屏障就起到了一種過濾和優(yōu)先級的作用。
下面用示意圖簡單說明:


如上圖所示淑蔚,在消息隊列中有同步消息和異步消息(黃色部分)以及一道墻----同步屏障(紅色部分)市殷。有了同步屏障的存在,msg_2 和 msg_M 這兩個異步消息可以被優(yōu)先處理刹衫,而后面的 msg_3 等同步消息則不會被處理醋寝。那么這些同步消息什么時候可以被處理呢?那就需要先移除這個同步屏障带迟,即調(diào)用removeSyncBarrier()

四音羞、同步屏障使用場景

似乎在日常的應(yīng)用開發(fā)中,很少會用到同步屏障仓犬。那么嗅绰,同步屏障在系統(tǒng)源碼中有哪些使用場景呢?Android 系統(tǒng)中的 UI 更新相關(guān)的消息即為異步消息搀继,需要優(yōu)先處理窘面。

比如,在 View 更新時叽躯,draw财边、requestLayout、invalidate 等很多地方都調(diào)用了

ViewRootImpl#scheduleTraversals()如下:

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //開啟同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //發(fā)送異步消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

postCallback() 最終走到了 Choreographer#postCallbackDelayedInternal()

    @UnsupportedAppUsage
    @TestApi
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }

    @UnsupportedAppUsage
    @TestApi
    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }

        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + 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);
            }
        }
    }

這里就開啟了同步屏障点骑,并發(fā)送異步消息制圈,由于 UI 更新相關(guān)的消息是優(yōu)先級最高的,這樣系統(tǒng)就會優(yōu)先處理這些異步消息

最后畔况,當要移除同步屏障的時候需要調(diào)用 ViewRootImpl#unscheduleTraversals()

    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除同步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

五鲸鹦、IdleHandler

很多人在Android項目中都會遇到希望一些操作延遲一點處理,一般會使用Handler.postDelayed(Runnable r, long delayMillis)來實現(xiàn)跷跪,但是又不知道該延遲多少時間比較合適馋嗜,因為手機性能不同,有的性能較差可能需要延遲較多吵瞻,有的性能較好可以允許較少的延遲時間葛菇。

之前在項目中對啟動過程進行優(yōu)化甘磨,用到了IdleHandler,它可以在主線程空閑時執(zhí)行任務(wù)眯停,而不影響其他任務(wù)的執(zhí)行济舆。

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Handler handler = new Handler(Looper.myLooper());
        //添加 IdleHandler
        handler.getLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                //一些延遲一點處理的操做可以在此處理,
                //當其他Handler消息分發(fā)完畢才會執(zhí)行此處的消息
                return false;
            }
        });
        //移除 IdleHandler
        handler.getLooper().getQueue().removeIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                return false;
            }
        });
    }

可以將上述代碼添加到Activity onCreate中莺债,在queueIdle()方法中實現(xiàn)延遲執(zhí)行任務(wù)滋觉,在主線程空閑,也就是activity創(chuàng)建完成之后齐邦,它會執(zhí)行queueIdle()方法中的代碼椎侠。

queueIdle() 返回true表示可以反復執(zhí)行該方法,即執(zhí)行后還可以再次執(zhí)行措拇;返回false表示執(zhí)行完該方法后會移除該IdleHandler我纪,即只執(zhí)行一次。

注意:在主線程中使用時queueIdle中不能執(zhí)行太耗時的任務(wù)丐吓。

六浅悉、小結(jié)

同步屏障的設(shè)置可以方便地處理那些優(yōu)先級較高的異步消息。當我們調(diào)用Handler.getLooper().getQueue().postSyncBarrier() 并設(shè)置消息的 setAsynchronous(true) 時券犁,target 即為 null 仇冯,也就開啟了同步屏障。當在消息輪詢器 Looper 在 loop() 中循環(huán)處理消息時族操,如若開啟了同步屏障,會優(yōu)先處理其中的異步消息比被,而阻礙同步消息色难。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市等缀,隨后出現(xiàn)的幾起案子枷莉,更是在濱河造成了極大的恐慌,老刑警劉巖尺迂,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笤妙,死亡現(xiàn)場離奇詭異,居然都是意外死亡噪裕,警方通過查閱死者的電腦和手機蹲盘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膳音,“玉大人召衔,你說我怎么就攤上這事〖老荩” “怎么了苍凛?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵趣席,是天一觀的道長。 經(jīng)常有香客問我醇蝴,道長宣肚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任悠栓,我火速辦了婚禮霉涨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闸迷。我一直安慰自己嵌纲,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布腥沽。 她就那樣靜靜地躺著逮走,像睡著了一般。 火紅的嫁衣襯著肌膚如雪今阳。 梳的紋絲不亂的頭發(fā)上师溅,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音盾舌,去河邊找鬼墓臭。 笑死,一個胖子當著我的面吹牛妖谴,可吹牛的內(nèi)容都是我干的窿锉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼膝舅,長吁一口氣:“原來是場噩夢啊……” “哼嗡载!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仍稀,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤洼滚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后技潘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遥巴,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年享幽,在試婚紗的時候發(fā)現(xiàn)自己被綠了铲掐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡值桩,死狀恐怖迹炼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤斯入,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布砂碉,位于F島的核電站,受9級特大地震影響刻两,放射性物質(zhì)發(fā)生泄漏增蹭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一磅摹、第九天 我趴在偏房一處隱蔽的房頂上張望滋迈。 院中可真熱鬧,春花似錦户誓、人聲如沸饼灿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碍彭。三九已至,卻和暖如春悼潭,著一層夾襖步出監(jiān)牢的瞬間庇忌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工舰褪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留皆疹,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓占拍,卻偏偏與公主長得像略就,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子晃酒,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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