Android中的消息處理機(jī)制學(xué)習(xí)筆記

目錄

  • 簡(jiǎn)介
  • ThreadLocal
  • MessageQueue
  • Looper
  • Handler

簡(jiǎn)介

消息處理機(jī)制應(yīng)該說基本都用過,因?yàn)锳ndroid中不允許在UI線程中進(jìn)行一些耗時(shí)的操作悼粮,否則就會(huì)出現(xiàn)ANR略吨,而將耗時(shí)操作放在子線程中運(yùn)行,運(yùn)行結(jié)束后捞稿,子線程需要返回?cái)?shù)據(jù)給UI線程,但是子線程中是不能直接更新UI線程的數(shù)據(jù)的,因此就需要子線程將數(shù)據(jù)法統(tǒng)給UI線程葫哗,然后UI線程進(jìn)行更新,也就是我們一直使用的Handler球涛。

整體的工作流程:在一個(gè)線程創(chuàng)建一個(gè)Handler劣针,在另一個(gè)線程線程中通過Handler將一個(gè)Message 發(fā)送到創(chuàng)建這個(gè)Handler的線程進(jìn)行處理。
這中間有幾點(diǎn)需要注意:
1.創(chuàng)建Handler的線程必須要有一個(gè)Looper亿扁,否則就會(huì)報(bào)錯(cuò)捺典,我們平時(shí)在UI線程創(chuàng)建的時(shí)候UI線程自己已經(jīng)創(chuàng)建好了Looper,因此我們感覺不到从祝。
2.在調(diào)用了Handler的send方法之后襟己,其實(shí)是Handler將消息放到了待處理消息的線程的消息隊(duì)列中,即MessageQueue牍陌。每個(gè)線程中的Looper都維護(hù)著一個(gè)消息隊(duì)列擎浴,且一個(gè)線程中只有一個(gè),只能本線程訪問毒涧。
3.線程中的Looper無限循環(huán)的讀取消息隊(duì)列贮预,有消息來時(shí)就拿出來進(jìn)行處理,沒有就阻塞契讲。
簡(jiǎn)單的說仿吞,線程A創(chuàng)建了Handler,線程B用這個(gè)Handler用來發(fā)送消息到A的消息隊(duì)列捡偏,A的Looper不斷的循環(huán)取消息唤冈,取出來后A中的Handler進(jìn)行相應(yīng)的操作。

消息處理機(jī)制主要就是這四部分組成:Handler银伟、Looper你虹、MessageQueue、Message彤避,大致了解了工作流程中之后傅物,下來就分別學(xué)習(xí)一下每個(gè)模塊的和原理。

ThreadLocal

這個(gè)有必要提一下忠藤。
這個(gè)類主要是用來存儲(chǔ)線程內(nèi)部的數(shù)據(jù)的挟伙。通過它可以使得線程存儲(chǔ)數(shù)據(jù),而且只有自己才可以獲取到數(shù)據(jù),其他線程無法獲取尖阔,即使不同的線程訪問的是同一個(gè)ThreadLocal對(duì)象贮缅。前面提到的Handler在創(chuàng)建的時(shí)候就會(huì)使用當(dāng)前線程的Looper構(gòu)造消息循環(huán)系統(tǒng),那么Handler在創(chuàng)建的時(shí)候獲取當(dāng)前線程的Looper就是通過ThreadLocal介却。使用:通過構(gòu)造ThreadLocal對(duì)象谴供,然后調(diào)用set與get方法即可。
set實(shí)現(xiàn)原理:
ThreadLocal有一個(gè)靜態(tài)內(nèi)部類ThreadLocalMap齿坷,這個(gè)Map的鍵就是ThreadLocal桂肌,值則是我們set進(jìn)去的一個(gè)Object。
這個(gè)Map在創(chuàng)建的時(shí)候是根據(jù)當(dāng)前線程來創(chuàng)建的永淌。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

在set的時(shí)候如果當(dāng)前線程中不存在Map則進(jìn)行創(chuàng)建崎场,如果已經(jīng)存在則將值set進(jìn)去。
大致的set過程是這樣的:ThreadLocalMap中有一個(gè)Entry(鍵是ThreadLocal遂蛀,值是傳入的Object)數(shù)組谭跨,根據(jù)傳入的鍵(ThreadLocal)計(jì)算出一個(gè)hash值,映射到數(shù)組index上李滴,將ThreadLocal作為鍵value作為值的一個(gè)Entry存到數(shù)組的index+1上螃宙。
有點(diǎn)繞,梳理一下所坯,當(dāng)每個(gè)Thread都用的是一個(gè)ThreadLocal谆扎,然后根據(jù)ThreadLocal為每一個(gè)線程都創(chuàng)建一個(gè)Map,這個(gè)map中存放的是ThreadLocal以及set進(jìn)去的值芹助。
然后是get方法:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

根據(jù)線程獲取到map堂湖,其中有一個(gè)數(shù)組,然后根據(jù)ThreadLocal獲取到數(shù)組中的Entry周瞎,再獲取到其中的值苗缩。

MessageQueue

消息隊(duì)列,主要用于放入消息和取出消息声诸,其中實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)其實(shí)是單鏈表。主要方法有兩個(gè):boolean enqueueMessage(Message msg, long when)Message next()退盯,分別對(duì)應(yīng)的是入隊(duì)和出隊(duì)的操作彼乌。
在出隊(duì)操作中有一個(gè)無限循環(huán),如果沒有消息渊迁,則Looper調(diào)用next就會(huì)阻塞在這里慰照,一直循環(huán)。

Looper

主要扮演的角色就是一直循環(huán)從隊(duì)列里拿消息琉朽。
創(chuàng)建Handler的時(shí)候需要該線程有Looper毒租,創(chuàng)建Looper的過程:

//創(chuàng)建一個(gè)Looper
Looper.prepare();
//開啟無限循環(huán)
Looper.loop();

另外,如果是自己在子線程中創(chuàng)建的Looper箱叁,在使用完畢之后一定要關(guān)掉墅垮,否則將會(huì)無限循環(huán)下去惕医。
關(guān)閉的方式:在Looper中有以下兩個(gè)方法:

public void quitSafely() {
    mQueue.quit(true);
}

public void quit() {
    mQueue.quit(false);
}

簡(jiǎn)單的說就是quit是直接退出,quitSafely則是在隊(duì)列中的消息都處理完之后退出算色。
loop方法:
這個(gè)方法是一個(gè)無限循環(huán)方法抬伺,唯一的出口就是當(dāng)隊(duì)列的next返回了null,也就是在調(diào)用了mQueue.quit方法之后才會(huì)退出灾梦。
然后看一下Looper取出消息之后干了啥:
msg.target.dispatchMessage(msg);
這個(gè)msg.target就很容易了峡钓,就是前面創(chuàng)建的Handler。
也就是說若河,取出消息之后能岩,交給了Handler來進(jìn)行處理。

Handler

首先是Handler的send

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);
}

可以看到萧福,就是將消息入隊(duì)了拉鹃。
前面提到,在Looper取出消息的時(shí)候统锤,就會(huì)交給Handler來處理:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

終于毛俏,看到我們熟悉的handleMessage(msg);了。
那這兩個(gè)callback又是怎么回事饲窿?
msg.callback這個(gè)是一個(gè)Runnable對(duì)象煌寇,也就是Handler的傳遞的參數(shù)。
mCallback:

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 *
 * @param msg A {@link android.os.Message Message} object
 * @return True if no further handling is desired
 */
public interface Callback {
    public boolean handleMessage(Message msg);
}

可以通過這個(gè)Callback 來創(chuàng)建一個(gè)不需要派生子類的Handler逾雄,也就是另外一種創(chuàng)建Handler的方式阀溶,一般我們都是繼承Handler復(fù)寫handleMessage。

還有一點(diǎn)鸦泳,就是創(chuàng)建一個(gè)Handler可以通過傳遞一個(gè)Looper來實(shí)現(xiàn)银锻,默認(rèn)創(chuàng)建的時(shí)候,我們是獲取創(chuàng)建線程的Looper做鹰,而這種創(chuàng)建方式就指定特定的Looper击纬,這樣的話就可以實(shí)現(xiàn)不同的線程中創(chuàng)建的Handler共用同一個(gè)Looper,也就是共用消息隊(duì)列钾麸。

主要的核心源碼都沒有貼更振,只是大致了解了其實(shí)現(xiàn)方式和流程,一方面是篇幅太長(zhǎng)了饭尝,另外主要就是水平不是特別夠肯腕,也就了解個(gè)大概,后面還需要繼續(xù)深入學(xué)習(xí)钥平。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末实撒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌知态,老刑警劉巖捷兰,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異肴甸,居然都是意外死亡寂殉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門原在,熙熙樓的掌柜王于貴愁眉苦臉地迎上來友扰,“玉大人,你說我怎么就攤上這事庶柿〈骞郑” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵浮庐,是天一觀的道長(zhǎng)甚负。 經(jīng)常有香客問我,道長(zhǎng)审残,這世上最難降的妖魔是什么梭域? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮搅轿,結(jié)果婚禮上病涨,老公的妹妹穿的比我還像新娘。我一直安慰自己璧坟,他們只是感情好既穆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著雀鹃,像睡著了一般幻工。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上黎茎,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天囊颅,我揣著相機(jī)與錄音,去河邊找鬼傅瞻。 笑死迁酸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的俭正。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼焙畔,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼掸读!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤叨恨,失蹤者是張志新(化名)和其女友劉穎俊卤,沒想到半個(gè)月后玩郊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡留搔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铛铁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隔显。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖饵逐,靈堂內(nèi)的尸體忽然破棺而出括眠,到底是詐尸還是另有隱情,我是刑警寧澤倍权,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布掷豺,位于F島的核電站,受9級(jí)特大地震影響薄声,放射性物質(zhì)發(fā)生泄漏当船。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一默辨、第九天 我趴在偏房一處隱蔽的房頂上張望德频。 院中可真熱鬧,春花似錦廓奕、人聲如沸抱婉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒸绩。三九已至,卻和暖如春铃肯,著一層夾襖步出監(jiān)牢的瞬間患亿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工押逼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留步藕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓挑格,卻偏偏與公主長(zhǎng)得像咙冗,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子漂彤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

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