Android 消息機制之 MessageQueue.next() 消息取出的深入源碼分析 [ 八 ]

Android 消息機制深入源碼分析 [ 一 ]
Android 消息機制之 ThreadLocal 深入源碼分析 [ 二 ]
Android 消息機制之 Looper 深入源碼分析 [ 三 ]
Android 消息機制之 Message 與消息對象池的深入源碼分析 [ 四 ]
Android 消息機制之 MessageQueue 深入源碼分析 [ 五 ]
Android 消息機制之初識Handler [ 六 ]
Android 消息機制之 Handler 發(fā)送消息的深入源碼分析 [ 七 ]
Android 消息機制之 MessageQueue.next() 消息取出的深入源碼分析 [ 八 ]
Android 消息機制之消息的其他處理深入源碼分析 [ 九 ]
Android 消息機制總結 [ 十 ]

緊接上一章消息的發(fā)送, 本章內容為消息的取出分析學習.

消息的取出主要是通過 Looperloop 方法. 這個方法在第三章的時候已經分析過, 分為以下幾步

  1. 獲取Looper對象
  2. 獲取MessageQueue消息隊列對象
  3. 死循環(huán)遍歷
  4. 通過queue.next()來從MessageQueue的消息隊列中獲取一個Message msg對象
  5. 通過msg.target,dispatchMessage(msg)來處理消息
  6. 通過msg.recycleUnchecked() 方法來回收Message到消息對象池中.

其中 Message.recycleUnchecked() 在第四章的時候已經分析過, 那么現(xiàn)在就剩下 MessageQueue.next()handler.dispatchMessage() . 那么先來看 MessageQueue.next()

1. MessageQueue.next()

MessageQueue.java 310 行, 代碼過多, 將分段分析.

Message next() {
  //分析 1
  final long ptr = mPtr;
  if (ptr == 0) {
      return null;
  }
  //分析 2
  int pendingIdleHandlerCount = -1; // -1 only during first iteration
  //分析 3
  int nextPollTimeoutMillis = 0;
  • 分析:
    1. 如果消息循環(huán)已經退出了, 則在這里直接 return, 因為調用了 disposed()方法后, mPtr = 0;
    1. 記錄空閑時間處理的 IdleHandler 的數(shù)量. 初始為 -1
    1. native 需要用到的變量. 初始化為 0, 如果大于 0, 表示還有消息待處理(未到執(zhí)行時間). -1表示阻塞等待
 //分析 4
  for (;;) {
      //分析 5
      if (nextPollTimeoutMillis != 0) {
          Binder.flushPendingCommands();
      }
      //分析 6
      nativePollOnce(ptr, nextPollTimeoutMillis);
      //分析 7
      synchronized (this) {
          final long now = SystemClock.uptimeMillis();
          Message prevMsg = null;
          Message msg = mMessages;
          //分析 8
          if (msg != null && msg.target == null) {
              do {
                  prevMsg = msg;
                  msg = msg.next;
              } while (msg != null && !msg.isAsynchronous());
          }
  • 分析
    1. 開啟死循環(huán) (循環(huán)內容一直到最后)
    1. 如果還有消息未處理, 就刷新 Binder 命令, 一般在阻塞前調用
    1. 調用 native 方法, 當 nextPollTimeoutMillis == -1 的時候就阻塞等待, 直到下一條消息可用為止. 否則就繼續(xù)向下執(zhí)行. 還記得第七章發(fā)送消息時候消息入隊操作的最后嗎? 里面有一個 nativeWake() 喚醒. 就是喚醒此處. 沒有消息的時候, 這里就處于阻塞狀態(tài). 當我們發(fā)送消息的時候, 這里就會被喚醒.
    1. 加上同步鎖, 然后獲取從開機到現(xiàn)在的時間, 獲取消息鏈表頭部元素,
    1. 判斷第一個消息是不是障柵. (在前面第五篇中說過: 只有障柵的 tatget 才為 null), 如果第一個消息是障柵, 則又開啟一個循環(huán), 取出第一個異步消息, 從 do..while 這段代碼中. 可以印證出障柵會攔截所有的同步消息.
      如果 msg != null && ! msg.isAsynchronous() 這個條件成立, 說明就是同步消息, 那么就跳出同步消息繼續(xù)循環(huán), 直到找到第一條異步消息并賦值給
      .就退出 do..while 循環(huán).
          //分析 9
          if (msg != null) {
              //分析 10
              if (now < msg.when) {
                  nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
              } else { //分析 11
                  mBlocked = false;
                  //分析 12
                  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 {
              //分析 13
              nextPollTimeoutMillis = -1;
          }
  • 分析
    1. 判斷消息是否為 null
      如果進入了分析 8 中的 if 邏輯, 那么到這一步, msg 要么是一個異步消息, 要么為null. 不可能是同步消息
      如果沒有進入分析 8, 說明頭部消息不是障柵, 需要判斷是否是可執(zhí)行的異步消息或者同步消息.
    1. 如果當前時間小于消息的執(zhí)行時間, 表示當前可執(zhí)行的消息還未到執(zhí)行時間, 則記錄下剩余時間.
    1. 如果當前時間大于等于消息的執(zhí)行時間, 表示當前消息的執(zhí)行時間已經到了, 接著將 MessageQueue.mBlocked 設置為 false 表示 MessageQueue 不阻塞, mBlocked 變量與消息入隊時,需要不需要喚醒
    1. 這個 if..esle.. 判斷內的邏輯就是將需要立刻執(zhí)行的消息從消息隊列中抽出來, 然后再將消息隊列組合起來. 再將要執(zhí)行消息的 next 賦值為 null,并標記為正在使用. 最后把要執(zhí)行的消息返回出去. 獲取消息結束.
      例如: 消息鏈表中有三個消息 A -> B -> C, A是障柵, B是異步, C是同步. 分析 12 走完, 就變成了, B 是單獨的一個消息, 并將 B.next 置為 null, 最后組合后的消息鏈表就為 A -> C.
    1. 如果分析9 的判斷不成立, 則表示目前沒有可執(zhí)行的消息, 設置 nextPollTimeoutMillis = -1 . 在分析 3 中說過這個變量的作用表示是否阻塞.

          //分析 14
          if (mQuitting) {
              dispose();
              return null;
          }
          //分析 15
          if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
              pendingIdleHandlerCount = mIdleHandlers.size();
          }
          //分析 16
          if (pendingIdleHandlerCount <= 0) {
              // No idle handlers to run.  Loop and wait some more.
              mBlocked = true;
              continue;
          }
          //分析 17
          if (mPendingIdleHandlers == null) {
              mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
          }
          mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
      }

注意: 當代碼開始執(zhí)行這里的時候, msg 要么為 null, 要么未到執(zhí)行時間.

  • 分析
    1. 官方翻譯為:現(xiàn)在已處理所有掛起的消息,請?zhí)幚硗顺鱿⒑戎汀P枰P閉消息隊列, 返回 null, 通知 Looper 停止循環(huán).
    1. 當?shù)谝淮窝h(huán)才會在空閑的時候去執(zhí)行 IdleHanler, 從代碼可以看出所謂的空閑狀態(tài)指的就是, 目前沒有任何可執(zhí)行的 Message, 這里的可執(zhí)行有兩個要求, 當前 Message 不會被障柵攔截, 當前 Message 到達了執(zhí)行時間. 才會為變量 pendingIdleHandlerCount 賦值.
    1. 如果沒有在空閑時需要執(zhí)行的 IdleHandler. 這里是消息隊列阻塞(死循環(huán))的重點, 在 msg = null 或者未到執(zhí)行時間的情況下, 表示消息隊列空閑, 但是也沒有可執(zhí)行的 idleHandler, 那么就把 mBlock 變量置為 true, 表示需要喚醒, 并開始下一次循環(huán). 就會回到上面的分析 5 和分析 6, 這個時候 nextPollTimeoutMillis 要么為 -1, 要么就為上個消息剩下要執(zhí)行的時間. 那么分析 5 肯定成立, 接著刷新 binder 命令, 然后在分析 6 中就開始阻塞, 只要不是 0, 就會阻塞. 等要執(zhí)行的時間到了就會被喚醒. 或者當有新的消息入隊的時候. 就會根據(jù) mBlock 的值來判斷是否要喚醒消息隊列.
    1. 如果有需要在空閑時執(zhí)行的 IdleHandler, 接著判斷是否初始化過 mPendingIdleHandlers 數(shù)組, 最小4 個長度. 并把要執(zhí)行的 IdleHandler 賦值給 mPendingIdleHandlers 數(shù)組.
      //分析18
      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);
              }
          }
      }
      //分析 19
      pendingIdleHandlerCount = 0;
      //分析 20
      nextPollTimeoutMillis = 0;
  }
}

注意: 執(zhí)行到這里, 說明目前沒有可執(zhí)行的 Message, 但是有可以在空閑時執(zhí)行的 IdleHandler.

官方對這個循環(huán)的注釋為:

    1. 執(zhí)行 idleHandler. 我們只會在第一次迭代時到達此代碼塊影涉。為什么呢, 這個稍后分析, 先分析完 19 和 20.
    1. IdHandler 只會在消息隊列阻塞之前執(zhí)行一次, 執(zhí)行之后, pendingIdleHandlerCount 賦值為 0, 之后就不會再執(zhí)行. 一直到下一次調用 MessageQueue.next() 方法.
    1. 當執(zhí)行了 IdleHander 之后, 會消耗一段時間, 這時候消息隊列里可能已經有消息到達可執(zhí)行時間, 所以重置 nextPollTimeoutMillis 回去重新檢查消息隊列.

?
關于分析 18 的疑問. 為什么只會在第一次循環(huán)的時候會執(zhí)行這里呢.

也就是說只有在 MessageQueue.next 方法的死循環(huán), 第一次循環(huán)的時候, msgnull 或者 msg 未到執(zhí)行時間, 并且有可執(zhí)行的空閑 IdleHandler 的情況下會 執(zhí)行. 或者下次調用 MessageQueue.next() 方法. 為什么呢? 一起來分析一下,

  • 第一次循環(huán)開始,

    • 分析 6 那里肯定不會阻塞, 因為 nextPollTimeoutMillis 初始值為 0.
    • 然后到分析 9 , 如果 msg != null 并且有需要立即執(zhí)行的消息的話, 就直接跳出死循環(huán)了, 我們假設 msgnull 或者 msg 未到執(zhí)行時間. 那么在分析 9 內, 會對 nextPollTimeoutMillis 賦值,
    • 接著到分析 15, pendingIdleHandlerCount 初始值為 -1, 判斷成立, 給 pendingIdleHandlerCount 賦值
    • 到分析 16, 條件不成立, (因為我們假設有可執(zhí)行的空閑 IdleHandler). 如果沒有則直接就進行下次一次循環(huán)了, 下一次循環(huán)到分析 6 處, 就會阻塞了.
    • 分析 18, 開始執(zhí)行 IdleHandler. 執(zhí)行一個, 就從 mIdleHandlers 中移除一個.
    • 分析 19, pendingIdleHandlerCountnextPollTimeoutMillis 都賦值為 0,
  • 第二次循環(huán)開始

    • 分析 6 不阻塞, 因為在第一次循環(huán)的分析 20 處被重置了, 需要重新檢查消息隊列.
    • 分析 9 如果有消息到執(zhí)行時間了, 會直接 return, 沒有消息或者還是未到時間就再對 nextPollTimeoutMillis 賦值 -1 或者剩余執(zhí)行時間. 接著向下走.
    • 分析 15. 這里的判斷就不會成立了, 因為在第一次循環(huán)最后分析 19 處 pendingIdleHandlerCount 被置為 0 了. 所以跳到分析 16.
    • 分析 16. pendingIdleHandlerCount <= 0 條件成立. 跳出本次循環(huán), 開始進入第三次循環(huán)了. 然后在第三次循環(huán)中的分析 6, 就開始阻塞. 直到被喚醒.

?
總結
總的來說當在 Looper.loop() 方法的死循環(huán)內, 調用MessageQueue.next() 方法獲取一個 Message 的時候, 大致會分為以下幾步.

  1. MessageQueue 會先判斷隊列中是否有障柵存在
  • 有: 返回第一個異步消息,
  • 沒有: 逐個返回同步消息
  1. MessageQueue 中沒有任何消息可以處理或者未到消息的執(zhí)行時間的時候, 就會進入阻塞狀態(tài)等待新的消息到來被喚醒. 或者有消息的執(zhí)行時間到了被喚醒. 在阻塞之前會執(zhí)行一次 IdleHandler.
  2. MessageQueue 被關閉的時候, 成員變量 mQutting 會被標記為 true, 然后在 Looper 試圖從 MessageQueue 取消息的時候返回 null. 而 Message = null就是告訴 Looper 消息隊列已經關閉, 應該停止死循環(huán)了.(在第三篇 Looper.loop() 方法分析中有說明 )
  3. Handler 中實際上有兩個無限循環(huán)體, 一個是在 Looper.loop() 中的循環(huán)體, 以及 MessageQueue 中的循環(huán)體. 真正的阻塞是在 MessageQueue 的循環(huán)體中.

好了, 本章分析學習就到這里結束了, 下一章將會分析學習 消息分發(fā)處理, 消息的移除, 以及消息的其他操作.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子茵宪,更是在濱河造成了極大的恐慌最冰,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稀火,死亡現(xiàn)場離奇詭異暖哨,居然都是意外死亡,警方通過查閱死者的電腦和手機凰狞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門篇裁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赡若,你說我怎么就攤上這事达布。” “怎么了逾冬?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵黍聂,是天一觀的道長。 經常有香客問我身腻,道長产还,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任霸株,我火速辦了婚禮雕沉,結果婚禮上,老公的妹妹穿的比我還像新娘去件。我一直安慰自己坡椒,他們只是感情好,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布尤溜。 她就那樣靜靜地躺著倔叼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宫莱。 梳的紋絲不亂的頭發(fā)上丈攒,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機與錄音授霸,去河邊找鬼巡验。 笑死,一個胖子當著我的面吹牛碘耳,可吹牛的內容都是我干的显设。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼辛辨,長吁一口氣:“原來是場噩夢啊……” “哼捕捂!你這毒婦竟也來了瑟枫?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤指攒,失蹤者是張志新(化名)和其女友劉穎慷妙,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體允悦,經...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡膝擂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了澡屡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猿挚。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡咐旧,死狀恐怖驶鹉,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情铣墨,我是刑警寧澤室埋,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站伊约,受9級特大地震影響姚淆,放射性物質發(fā)生泄漏。R本人自食惡果不足惜屡律,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一腌逢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧超埋,春花似錦搏讶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至来庭,卻和暖如春妒蔚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背月弛。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工肴盏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人帽衙。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓菜皂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親佛寿。 傳聞我的和親對象是個殘疾皇子幌墓,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351