Androd開發(fā)藝術(shù)探索 第10章 Android的消息機(jī)制 讀書筆記


前言:
對(duì)于熟悉Android消息機(jī)制的小伙伴,可以跳到最后力穗,看主線程的消息循環(huán)转砖。


Android的消息機(jī)制主要是指Handler的運(yùn)行機(jī)制蔫仙,Handler的運(yùn)行需要底層MessageQueue和Looper的支撐。MessageQueue是采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)消息列表巨坊。 Looper會(huì)以無(wú)限循環(huán)的形式去查看是否有新消息撬槽,如果有就處理消息,否則就一直等待趾撵。ThreadLocal可以在不同線程中互不干擾的存儲(chǔ)并提供數(shù)據(jù)侄柔,通過ThreadLocal可以輕松的獲取每個(gè)線程的Looper。


10.1 Android消息機(jī)制概述

  1. Handler的主要作用是將某個(gè)任務(wù)切換到Handler所在的線程中去執(zhí)行占调。為什么Android要提供這個(gè)功能呢勋拟?這是因?yàn)锳ndroid規(guī)定訪問UI只能通過主線程,如果子線程訪問UI,程序會(huì)拋出異常妈候;ViewRootImpl在checkThread方法中做了判斷敢靡。
  2. 由于Android不建議在主線程進(jìn)行耗時(shí)操作,否則可能會(huì)導(dǎo)致ANR苦银。那我們耗時(shí)操作在子線程執(zhí)行完畢后啸胧,我們需要將一些更新UI的操作切換到主線程當(dāng)中去。所以系統(tǒng)就提供了Handler幔虏。
  3. 系統(tǒng)為什么不允許在子線程中去訪問UI呢纺念?
    因?yàn)锳ndroid的UI控件不是線程安全的,多線程并發(fā)訪問可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài)想括,為什么不加鎖陷谱?因?yàn)榧渔i機(jī)制會(huì)讓UI訪問邏輯變得復(fù)雜;其次鎖機(jī)制會(huì)降低UI訪問的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行烟逊。所以Android采用了高效的單線程模型來(lái)處理UI操作渣窜。
  4. Handler創(chuàng)建時(shí)會(huì)采用當(dāng)前線程的Looper來(lái)構(gòu)建內(nèi)部的消息循環(huán)系統(tǒng),如果當(dāng)前線程沒有Looper就會(huì)報(bào)錯(cuò)宪躯。Handler可以通過post方法發(fā)送一個(gè)Runnable到消息隊(duì)列中乔宿,也可以通過send方法發(fā)送一個(gè)消息到消息隊(duì)列中,其實(shí)post方法最終也是通過send方法來(lái)完成访雪。
  5. MessageQueue的enqueueMessage方法最終將這個(gè)消息放到消息隊(duì)列中详瑞,當(dāng)Looper發(fā)現(xiàn)有新消息到來(lái)時(shí),處理這個(gè)消息臣缀,最終消息中的Runnable或者Handler的handleMessage方法就會(huì)被調(diào)用坝橡,注意Looper是運(yùn)行Handler所在的線程中的,這樣一來(lái)業(yè)務(wù)邏輯就切換到了Handler所在的線程中去執(zhí)行了精置。

10.2 Android的消息機(jī)制分析

10.2.1 ThreadLocal的工作原理
  1. ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類驳庭,通過它可以在指定線程存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)后氯窍,只能在指定的線程可以獲取到存儲(chǔ)的數(shù)據(jù)饲常,對(duì)于其他線程則無(wú)法獲取到數(shù)據(jù)。一般來(lái)說(shuō)狼讨,當(dāng)數(shù)據(jù)是以線程作為作用域并且不同線程有不同副本的時(shí)候贝淤,就可以考慮使用ThreadLocal。對(duì)于Handler來(lái)說(shuō)政供,它需要獲取當(dāng)前線程的Looper,而Looper的作用于就是線程并且不同的線程具有不同的Looper播聪,通過ThreadLocal可以輕松實(shí)現(xiàn)線程中的存取。
  2. ThreadLocal的另一個(gè)使用場(chǎng)景是復(fù)雜邏輯下的對(duì)象傳遞布隔。
  3. ThreadLocal原理:不同線程訪問同一個(gè)ThreadLoacl的get方法离陶,ThreadLocal的get方法會(huì)從各自的線程中取出一個(gè)數(shù)組,然后再?gòu)臄?shù)組中根據(jù)當(dāng)前ThreadLocal的索引去查找對(duì)應(yīng)的Value值衅檀。
    (1) ThreadLocal set方法
    //PS:JDK中與Android SDK的ThreadLocal有些許區(qū)別招刨,我們以SDK中的為例
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

獲取到當(dāng)前線程,并從當(dāng)前線程中取出對(duì)應(yīng)的ThreadLocalMap(內(nèi)部由Entry[] table來(lái)存儲(chǔ)ThreadLoacl的值)哀军,再把相應(yīng)數(shù)據(jù)存入其中沉眶。如果ThreadLocalMap為空,則創(chuàng)建該線程的ThreadLocalMap對(duì)象杉适,再將ThreadLocal的值進(jìn)行存儲(chǔ)谎倔。
(2) ThreadLocal get方法

    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }
        return (T) values.getAfterMiss(this);
    }

獲取當(dāng)前線程,并從當(dāng)前線程中獲取對(duì)應(yīng)的ThreadLocalMap猿推,通過key獲取對(duì)應(yīng)的value片习;如果未獲取到對(duì)應(yīng)的ThreadLocalMap,則創(chuàng)建并將該對(duì)象返回。
(3)ThreadLocal的值在table數(shù)值中的位置總是ThreadLocal的索引+1藕咏。


10.2.2 消息隊(duì)列的工作原理
  1. MessageQueue主要有兩個(gè)操作状知,插入和讀取,讀取操作伴隨著刪除操作侈离;MessageQueue是通過單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息列表的。
  2. enqueueMessage方法的作用是往消息隊(duì)列插入一條消息筝蚕。next方法是一個(gè)無(wú)線循環(huán)的方法卦碾,如果消息隊(duì)列中沒有消息,那么next方法會(huì)一直阻塞在這里起宽。當(dāng)有新消息到來(lái)時(shí)洲胖,next方法會(huì)返回這條消息并將其從單鏈表中移除。

10.2.3 Looper的工作原理
  1. prepareMainLooper方法主要給主線程也就是ActivityThread創(chuàng)建Looper使用的坯沪,本質(zhì)也是通過prepare方法實(shí)現(xiàn)的绿映。
  2. Looper提供quitquitSafely來(lái)退出一個(gè)Looper,區(qū)別在于quit會(huì)直接退出Looper腐晾,而quitSafely會(huì)把消息隊(duì)列中已有的消息處理完畢后才安全地退出叉弦。
    Looper退出后,這時(shí)候通過Handler發(fā)送的消息會(huì)失敗藻糖,Handler的send方法會(huì)返回false淹冰。
    在子線程中,如果手動(dòng)為其創(chuàng)建了Looper巨柒,在所有事情做完后樱拴,應(yīng)該調(diào)用Looper的quit方法來(lái)終止消息循環(huán),否則這個(gè)子線程就會(huì)一直處于等待狀態(tài)洋满;而如果退出了Looper以后晶乔,這個(gè)線程就會(huì)立刻終止,因此建議不需要的時(shí)候終止Looper牺勾。
  3. loop方法會(huì)調(diào)用MessageQueue的next方法來(lái)獲取新消息正罢,而next是是一個(gè)阻塞操作,但沒有信息時(shí)驻民,next方法會(huì)一直阻塞在那里腺怯,這也導(dǎo)致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息川无,Looper就會(huì)處理這條消息:mas.target.dispatchMessage(msg),這里的msg.target是發(fā)送這條消息的Handler對(duì)象呛占,這樣Handler發(fā)送的消息最終又交給Handler來(lái)處理了。

10.2.4 Handler的工作原理

  1. Handler的工作主要包含消息的發(fā)送和接受過程懦趋。發(fā)送過程通過post的一系列方法和send的一系列方法來(lái)實(shí)現(xiàn)晾虑。Handler發(fā)送過程僅僅是向消息隊(duì)列中插入了一條消息。MessageQueue的next方法就會(huì)返回這條消息給Looper,Looper拿到這條消息就開始處理帜篇,最終消息會(huì)交給Handler來(lái)處理糙捺。
  2. Handler處理消息
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            //Message的callback是一個(gè)Runnable,       
           //也就是Handler的 post方法所傳遞的Runnable參數(shù)
            handleCallback(msg);
        } else {
            //如果給Handler設(shè)置了Callback的實(shí)現(xiàn),
            //則調(diào)用Callback的handleMessage(msg)
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //調(diào)用Handler的handleMessage方法來(lái)處理消息笙隙,
            //該Handler子類需重寫handlerMessage(msg)方法
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    public interface Callback {
        public boolean handleMessage(Message msg);
    }
    //默認(rèn)空實(shí)現(xiàn)
    public void handleMessage(Message msg) {
    }
  1. Handler還有一個(gè)特殊的構(gòu)造方法洪灯,可以指定一個(gè)特殊的Looper來(lái)構(gòu)造Handler。
    public Handler(Looper looper) {
        this(looper, null, false);
    }
  1. Handler創(chuàng)建需要Looper竟痰,否則會(huì)拋出異常签钩,默認(rèn)獲取當(dāng)前線程的Looper。主線程也就是ActivityThread會(huì)自動(dòng)創(chuàng)建Looper坏快,其他線程如果需要Looper均需要手動(dòng)創(chuàng)建铅檩。

10.3 主線程消息循環(huán)

  1. Android的主線程就是ActivityThread,主線程的入口方法為main莽鸿,在main方法中系統(tǒng)會(huì)通過Looper.prepareMainLooper()來(lái)創(chuàng)建主線程的Looper以及MessageQueue昧旨,并通過Looper.loop()來(lái)開啟主線程的消息循環(huán)。
public static void main(String[] args) {
        ... 
        Process.setArgV0("<pre-initialized>");
 
        Looper.prepareMainLooper();
 
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
 
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
}
  1. Looper.loop()祥得,這里是一個(gè)死循環(huán)兔沃,如果主線程的Looper終止,則應(yīng)用程序會(huì)拋出異常级及。那么問題來(lái)了粘拾,既然主線程卡在這里了,(1)那Activity為什么還能啟動(dòng)创千;(2)點(diǎn)擊一個(gè)按鈕仍然可以響應(yīng)缰雇?
    問題1:startActivity的時(shí)候,會(huì)向AMS(ActivityManagerService)發(fā)一個(gè)跨進(jìn)程請(qǐng)求(AMS運(yùn)行在系統(tǒng)進(jìn)程中)追驴,之后AMS啟動(dòng)對(duì)應(yīng)的Activity械哟;AMS也需要調(diào)用App中Activity的生命周期方法(不同進(jìn)程不可直接調(diào)用),AMS會(huì)發(fā)送跨進(jìn)程請(qǐng)求殿雪,然后由App的ActivityThread中的ApplicationThread會(huì)來(lái)處理暇咆,ApplicationThread會(huì)通過主線程線程的Handler將執(zhí)行邏輯切換到主線程。重點(diǎn)來(lái)了丙曙,主線程的Handler把消息添加到了MessageQueue爸业,Looper.loop會(huì)拿到該消息,并在主線程中執(zhí)行亏镰。這就解釋了為什么主線程的Looper是個(gè)死循環(huán)扯旷,而Activity還能啟動(dòng),因?yàn)樗拇蠼M件的生命周期都是以消息的形式通過UI線程的Handler發(fā)送索抓,由UI線程的Looper執(zhí)行的钧忽。
    問題2:和問題1原理一樣毯炮,最終都是由系統(tǒng)發(fā)消息來(lái)處理的,都經(jīng)過了Looper.loop()耸黑。
    問題2詳細(xì)分析請(qǐng)看原書作者的Android中MotionEvent的來(lái)源和ViewRootImpl

關(guān)于我

簡(jiǎn)書:HuDP
WeChat:mox1103
WeiBo:HuDP_
歡迎各位小伙伴留言交流[開心臉]

最后編輯于
?著作權(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)離奇詭異葫辐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)男翰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門另患,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纽乱,“玉大人蛾绎,你說(shuō)我怎么就攤上這事⊙涣校” “怎么了租冠?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)薯嗤。 經(jīng)常有香客問我顽爹,道長(zhǎng),這世上最難降的妖魔是什么骆姐? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任镜粤,我火速辦了婚禮,結(jié)果婚禮上玻褪,老公的妹妹穿的比我還像新娘肉渴。我一直安慰自己,他們只是感情好带射,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布同规。 她就那樣靜靜地躺著,像睡著了一般窟社。 火紅的嫁衣襯著肌膚如雪券勺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天灿里,我揣著相機(jī)與錄音关炼,去河邊找鬼。 笑死匣吊,一個(gè)胖子當(dāng)著我的面吹牛盗扒,可吹牛的內(nèi)容都是我干的跪楞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼侣灶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼甸祭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起褥影,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤池户,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后凡怎,有當(dāng)?shù)厝嗽跇淞掷锇l(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
  • 文/蒙蒙 一约急、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苗分,春花似錦厌蔽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至供填,卻和暖如春拐云,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背近她。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工叉瘩, 沒想到剛下飛機(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)容