Handler源碼學(xué)習(xí)(三)MessageQueue入隊(duì)插隊(duì)

Handler源碼學(xué)習(xí)(一)流程
Handler源碼學(xué)習(xí)(二)Message對(duì)象池
Handler源碼學(xué)習(xí)(三)MessageQueue入隊(duì)插隊(duì)

1.消息入隊(duì)

消息隊(duì)列與Message對(duì)象池的結(jié)構(gòu)很像焦影,也是通過(guò)對(duì)象之間通過(guò)next指向形成鏈表結(jié)構(gòu)

這時(shí)候加入一個(gè)msg消息,先來(lái)看如果消息隊(duì)列為空的情況

//判斷消息隊(duì)列為空時(shí),會(huì)直接將這個(gè)msg賦值給mMessage透乾,并將p賦值給msg.next,這時(shí)next當(dāng)然時(shí)null
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;
} 

假設(shè)現(xiàn)在隊(duì)列中已經(jīng)有了兩個(gè)消息,這兩個(gè)消息的排練是按照?qǐng)?zhí)行時(shí)間蓝撇,如果時(shí)間相同則是按照入隊(duì)先后排列。來(lái)一步步分析陈莽,可以先看后面的圖渤昌,把思路理清虽抄,再看源碼

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();
    //首先聲明了一個(gè)message對(duì)象prev,從字面意思理解是前一個(gè)message的意思
    Message prev;
    for (;;) {
      //接著進(jìn)入一個(gè)死循環(huán)独柑,將p賦值給prev迈窟,看看前面的代碼可以知道,p指向的是mMessages,所以這里
      //是將prev指向了mMessage忌栅,再下一次循環(huán)的時(shí)候车酣,prev則是指向了第二個(gè)message,依次類推
        prev = p;
      //接著將p指向p.next也就是mMessages.next,也就是消息鏈表中的第二個(gè)message
        p = p.next;
      //接下來(lái)判斷兩件事
      //1.p==null索绪,說(shuō)明沒(méi)有下一個(gè)消息了湖员,跳出循環(huán)
      //2.p!=null,并且當(dāng)前需要入隊(duì)的這個(gè)message的執(zhí)行時(shí)間是小于隊(duì)列中這個(gè)任務(wù)的執(zhí)行時(shí)間的
      //也就是說(shuō)這個(gè)入隊(duì)的message需要比隊(duì)列中的這個(gè)message先執(zhí)行瑞驱,也跳出循環(huán)
      //3.如果這兩個(gè)條件都不滿足的話娘摔,則繼續(xù)跟隊(duì)列中的下一個(gè)消息進(jìn)行對(duì)比,直到滿足條件唤反,或者到
      //隊(duì)列的末尾
        if (p == null || when < p.when) {
            break;
        }
        if (needWake && p.isAsynchronous()) {
            needWake = false;
        }
    }
  //跳出循環(huán)后做了兩件事情
  // 1.將入隊(duì)的這個(gè)消息的next指向循環(huán)中獲取到的應(yīng)該排在這個(gè)消息之后的message
    msg.next = p; // invariant: p == prev.next
  //將msg前面那個(gè)message.next指向了msg
    prev.next = msg;
  //到這里就將一個(gè)message完成了入隊(duì)
  //入隊(duì)的過(guò)程是線程安全的
}
MessageQueue入隊(duì).jpg
MessageQueue入隊(duì)(2).jpg

2.取出消息

這里貼的不是完整代碼凳寺,而是取出message的核心邏輯代碼,這里其實(shí)分了兩個(gè)部分拴袭,第一個(gè)部分是消息插隊(duì)读第,這個(gè)在第三節(jié)敘述,第二個(gè)部分是正常的消息拥刻,其實(shí)取出消息的部分比較簡(jiǎn)單,注釋也比較清晰

synchronized (this) {
    // Try to retrieve the next message.  Return if found.
    final long now = SystemClock.uptimeMillis();
    Message prevMsg = null;
  //隊(duì)列的第一個(gè)message
    Message msg = mMessages;
  //正常取出消息
    if (msg != null) {
      //1.首先判斷當(dāng)前時(shí)間是否小于了msg的執(zhí)行時(shí)間父泳,
        if (now < msg.when) {
            // Next message is not ready.  Set a timeout to wake up when it is ready.
          //(翻譯)需要執(zhí)行的消息還沒(méi)有到執(zhí)行時(shí)間般哼,設(shè)置一個(gè)喚醒時(shí)間,當(dāng)?shù)搅藞?zhí)行時(shí)間時(shí)喚醒
            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        } else {
            // Got a message.正常取出消息惠窄,這里邏輯比較簡(jiǎn)單蒸眠,不再贅述
            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;
    }

3.消息插隊(duì)

首先需要知道的是整個(gè)android系統(tǒng)其實(shí)主要是依賴消息機(jī)制來(lái)處理事件,比如說(shuō)點(diǎn)擊事件等等杆融,都是通過(guò)handler發(fā)送消息進(jìn)行處理楞卡,但是這些系統(tǒng)消息相較于程序自己發(fā)送的消息,應(yīng)該要優(yōu)先執(zhí)行脾歇,所以就涉及到了消息插隊(duì)

  • isAsynchronous() — 如何將系統(tǒng)消息和程序發(fā)送的消息區(qū)分開來(lái)

在閱讀Hanlder的源碼的時(shí)候蒋腮,可以看到一個(gè)@hide的構(gòu)造方法,傳入一個(gè)布爾值藕各,從字面意思理解是異步

public Handler(boolean async) {
    this(null, async);
}
mAsynchronous = async;//賦值給mAsychronous
//消息入隊(duì)的時(shí)候池摧,msg.setAsynchronous(true);將這個(gè)msg標(biāo)記為異步
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

為什么要標(biāo)記為異步呢?是要新開一個(gè)線程來(lái)執(zhí)行么激况?可是最后也是發(fā)送到looper所綁定的消息隊(duì)列中啊作彤,貌似這里并沒(méi)有異步膘魄,接著看上一節(jié)提到的消息插隊(duì)的源碼,在這里看到調(diào)用了一個(gè)方法msg.isAsynchronous()竭讳,貌似這個(gè)布爾值并不是跟異步有關(guān)系创葡,而是將這個(gè)消息做了一個(gè)標(biāo)記而已

前面的消息入隊(duì)分析可以知道,雖然被標(biāo)記了系統(tǒng)消息绢慢,但是還是按照消息隊(duì)列的規(guī)則去入隊(duì)蹈丸,那么如何做到優(yōu)先取出這個(gè)任務(wù)呢,一步步看

Message prevMsg = null;
Message msg = mMessages;
//1.判斷當(dāng)前第一個(gè)消息不為空
//2.重點(diǎn)來(lái)了,msg.target == null,如果這個(gè)消息的target為空呐芥,看過(guò)handler源碼可以知道逻杖,
//只要是通過(guò)handler發(fā)送消息,就會(huì)將這個(gè)msg的target指向發(fā)送消息的handler思瘟,否則也無(wú)法處理這個(gè)消息
//那么為什么這里會(huì)出現(xiàn)target為空呢荸百?先看if代碼塊里面的代碼
if (msg != null && msg.target == null) {
    // Stalled by a barrier.  Find the next asynchronous message in the queue.
    //進(jìn)入一個(gè)循環(huán),注意while中的判斷條件
    do {
      
      //不斷地取下一個(gè)消息來(lái)匹配判斷條件
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
  //只有當(dāng)msg不為空 并且當(dāng)前的這個(gè)消息是異步的滨攻,也就是說(shuō)是系統(tǒng)消息够话,則跳出循環(huán)。
  //跳出循環(huán)后就走到了正常取消息的代碼中光绕,取出的正是這個(gè)系統(tǒng)消息女嘲,發(fā)現(xiàn)插隊(duì)就成功了
}

上面的分析已經(jīng)插隊(duì)成功,但是還有疑點(diǎn)诞帐,到底是怎么進(jìn)入到if代碼塊中的

// Stalled by a barrier. Find the next asynchronous message in the queue.

作者在這里給了一條注釋欣尼,通過(guò)一個(gè)阻塞塊來(lái)停止正常的隊(duì)列,找到隊(duì)列中的第一個(gè)系統(tǒng)消息

在MessageQueue中試圖搜索barrier停蕉,發(fā)現(xiàn)一個(gè)方法愕鼓,

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;
      //判斷將這個(gè)消息插入到隊(duì)列中的哪個(gè)位置
        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;
    }
}

加入隊(duì)列的代碼已經(jīng)很熟悉了,可以看到在這段代碼中其實(shí)入隊(duì)了一個(gè)沒(méi)有target的消息慧起,而最上面那個(gè)public方法中可以看到傳入的when是當(dāng)前的系統(tǒng)時(shí)間菇晃,也就是說(shuō)如果調(diào)用這個(gè)方法會(huì)在消息隊(duì)列的頭部插入一個(gè)沒(méi)有target的message,到這里思路就比較清晰了蚓挤,但是這個(gè)消息肯定不能一直在隊(duì)列中磺送,否則整個(gè)隊(duì)列的正常消息就永遠(yuǎn)無(wú)法處理,所以相對(duì)應(yīng)還有一個(gè)remove灿意。注釋中說(shuō)明估灿,這個(gè)方法應(yīng)該跟插入的方法匹配使用。

public void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();

        // If the loop is quitting then it is already awake.
        // We can assume mPtr != 0 when mQuitting is false.
        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

當(dāng)發(fā)送系統(tǒng)消息時(shí) 會(huì)在消息隊(duì)列中插入一個(gè)target為空的message脾歧,在取出消息時(shí)如果發(fā)現(xiàn)了這個(gè)消息甲捏,就跳過(guò)所有的正常消息,返回最近的一個(gè)系統(tǒng)消息鞭执,然后將這個(gè)標(biāo)記消息從隊(duì)列中remove司顿。

整個(gè)Handler源碼學(xué)習(xí)系列筆記就完結(jié)了芒粹,當(dāng)然現(xiàn)在也沒(méi)有到特別深入的程度,但了解了整套Handler的消息機(jī)制后大溜,相信對(duì)于源碼的閱讀能力和編程思路上也有挺大的提高化漆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钦奋,隨后出現(xiàn)的幾起案子座云,更是在濱河造成了極大的恐慌,老刑警劉巖付材,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朦拖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡厌衔,警方通過(guò)查閱死者的電腦和手機(jī)璧帝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事桅打。” “怎么了苏潜?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)变勇。 經(jīng)常有香客問(wèn)我恤左,道長(zhǎng),這世上最難降的妖魔是什么贰锁? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任赃梧,我火速辦了婚禮,結(jié)果婚禮上豌熄,老公的妹妹穿的比我還像新娘。我一直安慰自己物咳,他們只是感情好锣险,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著览闰,像睡著了一般芯肤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上压鉴,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天崖咨,我揣著相機(jī)與錄音,去河邊找鬼油吭。 笑死击蹲,一個(gè)胖子當(dāng)著我的面吹牛署拟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播歌豺,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼推穷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了类咧?” 一聲冷哼從身側(cè)響起馒铃,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痕惋,沒(méi)想到半個(gè)月后区宇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡值戳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年议谷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片述寡。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡柿隙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鲫凶,到底是詐尸還是另有隱情禀崖,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布螟炫,位于F島的核電站波附,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏昼钻。R本人自食惡果不足惜掸屡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望然评。 院中可真熱鬧仅财,春花似錦、人聲如沸碗淌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)亿眠。三九已至碎罚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纳像,已是汗流浹背荆烈。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留竟趾,地道東北人憔购。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓宫峦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親倦始。 傳聞我的和親對(duì)象是個(gè)殘疾皇子斗遏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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