Android 消息機制總結(jié) [ 十 ]

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

思考: 如果讓我們來設(shè)計一個操作系統(tǒng), 我們會來怎么設(shè)計 ?

一般操作系統(tǒng)都會有一個消息系統(tǒng), 里面有個死循環(huán), 不斷的輪詢處理其他各種輸入設(shè)備輸入的消息. 比如鍵盤的輸入, 鼠標的移動等. 這些輸入信息最終都會進入操作系統(tǒng), 然后由操作系統(tǒng)的內(nèi)部輪訓機制挨個處理這些信息.

  1. 設(shè)計一個類, 里面有一個死循環(huán)去做循環(huán)操作.
  2. 再用一個類來抽象代表各種輸入的信息.
    • 這個信息/消息應該還有一個唯一標識符, 用來區(qū)分不同的信息/消息
    • 信息/消息中有個對象來保存對應的鍵值對, 方便往信息/消息中存放數(shù)據(jù).
    • 信息/消息還需要有字段來表明要執(zhí)行的時間.
  3. 上面說的這些信息/消息又會組合成一個集合, 常用集合有很多, ArrayList, LinkedList, Map. 因為第一點說了使用一個死循環(huán)去處理, 那么這個集合最好是線性和排序較好的. 因為輸入有先后, 一般都是按照輸入的時間先后來構(gòu)成, 既然這樣那就先排除掉 Map, 那么就剩下了 ArrayList, LinkedList. 要知道一個操作系統(tǒng)的事件是非常多的, 也就是說對應的信息/消息很多, 所以這個集合要面臨大量的插入操作, 而在插入效率這塊, LinkedList 具有明顯的優(yōu)勢. 所以這個集合應該是一個鏈表, 但是鏈表又分為多種, 因為是線性排序的, 所以只剩下雙向鏈表與單向鏈表. 又考慮到手機性能的問題, 那就之剩下單向鏈表了, 因為單向鏈表在插入和刪除上的復雜度明顯低于雙向鏈表.
  4. 最后還應該有兩個類, 一個負責生產(chǎn)信息/消息, 一個負責消費這些信息/消息. 因為涉及到消費端, 所以在第二點中, 還應該增加一個字段負責指向消費端.

經(jīng)過這幾點的分析, 是不是發(fā)現(xiàn)其實和 Handler 機制差不多.

  1. Looper 負責輪詢
  2. Message 代表消息, 內(nèi)部單向鏈表.
    • what 作為標識符
    • when 代表時間.
    • data 數(shù)據(jù)存放鍵值對
    • target 指向消費端
  3. MessageQueue 存放消息集合.
  4. Handler 負責生產(chǎn)和處理消息.

現(xiàn)在將對 Android 消息機制中的這幾個組成部分逐個做出總結(jié).

1. Handler

Handler 是線程間傳遞消息的即時接口, 分為生產(chǎn)線程和消費線程.
生產(chǎn)線程: 在消息隊列中創(chuàng)建, 插入或者移除消息

send *
post *
remove *

消費線程: 處理消息.

handleMessage

每個 Handler 都有一個與之關(guān)聯(lián)的 LooperMessageQueue, 有兩種創(chuàng)建 Handler 的方式 (這里說的兩種不是兩個構(gòu)造函數(shù), 而是構(gòu)造函數(shù)的分類. )

  1. 通過默認的構(gòu)造方法, 使用當前線程中關(guān)聯(lián)的 Looper.
  2. 顯示的指定使用 Looper.

如果沒有指定 LooperHandler 是無法工作的, 因為它無法將消息放到消息隊列中, 同樣的, 也無法獲取要處理的消息.

public Handler(Callback callback, boolean async) {
  if (FIND_POTENTIAL_LEAKS) {
      final Class<? extends Handler> klass = getClass();
      if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) {
          Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName());
      }
  }

  mLooper = Looper.myLooper();
  if (mLooper == null) {
      throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
  }
  mQueue = mLooper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}

如果使用的是上面的構(gòu)造函數(shù), 它會檢查當前線程有沒有可用的 Looper 對象, 如果沒有, 就會拋出運行時異常. 如果正常的話, Handler 就會持有 Looper 中的消息隊列對象的引用.

并且, 同一個線程中會有多個 Handler, 但是他們共享同一個消息隊列, 因為他們共享的是同一個 Looper 對象.
?


2. Message

Message 是容納任意數(shù)據(jù)的容器, 生產(chǎn)線程發(fā)送消息給 Handler, Handler 將消息加入到 MessageQueue 中等待執(zhí)行. Message 提供了 3 種額外的信息, 以供 HandlerMessageQueue 處理時使用.

  1. what: 一種標識符, Handler 使用它來區(qū)分不同的消息, 從而采用不同的處理方法.
  2. when: 告訴消息隊列何時處理.
  3. targe: 表示是哪一個 Handler 應該處理這個消息.

?
Message 一般是通過以下幾種方式來創(chuàng)建的.

public final Message obtainMessage()
public final Message obtainMessage(int what)
public final Message obtainMessage(int what, Object obj)
public final Message obtainMessage(int what, int arg1, int arg2)
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)

Message 通過從消息對象池中獲取得到, 方法中提供的參數(shù)會放到消息體對應的字段中. Handler 同樣可以設(shè)置消息的目標為其自身, 這允許我們進行鏈式調(diào)用. 例如

mHandler.obtainMessage(MSG_SHOW_IMAGE, mBitmap).sendToTarget();

消息對象池是一個消息對象的單向鏈表集合, 它最大長度是 50, 在 Handler 處理完一條消息后, 這條消息就會返回到消息對象池中, 并且重置其所有字段.

當使用 Handlerpost 方法來執(zhí)行一個 Runnable 的時候, Handler 就會隱式的為我們創(chuàng)建了一個新的消息, 并且設(shè)置 Callback 參數(shù)來存儲這個 Runnable.

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

?

3. MessageQueue

MessageQueue 是一個消息體對象的無界的單向鏈表集合, 它按照時序?qū)⑾⒉迦腙犃? 最小時間戳的消息將會被優(yōu)先處理.

MessageQueue 通過 SystemClock.uptimeMillis() 獲取系統(tǒng)時間, 維護一個阻塞閾值, 當一個消息體的時間戳低于這個值的時候, 消息就會分發(fā)給 Handler 進行處理.

從 MessageQueue 中調(diào)用 next 方法循環(huán)獲取消息的時候, 如果消息為 Barrier 障柵的時候, 該隊列中的同步消息全部都會被攔截掉, 而放行所有的異步消息.

從 MessageQueue 中調(diào)用 next 方法循環(huán)獲取消息的時候, 在第一次循環(huán), 并且沒有消息或者頭部消息的執(zhí)行時間未到的情況下, 會執(zhí)行 IdleHandler. 直到下次調(diào)用 MessageQueue.next() 方法. (MessageQueue.next() 方法在 Looper.loop() 死循環(huán)中被調(diào)用.)

MessageQueue 中的 mPendingIdleHandlers 數(shù)組, 初始化的時候, 最小長度為 4.

沒有消息或者消息未到執(zhí)行時間, MessageQueue 將會調(diào)用 nativePollOnce 方法進行阻塞, 有新的消息入隊會根據(jù)情況喚醒. 內(nèi)部的消息執(zhí)行時間到了, 也會自動喚醒.
?


4. Looper

Looper 在 loop() 方法的死循環(huán)中調(diào)用 MessageQueue.next() 方法獲取消息, 然后分發(fā)給 Handler 處理. 一旦消息超過阻塞閾, 那么 Looper 就會在下一輪循環(huán)中讀取到它. Looper 在沒有消息分發(fā)的時候會進行阻塞狀態(tài)(其實是 MessageQueue 進行阻塞.), 當有消息時, 會繼續(xù)輪詢.

每個線程只能關(guān)聯(lián)一個 Looper, 給線程附加另外的 Looper 會拋出運行時異常. 通過使用 Looper 的 ThreadLocal 對象可以保證每個線程只關(guān)聯(lián)一個 Looper 對象.

調(diào)用 Looper.quit() 方法會立即終止 Looper 的死循環(huán). 并移除消息隊列中所有的消息.(延遲消息與非延遲消息).

調(diào)用 Looper.quitSafely() 方法將消息隊列中所有延遲消息移除, 非延遲消息則派發(fā)出去讓 Handler 處理.

只有消息隊列正在關(guān)閉退出的情況下, 在 Looper.loop() 方法死循環(huán)中調(diào)用 MessageQueue.next() 才會返回 null.

?

至此 Handler 消息機制已經(jīng)分析完畢. 另外還將會有一篇番外的 HandlerThread.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末请契,一起剝皮案震驚了整個濱河市聊浅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌找默,老刑警劉巖砚蓬,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铃剔,死亡現(xiàn)場離奇詭異懂拾,居然都是意外死亡捅儒,警方通過查閱死者的電腦和手機液样,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巧还,“玉大人鞭莽,你說我怎么就攤上這事◆锏唬” “怎么了澎怒?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長阶牍。 經(jīng)常有香客問我喷面,道長,這世上最難降的妖魔是什么走孽? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任惧辈,我火速辦了婚禮,結(jié)果婚禮上磕瓷,老公的妹妹穿的比我還像新娘盒齿。我一直安慰自己,他們只是感情好困食,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布边翁。 她就那樣靜靜地躺著,像睡著了一般陷舅。 火紅的嫁衣襯著肌膚如雪倒彰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天莱睁,我揣著相機與錄音待讳,去河邊找鬼芒澜。 笑死,一個胖子當著我的面吹牛创淡,可吹牛的內(nèi)容都是我干的痴晦。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼琳彩,長吁一口氣:“原來是場噩夢啊……” “哼誊酌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起露乏,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤碧浊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瘟仿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箱锐,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年劳较,在試婚紗的時候發(fā)現(xiàn)自己被綠了驹止。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡观蜗,死狀恐怖臊恋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情墓捻,我是刑警寧澤抖仅,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站毙替,受9級特大地震影響岸售,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜厂画,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拷邢。 院中可真熱鬧袱院,春花似錦、人聲如沸瞭稼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽环肘。三九已至欲虚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間悔雹,已是汗流浹背复哆。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工欣喧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人梯找。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓唆阿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锈锤。 傳聞我的和親對象是個殘疾皇子驯鳖,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350