Android 消息機(jī)制之 Handler 發(fā)送消息的深入源碼分析 [ 七 ]

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

上一章, 初步認(rèn)識了 Handler, 本章節(jié)將繼續(xù)接著上一章開始對消息的具體發(fā)送進(jìn)行學(xué)習(xí)和分析.

1. Handler 消息發(fā)送的分類

  • Handler 平時(shí)發(fā)送消息主要是調(diào)用兩大類方法, 分別是 send 方案 以及 post 方案

  • send 方案

    • boolean sendMessage(Message msg)
    • boolean sendEmptyMessage(int what)
    • boolean sendEmptyMessageDelayed(int what, long delayMillis)
    • boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
    • boolean sendMessageDelayed(Message msg, long delayMillis)
    • boolean sendMessageAtTime(Message msg, long uptimeMillis)
    • boolean sendMessageAtFrontOfQueue(Message msg)
  • post 方案

    • boolean post(Runnable r)
    • boolean postAtFrontOfQueue(Runnable r)
    • boolean postAtTime(Runnable r, long uptimeMillis)
    • boolean postAtTime(Runnable r, Object token, long uptimeMillis)
    • boolean postDelayed(Runnable r, long delayMillis)
    • boolean postDelayed(Runnable r, Object token, long delayMillis)
      ?

?

2. send 方案.

還是先從 send 方案的第一個(gè)開始看, 也就是我們最常用的一個(gè).

2.1 boolean sendMessage(Message msg)
Handler.java 602 行.

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}
  • 官方注釋:

在當(dāng)前時(shí)間之前的所有掛起消息之后,將消息推送到消息隊(duì)列的末尾. 在和當(dāng)前線程關(guān)聯(lián)的 Handler里面的 HandlerMessage 將收到這條消息.

  • 分析

代碼中就是簡單的調(diào)用了 sendMessageDelayed(msg, 0)

?
2.2 boolean sendMessageDelayed(Message msg, long delayMillis)
Handler.java 662 行.

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    //判斷 delayMillis 是否小于0
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
  • 分析

如果傳入的延遲時(shí)間小于 0 , 那么就等于 0. 然后調(diào)用 sendMessageAtTime 傳入了 SystemClock.uptimeMillis() + delayMillis
SystemClock.uptimeMillis() = 從開機(jī)到現(xiàn)在的毫秒數(shù)(手機(jī)睡眠的時(shí)間不包括在內(nèi)).

?
2.3 boolean sendMessageAtTime(Message msg, long uptimeMillis)
Handler.java 689 行.

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}
  • 官方注釋

在 Android 系統(tǒng)的重開機(jī)到現(xiàn)在的毫秒數(shù)為基準(zhǔn)的絕對時(shí)間下, 將消息放到所有的待處理消息后的隊(duì)列中. 深度睡眠中的時(shí)間將會(huì)延遲執(zhí)行的時(shí)間,(也就是說手機(jī)睡眠的時(shí)間不包括在內(nèi)), 你將在和當(dāng)前線程關(guān)聯(lián)的 Handler里面的 HandlerMessage 將收到這條消息.

  • 分析

方法內(nèi)部做了兩件事情

  • 獲取消息隊(duì)列, 并對消息隊(duì)列做非空判斷.,如果為 null, 直接返回 false. (在 Handler 的構(gòu)造方法中, 會(huì)對消息隊(duì)列對象 mQueue 進(jìn)行賦值)
  • 調(diào)用 enqueueMessage(queue, msg, uptimeMillis) 方法.

?
2.4 boolean enqueueMessagee(MessageQueue queue, Message msg, long uptimeMillis)
Handler.java 740 行.

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
  • 分析

內(nèi)部做了三件事

  • 設(shè)置 msgtarget 變量指向當(dāng)前 Handler
  • 如果當(dāng)前 Handler 是異步的, 則設(shè)置 msg 也為異步. 讓 HandlerMessage 達(dá)成一致. (在上一章 初識 Handler 中說過, 默認(rèn)是非異步的, 需要在創(chuàng)建 Handler 的時(shí)候指定為異步)
  • 調(diào)用 MessageQueue.enqueueMessage() 方法進(jìn)行消息入隊(duì) .

?
2.5 MessageQueue boolean enqueueMessage(Message msg, long when)
MessageQueue.java 536 行, 由于代碼過長, 將會(huì)分為多段來分析.

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.");
  }
  • 分析
  1. 判斷 msgtarget 變量是否為 null, 如果為 null,則會(huì)人會(huì)是障柵, 但是障柵的添加是通過 postSyncBarrier 方式添加的(在本系列的第五章節(jié)有分析). 所以正常 Messagetarget 一定是有值的. 反之拋出異常.
  2. 接著判斷 msg 的標(biāo)記為, 因?yàn)榇藭r(shí)的 Message 是要入隊(duì), 意味著 msg 的標(biāo)記位應(yīng)該是未被使用. 如果顯示已使用, 則拋出異常.
  //第三步
  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) {
          //把 msg 的下一個(gè)元素設(shè)置為 p
          msg.next = p;
          //把 msg 設(shè)置為鏈表的頭部元素
          mMessages = msg;
          //如果有阻塞,則需要喚醒
          needWake = mBlocked;
      } 
  • 分析
  1. 加入同步鎖
  2. 判斷消息隊(duì)列是否正在關(guān)閉, 如果是, 直接返回 false, 表示消息入隊(duì)失敗. 并且回收消息.
  3. 設(shè)置 msg 的發(fā)送時(shí)間, 以及設(shè)置 msg 正在使用的標(biāo)記位. 接著取出消息鏈表中頭部元素賦值給 p
  4. p == null 說明當(dāng)前要入隊(duì)的消息是第一個(gè)消息. when == 0 表示立即執(zhí)行. when < p.when 表示當(dāng)前要入隊(duì) msg 的執(zhí)行時(shí)間要早于消息鏈表頭部元素的執(zhí)行時(shí)間. 所以這三個(gè)條件, 無論哪個(gè)條件成立, 都會(huì)把當(dāng)前要入隊(duì)的 msg 設(shè)置為消息鏈表的頭部.
      //第七步
      else {
          //第八步
          needWake = mBlocked && p.target == null && msg.isAsynchronous();
          Message prev;
          //第九步
          //不斷遍歷消息隊(duì)列, 根據(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. 如果以上三個(gè)條件都不滿足, 則說明要吧 msg 插入到消息鏈表中非頭部的位置.
  2. 如果頭部元素是障柵或者是異步消息,而且還是插入中間位置,我們不會(huì)喚醒
    mBlocked 這里先認(rèn)為是 false, 它的賦值是在 MessageQueue.next() 方法中 , 如果當(dāng)前消息鏈表沒有消息, 或者未到執(zhí)行時(shí)間, 并且也沒有可執(zhí)行的空閑消息的時(shí)候, 會(huì)被賦值為 true, 并且消息隊(duì)列開始阻塞, 當(dāng)有可以立即執(zhí)行的消息的時(shí)候會(huì)被賦值為 false.
    p.target == null 表示頭部元素是一個(gè)障柵.
    msg.isAsynchronous() 當(dāng)前要入隊(duì)的消息是一個(gè)異步消息.
  3. 進(jìn)入死循環(huán), 將 p 賦值給 prev, 那么 prev 也指向了消息鏈表的頭部, 接著將 p 指向了 p.next. 也就是鏈表中的第二個(gè)元素. 這兩個(gè)賦值操作, 實(shí)現(xiàn)了消息指針的移動(dòng).
  4. 消息指針移動(dòng)過后, p == null, 說明沒有下一個(gè)元素了, when < p.when 則說明要入隊(duì)的這個(gè)消息的執(zhí)行時(shí)間是小于當(dāng)前 p 指向消息的執(zhí)行時(shí)間的. 這兩個(gè)條件有一個(gè)成立說明現(xiàn)在這個(gè)位置適合插入要入隊(duì)的消息, 然后就跳出死循環(huán),
  5. 若第 10 步?jīng)]有跳出循環(huán), 并且執(zhí)行到了 11 步, 那么說明沒有滿足條件, 隊(duì)列中還有消息, 不需要喚醒.
  6. 跳出循環(huán)后做了兩件事.
  • 將要入隊(duì)的這個(gè)消息的 next 指向循環(huán)中獲取到應(yīng)該排在這個(gè)入隊(duì)消息之后的 Message.
  • 將要入隊(duì)的這個(gè)消息前面消息的 next 指向自己. 這樣就完成了入隊(duì)操作.
  1. 是否要喚醒.
  2. 入隊(duì)成功返回 true

?
以上就是我們常用的 sendMessage(Message msg) 消息發(fā)送流程, 接下來看 send 方案中剩下的.

2.5 boolean sendMessageAtFrontOfQueue(Message msg)
Handler.java 712 行.

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, 0);
}
  • 官方翻譯

在消息隊(duì)列的最前面插入一個(gè)消息, 在消息循環(huán)的下一次迭代中進(jìn)行處理. 你將在當(dāng)前線程關(guān)聯(lián)的 HandlerhandleMessage() 中收到這個(gè)消息. 由于它可以輕松的解決消息的排序和其他的意外副作用.

  • 解析

方法內(nèi)部的實(shí)現(xiàn)和 boolean sendMessageAtTime(Message msg, long uptimeMillis) 大體上一致. 唯一的區(qū)別就是該方法在調(diào)用入隊(duì)方法的時(shí)候, 最后一個(gè)參數(shù)是 0, 表示需要立即執(zhí)行.

?
剩下的 boolean sendEmptyMessage(int what)boolean sendEmptyMessageDelayed(int what, long uptimeMillis) 就不再分析了, 非常簡單, 可以自己看一下.
?
send 方案的最后, 都是會(huì)調(diào)用 enqueueMessage() 入隊(duì)方法來完成消息的入隊(duì).

3. post 方案

3.1 boolean post(Runnable r)
Handler.java 393行.

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}
  • 官方注釋

將一個(gè) Runnable 添加到消息隊(duì)列中, 這個(gè) Runnable 將會(huì)在和當(dāng)前 Handler 關(guān)聯(lián)的線程中執(zhí)行.

-分析

這個(gè)方法內(nèi)部簡單, 就是調(diào)用了 sendMessageDelayed() 這個(gè)方法, 所以可見 boolean post(Runnable r) 這個(gè)方法最終還是走到上面說的 send 方案的流程中. 最后還是調(diào)用 enqueueMessage() 入隊(duì)方案.

看一下調(diào)用的 getPostMessage(Runnable r)

?
3.1 Message getPostMessage(Runnable r)
Handler.java 859 行

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
  • 分析
  1. 從消息對象池中獲取一個(gè)空的 Message
    2.將空 Messagecallback 指向 Runnable (這個(gè) CallbackMessage 中的 Runnable)
    3.最后返回這個(gè) Message

由此我們可以得知 boolean post(Runnable r) 方法的內(nèi)部也是通過 Message.obtain 來獲取一個(gè) Message 對象. 然后僅僅只是把 Message 對象的 Runnable callback 賦值而已. 最后還是調(diào)用了 send 方案的某個(gè)流程最終調(diào)用到入隊(duì)方法.

后面剩余的 post 方法依舊如此, 只是調(diào)用的是不同的 send 方案中的方法.

?

4.消息發(fā)送總結(jié)

Handler 發(fā)送消息 (除障柵外), 無論是 send 還是 post 最終都會(huì)調(diào)用 MessageQueue.enqueueMessage(Message msg, long when) 方法進(jìn)行入隊(duì). 下圖展示了他們之間的關(guān)系.

Handler 消息發(fā)送調(diào)用關(guān)系圖

消息發(fā)送出去了, 剩下的就是取出消息以及其他處理了, 將在下一章分析.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸭栖,一起剝皮案震驚了整個(gè)濱河市洲拇,隨后出現(xiàn)的幾起案子脉执,更是在濱河造成了極大的恐慌修陡,老刑警劉巖条获,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疆栏,死亡現(xiàn)場離奇詭異贬媒,居然都是意外死亡洋魂,警方通過查閱死者的電腦和手機(jī)绷旗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來副砍,“玉大人衔肢,你說我怎么就攤上這事』眙幔” “怎么了角骤?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我邦尊,道長背桐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任蝉揍,我火速辦了婚禮链峭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘又沾。我一直安慰自己弊仪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布杖刷。 她就那樣靜靜地躺著励饵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挺勿。 梳的紋絲不亂的頭發(fā)上曲横,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機(jī)與錄音不瓶,去河邊找鬼禾嫉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蚊丐,可吹牛的內(nèi)容都是我干的熙参。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼麦备,長吁一口氣:“原來是場噩夢啊……” “哼孽椰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凛篙,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤黍匾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后呛梆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锐涯,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年填物,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纹腌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡滞磺,死狀恐怖升薯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情击困,我是刑警寧澤涎劈,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響责语,放射性物質(zhì)發(fā)生泄漏炮障。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一坤候、第九天 我趴在偏房一處隱蔽的房頂上張望胁赢。 院中可真熱鬧,春花似錦白筹、人聲如沸智末。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽系馆。三九已至,卻和暖如春顽照,著一層夾襖步出監(jiān)牢的瞬間由蘑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工代兵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尼酿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓植影,卻偏偏與公主長得像裳擎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子思币,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351