Handler(五)--MessageQueue

系列目錄: Handler機制原理

MessageQueue介紹

MessageQueue是一個消息隊列吩坝,Handler將Message發(fā)送并加入到消息隊列中凤瘦,消息隊列會按照規(guī)則取出要執(zhí)行的Message。
Java層的MessageQueue負責處理Java的消息拂封,native也有一個MessageQueue負責處理native的消息茬射,我也沒看JNI代碼,此處不做分析了冒签。

代碼分析

1. 成員變量
    //用于標示消息隊列是否可以被關(guān)閉在抛,主線程的消息隊列不可關(guān)閉
    private final boolean mQuitAllowed;

    // 該變量用于保存native代碼中的MessageQueue的指針
    private long mPtr; 
    //在MessageQueue中,所有的Message是以鏈表的形式組織在一起的萧恕,該變量保存了鏈表的第一個元素刚梭,也可以說它就是鏈表的本身
    Message mMessages;
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
    private IdleHandler[] mPendingIdleHandlers;
    //標示MessageQueue是否正在關(guān)閉。
    private boolean mQuitting;
    // 標示 MessageQueue是否阻塞
    private boolean mBlocked;

    // 在MessageQueue里面有一個概念叫做障柵票唆,它用于攔截同步的Message朴读,阻止這些消息被執(zhí)行,
    // 只有異步Message才會放行走趋。障柵本身也是一個Message衅金,只是它的target為null并且arg1用于區(qū)分不同的障柵,
    // 所以該變量就是用于不斷累加生成不同的障柵簿煌。
    private int mNextBarrierToken;
2. 構(gòu)造函數(shù)
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
  1. 設(shè)置MessageQueue是否可以退出
  2. native層代碼的初始化
3. MessageQueue中的Message分類
    1. 同步消息
      正常情況下我們通過Handler發(fā)送的Message都屬于同步消息氮唯,除非我們在發(fā)送的時候執(zhí)行該消息是一個異步消息。
      同步消息會按順序排列在隊列中姨伟,除非指定Message的執(zhí)行時間惩琉,否咋Message會按順序執(zhí)行。
    1. 異步消息
      想要往消息隊列中發(fā)送異步消息授滓,我們必須在初始化Handler的時候通過構(gòu)造函數(shù)public Handler(boolean async)中指定Handler是異步的琳水,這樣Handler在講Message加入消息隊列的時候就會將Message設(shè)置為異步的肆糕。
    1. 障柵
      障柵(Barrier) 是一種特殊的Message,它的target為null(只有障柵的target可以為null在孝,如果我們自己視圖設(shè)置Message的target為null的話會報異常)诚啃,并且arg1屬性被用作障柵的標識符來區(qū)別不同的障柵。
      障柵的作用是用于攔截隊列中同步消息私沮,放行異步消息始赎。
      在道路擁擠的時候會決定哪些車輛可以先通過,這些可以通過的車輛就是異步消息仔燕。
4. enqueueMessage()
    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) {
            // 第四步
            //判斷消息隊列是否正在關(guān)閉
            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;
            // 第六步
            //根據(jù)when的比較來判斷要添加的Message是否應(yīng)該放在隊列頭部造垛,當?shù)谝粋€添加消息的時候,
            // 測試隊列為空晰搀,所以該Message也應(yīng)該位于頭部五辽。
            if (p == null || when == 0 || when < p.when) {
                // 把msg的下一個元素設(shè)置為p
                msg.next = p;
                // 把msg設(shè)置為鏈表的頭部元素
                mMessages = msg;
                 // 如果有阻塞,則需要喚醒
                needWake = mBlocked;
            } else {
                // 第七步
                //除非消息隊列的頭部是障柵(barrier)外恕,或者消息隊列的第一個消息是異步消息杆逗,
                //否則如果是插入到中間位置,我們通常不喚醒消息隊列鳞疲,
                // 第八步
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                 // 第九步
                 // 不斷遍歷消息隊列罪郊,根據(jù)when的比較找到合適的插入Message的位置。
                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;
    }

第1步驟尚洽、 判斷msg的target變量是否為null悔橄,如果為null,則為障柵(barrier)腺毫,而障柵(barrier)入隊則是通過postSyncBarrier()方法入隊癣疟,所以msg的target一定有值
第2步驟、 判斷msg的標志位拴曲,因為此時的msg應(yīng)該是要入隊争舞,意味著msg的標志位應(yīng)該顯示還未被使用。如果顯示已使用澈灼,明顯有問題,直接拋異常店溢。
第3步驟叁熔、 加入同步鎖。
第4步驟床牧、 判斷消息隊列是否正在被關(guān)閉荣回,如果是正在被關(guān)閉,則return false告訴消息入隊是失敗戈咳,并且回收消息
第5步驟心软、 設(shè)置msg的when并且修改msg的標志位壕吹,msg標志位顯示為已使用
第6步驟、 如果p==null則說明消息隊列中的鏈表的頭部元素為null删铃;when == 0 表示立即執(zhí)行耳贬;when< p.when 表示 msg的執(zhí)行時間早與鏈表中的頭部元素的時間,所以上面三個條件猎唁,那個條件成立咒劲,都要把msg設(shè)置成消息隊列中鏈表的頭部是元素
第7步驟、 如果上面三個條件都不滿足則說明要把msg插入到中間的位置诫隅,不需要插入到頭部
第8步驟腐魂、 如果頭部元素不是障柵(barrier)或者異步消息,而且還是插入中間的位置逐纬,我們是不喚醒消息隊列的蛔屹。
第9步驟、 進入一個死循環(huán)豁生,將p的值賦值給prev兔毒,前面的帶我們知道,p指向的是mMessage沛硅,所以這里是將prev指向了mMessage眼刃,在下一次循環(huán)的時候,prev則指向了第一個message摇肌,一次類推擂红。接著講p指向了p.next也就是mMessage.next,也就是消息隊列鏈表中的第二個元素围小。這一步驟實現(xiàn)了消息指針的移動昵骤,此時p表示的消息隊列中第二個元素。
第10步驟肯适、 p==null变秦,則說明沒有下一個元素,即消息隊列到頭了框舔,跳出循環(huán)蹦玫;p!=null&&when < p.when 則說明當前需要入隊的這個message的執(zhí)行時間是小于隊列中這個任務(wù)的執(zhí)行時間的,也就是說這個需要入隊的message需要比隊列中這個message先執(zhí)行刘绣,則說明這個位置剛剛是適合這個message的樱溉,所以跳出循環(huán)。 如果上面的兩個條件都不滿足纬凤,則說明這個位置還不是放置這個需要入隊的message福贞,則繼續(xù)跟鏈表中后面的元素,也就是繼續(xù)跟消息隊列中的下一個消息進行對比停士,直到滿足條件或者到達隊列的末尾挖帘。
第11步驟完丽、 因為沒有滿足條件,說明隊列中還有消息拇舀,不需要喚醒逻族。
第12步驟、 跳出循環(huán)后主要做了兩件事:事件A你稚,將入隊的這個消息的next指向循環(huán)中獲取到的應(yīng)該排在這個消息之后message瓷耙。事件B,將msg前面的message.next指向了msg刁赖。這樣就將一個message完成了入隊搁痛。
第13步驟、 如果需要喚醒宇弛,則喚醒鸡典,具體請看后面的Handler中的Native詳解。
第14步驟枪芒、 返回true彻况,告知入隊成功

5. next()
    Message next() {
        // 如果消息循環(huán)已經(jīng)退出了。則直接在這里return舅踪。因為調(diào)用disposed()方法后mPtr=0
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        //記錄空閑時處理的IdlerHandler的數(shù)量
        int pendingIdleHandlerCount = -1; 
        // native層用到的變量 纽甘,如果消息尚未到達處理時間,則表示為距離該消息處理事件的總時長抽碌,
        // 表明Native Looper只需要block到消息需要處理的時間就行了悍赢。 所以nextPollTimeoutMillis>0表示還有消息待處理
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                //刷新下Binder命令,一般在阻塞前調(diào)用
                Binder.flushPendingCommands();
            }
            // 調(diào)用native層進行消息標示货徙,nextPollTimeoutMillis 為0立即返回左权,為-1則阻塞等待。
            nativePollOnce(ptr, nextPollTimeoutMillis);
            //加上同步鎖
            synchronized (this) {
                // 獲取開機到現(xiàn)在的時間
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                // 獲取MessageQueue的鏈表表頭的第一個元素
                Message msg = mMessages;
                 // 判斷Message是否是障柵痴颊,如果是則執(zhí)行循環(huán)赏迟,攔截所有同步消息,直到取到第一個異步消息為止
                if (msg != null && msg.target == null) {
                     // 如果能進入這個if蠢棱,則表面MessageQueue的第一個元素就是障柵(barrier)
                    // 循環(huán)遍歷出第一個異步消息锌杀,這段代碼可以看出障柵會攔截所有同步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                       //如果msg==null或者msg是異步消息則退出循環(huán),msg==null則意味著已經(jīng)循環(huán)結(jié)束
                    } while (msg != null && !msg.isAsynchronous());
                }
                 // 判斷是否有可執(zhí)行的Message
                if (msg != null) {  
                    // 判斷該Mesage是否到了被執(zhí)行的時間泻仙。
                    if (now < msg.when) {
                        // 當Message還沒有到被執(zhí)行時間的時候抛丽,記錄下一次要執(zhí)行的Message的時間點
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Message的被執(zhí)行時間已到
                        // 從隊列中取出該Message,并重新構(gòu)建原來隊列的鏈接
                        // 刺客說明說有消息饰豺,所以不能阻塞
                        mBlocked = false;
                        // 如果還有上一個元素
                        if (prevMsg != null) {
                            //上一個元素的next(越過自己)直接指向下一個元素
                            prevMsg.next = msg.next;
                        } else {
                           //如果沒有上一個元素,則說明是消息隊列中的頭元素允蜈,直接讓第二個元素變成頭元素
                            mMessages = msg.next;
                        }
                        // 因為要取出msg冤吨,所以msg的next不能指向鏈表的任何元素蒿柳,所以next要置為null
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        // 標記該Message為正處于使用狀態(tài),然后返回Message
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 沒有任何可執(zhí)行的Message漩蟆,重置時間
                    nextPollTimeoutMillis = -1;
                }

                // 關(guān)閉消息隊列垒探,返回null,通知Looper停止循環(huán)
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // 當?shù)谝淮窝h(huán)的時候才會在空閑的時候去執(zhí)行IdleHandler怠李,從代碼可以看出所謂的空閑狀態(tài)
                // 指的就是當隊列中沒有任何可執(zhí)行的Message圾叼,這里的可執(zhí)行有兩要求,
                // 即該Message不會被障柵攔截捺癞,且Message.when到達了執(zhí)行時間點
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                
                // 這里是消息隊列阻塞( 死循環(huán)) 的重點夷蚊,消息隊列在阻塞的標示是消息隊列中沒有任何消息,
                // 并且所有的 IdleHandler 都已經(jīng)執(zhí)行過一次了
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }
    
                // 初始化要被執(zhí)行的IdleHandler髓介,最少4個
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 開始循環(huán)執(zhí)行所有的IdleHandler惕鼓,并且根據(jù)返回值判斷是否保留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();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 重點代碼,IdleHandler只會在消息隊列阻塞之前執(zhí)行一次唐础,執(zhí)行之后改標示設(shè)置為0箱歧,
            // 之后就不會再執(zhí)行,一直到下一次調(diào)用MessageQueue.next() 方法一膨。
            pendingIdleHandlerCount = 0;

            // 當執(zhí)行了IdleHandler 的 處理之后呀邢,會消耗一段時間,這時候消息隊列里的可能有消息已經(jīng)到達 
             // 可執(zhí)行時間豹绪,所以重置該變量回去重新檢查消息隊列价淌。
            nextPollTimeoutMillis = 0;
        }
    }

首先、MessageQueue會先判斷隊列中是否有障柵的存在森篷,如果有的話输钩,只會返回異步消息,否則就逐個返回仲智。
其次买乃、當MessageQueue沒有任何消息可以處理的時候,它會進度阻塞狀態(tài)等待新的消息到來(無線循環(huán))钓辆,在阻塞之前它會執(zhí)行以便 IdleHandler剪验,所謂的阻塞其實就是不斷的循環(huán)查看是否有新的消息進入隊列中。
再次前联、當MessageQueue被關(guān)閉的時候功戚,其成員變量mQuitting會被標記為true,然后在Looper視圖從隊列中取出Message的時候返回null似嗤,而Message==null就是告訴Looper消息隊列已經(jīng)關(guān)閉啸臀,應(yīng)該停止循環(huán)了,這一點可以在Looper.loop()方法源碼中看出。
最后乘粒、如果大家細心一定會發(fā)現(xiàn)豌注,Handler線程里面實際上有兩個無線循環(huán)體,Looper循環(huán)體和MessageQueue循環(huán)體灯萍,真正阻塞的地方是MessageQueue的next()方法里轧铁。

MessageQueue的核心代碼就是enqueueMessage()和next(),還有就是一些概念性的理解。
最重要的一點是要知道兩個for循環(huán)是在哪里阻塞的:

Looper循環(huán)體和MessageQueue循環(huán)體旦棉,真正阻塞的地方是MessageQueue的next()方法里.

參考

http://www.reibang.com/p/c4cc31e4052f

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末齿风,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绑洛,更是在濱河造成了極大的恐慌救斑,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诊笤,死亡現(xiàn)場離奇詭異系谐,居然都是意外死亡,警方通過查閱死者的電腦和手機讨跟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門纪他,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晾匠,你說我怎么就攤上這事茶袒。” “怎么了凉馆?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵薪寓,是天一觀的道長。 經(jīng)常有香客問我澜共,道長向叉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任嗦董,我火速辦了婚禮母谎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘京革。我一直安慰自己奇唤,他們只是感情好,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布匹摇。 她就那樣靜靜地躺著咬扇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪廊勃。 梳的紋絲不亂的頭發(fā)上懈贺,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音,去河邊找鬼隅居。 笑死钠至,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的胎源。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼屿脐,長吁一口氣:“原來是場噩夢啊……” “哼涕蚤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起的诵,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤万栅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后西疤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烦粒,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年代赁,在試婚紗的時候發(fā)現(xiàn)自己被綠了扰她。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡芭碍,死狀恐怖徒役,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情窖壕,我是刑警寧澤忧勿,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站瞻讽,受9級特大地震影響鸳吸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜速勇,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一晌砾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧快集,春花似錦贡羔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至院溺,卻和暖如春楣嘁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工逐虚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留聋溜,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓叭爱,卻偏偏與公主長得像撮躁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子买雾,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

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

  • (轉(zhuǎn))原文鏈接:https://blog.csdn.net/huangqili1314/article/detai...
    谷哥得小弟閱讀 3,634評論 0 66
  • 文章要筆耕不輟的寫把曼,直至有朝一日名揚天下; 好字要入木三分的練漓穿,終止懶理俗務(wù)可賣酒錢嗤军; 個性要一如既往鮮明,稍提孝...
    易郁生閱讀 231評論 1 5
  • 別那么驕傲晃危,請讓自己沉穩(wěn)下來叙赚。 2018年,我要說給自己聽僚饭。 我要穩(wěn)住我的心震叮。 別那么驕傲,看見不喜歡的人浪慌,不妄自...
    驛路梨花董小蝶閱讀 418評論 1 4
  • 多的是权纤,你不知道的事 故事一: 記得我很小的時候钓简,大概七八歲吧,外婆家住在公路邊汹想,恰逢春節(jié)時期外邓,我們一家到外婆家拜...
    卿訫閱讀 520評論 1 1