Android消息機(jī)制


image.png

消息機(jī)制的概述

  1. Handler:Android消息機(jī)制的上層接口画侣,日常開發(fā)中被開發(fā)者用來更新UI缰趋,可以輕松將任務(wù)切換到Handler所在線程去執(zhí)行揉燃。
  2. Android的消息機(jī)制:主要是指Handler的運(yùn)行機(jī)制迎卤,需要底層的MessageQueue和Looper支撐墩弯。MessageQueue是消息隊(duì)列它匕,結(jié)構(gòu)是單向鏈表展融,而Looper是以死循環(huán)的形式查詢消息并處理新消息。
  3. ThreadLocal:Looper中的一個成員變量豫柬,在不同線程中互不干擾存儲并提供數(shù)據(jù)告希,通過ThreadLocal可以輕松獲取每個線程中的Looper。線程默認(rèn)沒有Looper烧给,如果要用Handler燕偶,就必須為線程創(chuàng)建Looper。
  4. UI線程:本質(zhì)是ActivityThread础嫡,它被創(chuàng)建的時候就會初始化Looper指么,所以在主線程中默認(rèn)可以使用Handler。
  5. 只能在主線程中訪問UI榴鼎,是通過ViewRootImpl的checkThread方法檢查的伯诬,之所以有這個限制,是因?yàn)锳ndroid的UI控件是非線程安全的巫财,如果多個線程同事訪問可能導(dǎo)致UI控件處于不可預(yù)期狀態(tài)盗似。

Android消息機(jī)制分析

  1. ThreadLocal工作原理
  • ThreadLocal是一個線程內(nèi)部數(shù)據(jù)存儲內(nèi),在指定線程存儲數(shù)據(jù)平项,數(shù)據(jù)存儲之后赫舒,只能在指定線程中獲取到數(shù)據(jù)悍及,其他線程無法獲取數(shù)據(jù)。
  • 使用場景:①數(shù)據(jù)以線程為作用域接癌,且不同線程有不同數(shù)據(jù)副本心赶,就可以考慮使用ThreadLocal ②復(fù)雜邏輯下的對象傳遞
  • get/set方法理解:首先獲取當(dāng)前線程t,然后獲取t.threadLocals:ThreadLocalMap對象扔涧,set方法會把ThreadLocal作為key园担,泛型T作為value存儲。如果threadLocals對象為空枯夜,那么先創(chuàng)建這個對象弯汰,然后存儲key/value。get也是同樣的邏輯湖雹。
  1. MessageQueue工作原理
  • 主要包含兩個操作咏闪,插入和讀取,讀取伴隨刪除摔吏。插入和讀取分別對應(yīng)enqueueMessage和next鸽嫂,enqueueMessage作用是往隊(duì)列中插入一條消息,next是從隊(duì)列中讀取一條消息并將其從消息隊(duì)列中移除征讲。next是一個無限循環(huán)方法据某,隊(duì)列中沒有消息,一直阻塞在這里诗箍,有新消息時癣籽,next方法返回消息并從單鏈表中移除該消息。
  1. Looper工作原理
  • 在線程中通過prepare方法初始化Looper對象滤祖,prepareMainLooper是專門用來給主線程ActivityThread創(chuàng)建Looper對象的筷狼,本質(zhì)也是調(diào)用prepare方法。在任何線程中都可以通過getMainLooper獲取主線程的Looper匠童。
  • Looper的構(gòu)造方法中會初始化一個MessageQueue對象埂材,并且保存當(dāng)前線程。
  • 調(diào)用loop方法開啟消息循環(huán)汤求,loop方法是一個死循環(huán)俏险,會一直從MessageQueue中通過next方法獲取消息,并且處理消息扬绪。當(dāng)沒有消息時寡喝,next方法阻塞,導(dǎo)致loop方法也一直阻塞勒奇。唯一跳出循環(huán)的方式是MessageQueue的next方法中返回null预鬓。
  • 可以通過Looper的quit和quitSafety退出一個Looper。quit直接退出Looper,而quitSafety是設(shè)置一個退出標(biāo)記格二,把消息隊(duì)列中的消息處理完畢之后安全退出劈彪。Looper退出后,Handler發(fā)送消息會失敗顶猜,send方法返回false沧奴。Looper的quit或quitSafety也會調(diào)用MessageQueue中的對應(yīng)方法,把隊(duì)列標(biāo)記為退出狀態(tài)长窄,這時候next方法就會返回null滔吠。子線程中的Looper使用完后都需要調(diào)用quit或quitSafety方法,不然子線程一直處于等待狀態(tài)挠日,無法結(jié)束疮绷。
  1. Handler的工作原理
  • Handler包含消息的發(fā)送和接收過程,消息發(fā)送通過一些列post方法和send方法實(shí)現(xiàn)嚣潜,post方法的本質(zhì)也是調(diào)用send方法冬骚。而發(fā)送消息的過程很簡單,就是往Looper.MessageQueue中插入一條消息懂算。
  • Handler發(fā)送消息后只冻,MessageQueue通過next把消息交給Looper處理,Looper調(diào)用Handler的dispatchMessage方法计技。
  • dispatchMessage中有三層邏輯:第一:如果消息Message設(shè)置了callback(Runnable對象)喜德,那么直接執(zhí)行這個Runnable。第二:如果Handler設(shè)置了Handler.Callback垮媒,則執(zhí)行callback的handlerMessage方法住诸。第三:如果第一第二不滿足,則執(zhí)行Handler自身的handleMessage方法
  • 從上面的分析可以看出涣澡,Handler的實(shí)現(xiàn)有兩種方式:
//派生Handler子類創(chuàng)建Handler對象
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // TODO: 2018/7/9 處理消息
    }
};

//通過傳參Handler.Callback創(chuàng)建Handler對象
    Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // TODO: 2018/7/9 處理消息,false=需要進(jìn)一步處理丧诺,true=不需要進(jìn)一步處理
            return false;
        }
    });
  1. 主線程的消息循環(huán)
  • 主線程ActivityThread的入口方法為main,在main方法中調(diào)用Looper.prepareMainLooper創(chuàng)建主線程的Looper和MessageQueue(Looper構(gòu)造方法中初始化)。然后調(diào)用looper.loop開始主線程消息循環(huán)租副。
  • 開啟消息循環(huán)之后郑藏,主線程還需要一個Handler和消息隊(duì)列交互,這個Handler就是ActivityThraed.H呵晚,它內(nèi)部定義了一組消息蜘腌,包括四大組件的生命周期消息,如下了所示:
private class H extends Handler {
       public static final int LAUNCH_ACTIVITY         = 100;
       public static final int PAUSE_ACTIVITY          = 101;
       public static final int PAUSE_ACTIVITY_FINISHING= 102;
       public static final int STOP_ACTIVITY_SHOW      = 103;
       public static final int STOP_ACTIVITY_HIDE      = 104;
       public static final int SHOW_WINDOW             = 105;
       public static final int HIDE_WINDOW             = 106;
       public static final int RESUME_ACTIVITY         = 107;
       public static final int SEND_RESULT             = 108;
       public static final int DESTROY_ACTIVITY        = 109;
       public static final int BIND_APPLICATION        = 110;
       public static final int EXIT_APPLICATION        = 111;
       public static final int NEW_INTENT              = 112;
       public static final int RECEIVER                = 113;
       public static final int CREATE_SERVICE          = 114;
       public static final int SERVICE_ARGS            = 115;
       public static final int STOP_SERVICE            = 116;
       ...
   }
        

一個問題

關(guān)于Handler消息機(jī)制饵隙,經(jīng)常會有這樣一個問題:Looper.loop死循環(huán)為什么不會卡死?
要解答這個問題撮珠,首先,我們來說一下線程和進(jìn)程金矛,每個應(yīng)用運(yùn)行時都會創(chuàng)建一個進(jìn)程芯急,多數(shù)情況下App運(yùn)行在一個進(jìn)程中勺届,除非指定android:process或者通過native代碼fork進(jìn)程。在CPU看來娶耍,線程是一段可執(zhí)行代碼免姿,代碼執(zhí)行完畢,線程生命周期結(jié)束榕酒,退出線程胚膊。
而對于主線程,肯定不希望運(yùn)行一段時間就自動退出想鹰,那么如何保證一直存活紊婉,簡單的做法是通過死循環(huán)讓代碼一直執(zhí)行下去
那么死循環(huán)是否會浪費(fèi)CPU資源呢杖挣?其實(shí)不會肩榕,主線程的MessageQueue沒有消息是,變阻塞在loop的next中惩妇,此時線程釋放CPU資源進(jìn)入休眠狀態(tài)株汉,直到下個消息發(fā)生,喚醒主線程工作歌殃。所以主線程多數(shù)情況處于休眠狀態(tài)乔妈,并不會消耗大量CPU資源
Activity/Service生命周期如何在死循環(huán)外執(zhí)行的氓皱?ActivityThread的內(nèi)部類H繼承自Handler路召,通過Handler發(fā)送各種類型的消息(包括Activity/Service生命周期消息),死循環(huán)處理消息波材,就可以回調(diào)Activity/Service的生命周期方法股淡。

這里再用簡單的描述來總結(jié)這個問題:
Android是基于事件驅(qū)動,應(yīng)用啟動后會進(jìn)入一個死循環(huán)監(jiān)聽事件廷区,而Android中幾乎所有的系統(tǒng)調(diào)用都是通過主線程的Handler發(fā)送消息唯灵,然后回調(diào)handleMessage,如果退出死循環(huán)隙轻,就不能響應(yīng)事件了埠帕,應(yīng)用程序就會退出了。當(dāng)沒有Message的時候玖绿,主線程釋放CPU資源敛瓷,處于休眠狀態(tài)(Linux pipe/epoll機(jī)制)

Android消息機(jī)制總結(jié)

用一張圖來總結(jié)Handler消息機(jī)制:


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斑匪,隨后出現(xiàn)的幾起案子呐籽,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绝淡,死亡現(xiàn)場離奇詭異宙刘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)牢酵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門悬包,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人馍乙,你說我怎么就攤上這事布近。” “怎么了丝格?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵撑瞧,是天一觀的道長。 經(jīng)常有香客問我显蝌,道長预伺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任曼尊,我火速辦了婚禮酬诀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘骆撇。我一直安慰自己瞒御,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布神郊。 她就那樣靜靜地躺著肴裙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涌乳。 梳的紋絲不亂的頭發(fā)上蜻懦,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機(jī)與錄音夕晓,去河邊找鬼宛乃。 笑死,一個胖子當(dāng)著我的面吹牛运授,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乔煞,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼吁朦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了渡贾?” 一聲冷哼從身側(cè)響起逗宜,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纺讲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體擂仍,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年熬甚,在試婚紗的時候發(fā)現(xiàn)自己被綠了逢渔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡乡括,死狀恐怖肃廓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诲泌,我是刑警寧澤盲赊,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站敷扫,受9級特大地震影響哀蘑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜葵第,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一绘迁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧羹幸,春花似錦脊髓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屏镊,卻和暖如春依疼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背而芥。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工律罢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棍丐。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓误辑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親歌逢。 傳聞我的和親對象是個殘疾皇子巾钉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

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