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

Android消息機(jī)制主要是指Handler的運(yùn)行機(jī)制,以及MessageQueue和Looper的工作過(guò)程驼修。

概述

從開(kāi)發(fā)的角度來(lái)說(shuō)诈铛,Handler是Android消息機(jī)制的上層接口,開(kāi)發(fā)中只需要關(guān)心Handler的交互即可耳峦。

很多人認(rèn)為Handler的作用是更新UI焕毫,這個(gè)認(rèn)為沒(méi)有錯(cuò)但是比較片面,它只是Handler的一種使用場(chǎng)景循签。
Handler的主要作用是將一個(gè)任務(wù)切換到某一個(gè)指定的線程中去執(zhí)行疙咸。

Handler的運(yùn)行需要底層的MessageQueue和Looper的支撐。
MessageQueue叫做消息隊(duì)列乞旦,是用鏈表來(lái)實(shí)現(xiàn)的一個(gè)存儲(chǔ)消息的列表腔召。
Looper是混喚起扮惦,會(huì)以無(wú)限循環(huán)的形式查找消息。線程默認(rèn)沒(méi)有Looper浊仆,需要手動(dòng)為其創(chuàng)建,但是主線程即UI線程舔琅,會(huì)在創(chuàng)建的時(shí)候自動(dòng)初始化一個(gè)Looper洲劣。
ThreadLocal是Looper中的一個(gè)特殊概念,它不是線程而是一種數(shù)據(jù)載體囱稽,它可以互不干擾的為每一個(gè)線程存儲(chǔ)/提供數(shù)據(jù)。

由于Android的UI控件是線程不安全的流昏,多線程并發(fā)訪問(wèn)會(huì)導(dǎo)致控件處于不可預(yù)計(jì)的狀態(tài)吞获,但若加線程鎖,又會(huì)導(dǎo)致其訪問(wèn)邏輯變得復(fù)雜并且低效刁绒,得不償失烤黍。所以Android在設(shè)計(jì)上,規(guī)定訪問(wèn)UI線程只能在主線程中進(jìn)行初狰,并且在ViewRootImpl接口中對(duì)UI操作做了驗(yàn)證互例。

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(...);
        }
    }

并且提供了Handler機(jī)制,解決在子線程中訪問(wèn)UI線程的問(wèn)題腥光。
Handler的使用此處不表糊秆,這次的重點(diǎn)是Handler以及其底層類的運(yùn)作原理。

MessageQueue

MessageQueue捉片,即消息隊(duì)列,內(nèi)部通過(guò)一個(gè)單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息列表伍纫,使用單鏈表而非隊(duì)列是因?yàn)閱捂湵碓诓迦胍约皠h除操作上比較有優(yōu)勢(shì)。
消息隊(duì)列主要有兩個(gè)操作赔蒲,插入和刪除(讀攘际)。
enqueueMessage是插入方法母市,向隊(duì)列中插入一條消息
next是刪除方法,從消息隊(duì)列中取出一條消息焕刮,并將其從隊(duì)列中刪除墙杯。并且next方法是一個(gè)無(wú)限循環(huán)的方法,如果消息隊(duì)列中沒(méi)有消息溉旋,那么next方法會(huì)被阻塞嫉髓,直到有新消息出現(xiàn)。

ThreadLocal

介紹Looper之前必須先介紹ThreadLocal這個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類梧油。

ThreadLocal也叫線程本地變量州邢,它的特性是,它可以互不干擾的為每一個(gè)線程存儲(chǔ)/提供數(shù)據(jù)骗村,使用的時(shí)候會(huì)對(duì)線程自身的內(nèi)存進(jìn)行操作呀枢,并且提供了get/set方法來(lái)訪問(wèn)。簡(jiǎn)單來(lái)說(shuō)ThreadLocal可以為不同的線程提供不同的數(shù)據(jù)副本琅拌。

白話有點(diǎn)抽象,舉個(gè)例子:


    private ThreadLocal<String> stringThreadLocal;
    stringThreadLocal = new ThreadLocal<>();
    stringThreadLocal.set("main");
    Log.e(TAG, "run: " + stringThreadLocal.get());

    new Thread(new Runnable() {
        @Override
        public void run() {
            stringThreadLocal.set("Thread1");
            Log.e(TAG, "run: " + stringThreadLocal.get());
        }
    }).start();

    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.e(TAG, "run: " + stringThreadLocal.get());
        }
    }).start();
12-26 16:25:57.208 19506-19506/com.zx.studyapp E/WindowActivity: run: main
12-26 16:25:57.209 19506-19800/com.zx.studyapp E/WindowActivity: run: Thread1
12-26 16:25:57.210 19506-19801/com.zx.studyapp E/WindowActivity: run: null

ThreadLocal底層也是用Map實(shí)現(xiàn)财忽,可以近似的理解為即彪,此Map的key是線程標(biāo)識(shí)活尊,value就是當(dāng)前線程所對(duì)應(yīng)的值。

ThreadLocal的這個(gè)特性深胳,正好滿足Handler中Looper的需求:Looper的作用域是當(dāng)前線程铜犬,并且不同的線程擁有不同的Looper。

Looper

Looper 就是循環(huán)器癣猾,它會(huì)以無(wú)限循環(huán)的形式,向MessageQueue中查找新消息夸盟,有新消息就立即處理像捶,否則就阻塞。
Handler工作的時(shí)候需要Looper循環(huán)器的參與释簿,但是除了主線程硼莽,其他線程不會(huì)默認(rèn)初始化一個(gè)Looper供我們使用,這時(shí)候就需要手動(dòng)創(chuàng)建一個(gè)Looper渐尿。

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();//創(chuàng)建一個(gè)Looper
        Handler handler = new Handler();
        Looper.loop();//開(kāi)啟消息循環(huán)
    }
}).start();

Looper的loop方法中有一個(gè)死循環(huán)矾瑰,用來(lái)讀取MessageQueue中的新消息。
Looper除了prepare方法之外凉夯,還提供了prepareMainLooper方法為主線程創(chuàng)建Looper使用,本質(zhì)上還是prepare方法劲够。
此外,Looper還提供了兩個(gè)方法來(lái)退出消息循環(huán)征绎,分別是quit和quitSafely,他們的區(qū)別是:
quit會(huì)直接退出Looper柴墩;
quitSafely會(huì)設(shè)定一個(gè)標(biāo)記凫岖,當(dāng)隊(duì)列中已有的消息全部處理完成后,才會(huì)退出歼指。
Looper退出后甥雕,Handler發(fā)送的消息就會(huì)失敗,即send方法返回false犀农。在子線程中,應(yīng)該在所有任務(wù)處理完成后立即調(diào)用quit方法來(lái)退出消息循環(huán)赁濒,否則這個(gè)子線程就會(huì)持續(xù)處于等待狀態(tài)孟害,無(wú)法終止。

Handler

Handler主要工作是發(fā)送和接受消息击你。
Handler有一個(gè)特殊的構(gòu)造方法谎柄,是通過(guò)特定的Looper來(lái)構(gòu)造,這個(gè)方法會(huì)在默認(rèn)構(gòu)造方法中被調(diào)用朝巫,所以如果當(dāng)前線程沒(méi)有Looper的話,就會(huì)拋出無(wú)法創(chuàng)建Handler的異常拙吉。
Handler發(fā)送主要通過(guò)post或者send方法來(lái)實(shí)現(xiàn)(最終都是通過(guò)send實(shí)現(xiàn))。Handler發(fā)送消息的過(guò)程筷黔,實(shí)質(zhì)上就是向消息隊(duì)列中插入一條數(shù)據(jù)。
Handler.send在子線程發(fā)送了一條消息椎例,之后Looper的loop方法不再阻塞名眉,通過(guò)MessageQueue.next獲取了新消息凰棉,接著通過(guò)msg.targer.dispatchMessage方法,將其傳遞給Looper所在線程的Handlder撒犀,完成了一次消息傳遞福压。
如下圖:


handler工作流程.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荆姆,一起剝皮案震驚了整個(gè)濱河市胆筒,隨后出現(xiàn)的幾起案子诈豌,更是在濱河造成了極大的恐慌,老刑警劉巖彤蔽,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庙洼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蚁袭,警方通過(guò)查閱死者的電腦和手機(jī)石咬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)虏束,“玉大人,你說(shuō)我怎么就攤上這事镇匀。” “怎么了汗侵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵晰韵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我栏尚,道長(zhǎng),這世上最難降的妖魔是什么译仗? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任纵菌,我火速辦了婚禮,結(jié)果婚禮上咱圆,老公的妹妹穿的比我還像新娘功氨。我一直安慰自己,他們只是感情好疑故,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布纵势。 她就那樣靜靜地躺著,像睡著了一般软舌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佛点,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天超营,我揣著相機(jī)與錄音,去河邊找鬼演闭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛窝革,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虐译,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼漆诽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拴泌?” 一聲冷哼從身側(cè)響起惊橱,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤箭昵,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后正林,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體颤殴,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年杈绸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了矮瘟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡劫侧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出写妥,到底是詐尸還是另有隱情,我是刑警寧澤耳标,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布次坡,位于F島的核電站,受9級(jí)特大地震影響砸琅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜症脂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一诱篷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棕所,春花似錦、人聲如沸琳省。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)桦他。三九已至,卻和暖如春快压,著一層夾襖步出監(jiān)牢的瞬間础锐,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工拦宣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸵隧。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像珊蟀,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子育灸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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