每日一題: Handler源碼

每日一題: Handler源碼

深入了解handler

面試率: ★★★☆☆

面試技巧與建議

handler作為Android的主線程,是其主要的消息機(jī)制,作為的軟件開(kāi)發(fā)人員,掌握并熟知handler底層運(yùn)行原理已經(jīng)成為了Android開(kāi)發(fā)的一種標(biāo)配,面試必問(wèn).

面試建議

handler涉及很廣,我們可以選擇適合自己的深度來(lái)局部掌握該面試題,為什么局部掌握,因?yàn)槿秩チ私夂馁M(fèi)精力太大,而且原理邏輯繁雜,不適合快速吸收.因此下面我給出了兩種方案:
深入者如Looper樟蠕、Handler馏予、Message三者關(guān)系
淺入者如handler性能優(yōu)化,常見(jiàn)方法使用與區(qū)別,handler與子線程等相關(guān)問(wèn)題.

面試技巧

其實(shí)可以從側(cè)面的角度去分析這個(gè)問(wèn)題,如實(shí)際項(xiàng)目中如何正確的使用handler.使用時(shí)遇到過(guò)什么問(wèn)題,如何解決該問(wèn)題.

面試題

下面是從handler的源碼和實(shí)際開(kāi)發(fā)中提取出的一些面試問(wèn)題,從實(shí)際中出發(fā)探討handler源碼,面試中最恰當(dāng)不過(guò)的事情,莫過(guò)于此.

一個(gè)線程可以有幾個(gè)Lopper實(shí)例,為什么?

一個(gè)線程中只有一個(gè)Looper實(shí)例.

sThreadLocal是一個(gè)ThreadLocal對(duì)象榛鼎,可以在一個(gè)線程中存儲(chǔ)變量园欣∑浚可以看到狮杨,在第5行豺妓,將一個(gè)Looper的實(shí)例放入了ThreadLocal砂代,并且2-4行判斷了sThreadLocal是否為null蹋订,否則拋出異常。這也就說(shuō)明了Looper.prepare()方法不能被調(diào)用兩次刻伊,同時(shí)也保證了一個(gè)線程中只有一個(gè)Looper實(shí)例.

源碼

        public static final void prepare() {  
                if (sThreadLocal.get() != null) {  
                    throw new RuntimeException("Only one Looper may be created per thread");  
                }  
                sThreadLocal.set(new Looper(true));  
        }  

繼續(xù)看Lopper的構(gòu)造方法做了什么,那里面的loop()方法呢,知道finalfinal Looper me = myLooper()的作用嗎?

構(gòu)造方法中露戒,創(chuàng)建了一個(gè)MessageQueue(消息隊(duì)列)

final Looper me = myLooper()的final保證了loopr的唯一性.

構(gòu)造方法源碼:

        private Looper(boolean quitAllowed) {  
                mQueue = new MessageQueue(quitAllowed);  
                mRun = true;  
                mThread = Thread.currentThread();  
        }  

loop()方法源碼.

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//final了消息隊(duì)列

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); //阻塞輪詢消息
            if (msg == null) {      
                return;
            }
            
          //其他源碼省略....  
          }

通過(guò)前面1,2問(wèn)題可以總結(jié)下Looper的主要作用是什么?

Looper主要作用:

  1. 與當(dāng)前線程綁定,保證一個(gè)線程只會(huì)有一個(gè)Looper實(shí)例捶箱,同時(shí)一個(gè)Looper實(shí)例也只有一個(gè)MessageQueue智什。
  2. loop()方法,不斷從MessageQueue中去取消息丁屎,交給消息的target屬性的dispatchMessage去處理荠锭。

首先Looper.prepare()在本線程中保存一個(gè)Looper實(shí)例,然后該實(shí)例中保存一個(gè)MessageQueue對(duì)象晨川;因?yàn)長(zhǎng)ooper.prepare()在一個(gè)線程中只能調(diào)用一次证九,所以MessageQueue在一個(gè)線程中只會(huì)存在一個(gè)。

從Handler中有沒(méi)學(xué)到什么好的技術(shù),或者思想?

看過(guò)MessageQueue嗎,里面的for (;;) 和while(true) 區(qū)別:

  • while區(qū)別根據(jù)編譯器不同情況有所不同,例如寫死循環(huán)while(true)有的編譯器會(huì)傻傻的每次都把true做一下判斷.
  • for(;;)寫死循環(huán)比較好,減少了判斷
    編譯前 編譯后
    while (true){todo}; mov eax,true
    test eax,eax
    je foo+23h
    jmp foo+18h

編譯前 編譯后
for (;;){}; jmp foo+23h

一目了然共虑,for (;;)指令少愧怜,不占用寄存器,而且沒(méi)有判斷跳轉(zhuǎn)妈拌,比while (true)好叫搁。

Handler如何與MsgQueue關(guān)聯(lián)在一起?

這個(gè)問(wèn)題可以先觀察下Handler的構(gòu)造方法到底做了什么?

首先得到當(dāng)前線程中保存的Looper實(shí)例,進(jìn)而與Looper實(shí)例中的MessageQueue想綁定關(guān)聯(lián).

public Handler(Callback callback, boolean async) {
  //部分源碼省略...
  
  //獲取當(dāng)前線程保存的Looper實(shí)例
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
//通過(guò)上面mLooper獲取了實(shí)例中保存的MessageQueue,這樣兩者就綁定在一起了,關(guān)聯(lián)上.
        mQueue = mLooper.mQueue;  
        mCallback = callback; 
        mAsynchronous = async;  

使用handler發(fā)送Message的流程是什么?

sendMessage(hd) ->sendMessageAtTime(hd) ->
enqueueMessage(hd) ->dispatchMessage(looper) ->
handleMessage(looper)

那么在Activity中供炎,我們并沒(méi)有顯示的調(diào)用Looper.prepare()和Looper.loop()方法,為啥Handler可以成功創(chuàng)建呢?

這是因?yàn)樵贏ctivity的啟動(dòng)代碼中疾党,已經(jīng)在當(dāng)前UI線程ActivityThread調(diào)用了Looper.prepare()和Looper.loop()方法音诫。

子線程里可以創(chuàng)建handler嗎?

可以的,可以在一個(gè)子線程中去創(chuàng)建一個(gè)Handler雪位,然后使用這個(gè)handler實(shí)例在任何其他線程中發(fā)送消息竭钝,最終處理消息的代碼都會(huì)在你創(chuàng)建Handler實(shí)例的線程中運(yùn)行。

詳情見(jiàn)下面實(shí)例:

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare(); //創(chuàng)建Looper并與本線程綁定【第一步】

        mHandler = new Handler() {
            //定義并實(shí)現(xiàn)Handler.handleMessage方法【第二步】
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop(); // 啟動(dòng)Looper消息循環(huán)【第三步】
    }
}

handler會(huì)不會(huì)導(dǎo)致Activity的泄漏?

  • 問(wèn)題分析
    Handler泄露的關(guān)鍵點(diǎn)有兩個(gè):
    1. 內(nèi)部類
    2. 生命周期和Activity不一定一致

看看下面代碼

public class MainActivity extends QActivity {
//這里應(yīng)該使用static否則容易泄漏
         class MyHandler extends Handler {
                ... ...
        }
}

內(nèi)部類持有外部類Activity的引用雹洗,當(dāng)Handler對(duì)象有Message在排隊(duì)香罐,則無(wú)法釋放( 比如activity已經(jīng)destory了但是MessageQueen還有消息則,looper就會(huì)在輪詢,因此activity就無(wú)法被釋放,因內(nèi)部類有act引用),進(jìn)而導(dǎo)致Activity對(duì)象不能釋放。

  • 解決方式:
    如果Handler中有延遲的任務(wù)或者是等待執(zhí)行的任務(wù)隊(duì)列過(guò)長(zhǎng),都有可能因?yàn)镠andler繼續(xù)執(zhí)行而導(dǎo)致Activity發(fā)生泄漏踩窖。
    此時(shí)的引用關(guān)系鏈?zhǔn)?
    Looper -> MessageQueue -> Message -> Handler -> Activity

解決方案

  1. 可以在UI退出之前扣讼,執(zhí)行remove Handler消息隊(duì)列中的消息與runnable對(duì)象芹缔。
  2. 使用Static + WeakReference的方式來(lái)達(dá)到斷開(kāi)Handler與Activity之間存在引用關(guān)系的目的而咆。

主線程中的Looper.loop()一直無(wú)限循環(huán)為什么不會(huì)造成ANR吮炕?

  • Activity的生命周期都是運(yùn)行在 Looper.loop() 的控制之下,當(dāng)收到不同Message時(shí)則采用相應(yīng)措施,如果它停止了,應(yīng)用也就停止了.
  • Android的消息是一種事件機(jī)制,而looper.loop() 不斷地接收事件歧蒋、處理事件,只要每次處理的事件不被looper堵塞那么,就不會(huì)爆anr.
  • 也就說(shuō)我們的代碼其實(shí)就是在這個(gè)循環(huán)里面去執(zhí)行的,當(dāng)然不會(huì)阻塞了.

讓我們先看一遍造成ANR的原因,就明白了

造成ANR的原因一般有兩種:
1. 當(dāng)前的事件沒(méi)有機(jī)會(huì)得到處理(即主線程正在處理前一個(gè)事件宁炫,沒(méi)有及時(shí)的完成或者looper被某種原因阻塞住了)
2. 當(dāng)前的事件正在處理偿曙,但沒(méi)有及時(shí)完成

總結(jié):
真正會(huì)卡死主線程的操作是在回調(diào)方法onCreate/onStart/onResume等操作時(shí)間過(guò)長(zhǎng),會(huì)導(dǎo)致掉幀,甚至發(fā)生ANR,而looper.loop本身不會(huì)導(dǎo)致應(yīng)用卡死.

詳情見(jiàn)

總結(jié)

看了代碼之后,覺(jué)得它一點(diǎn)都不神秘羔巢,不就是實(shí)現(xiàn)了我們常用的“消息驅(qū)動(dòng)機(jī)制”嗎望忆?
消息驅(qū)動(dòng)機(jī)制的四要素:
1. 接收消息的“消息隊(duì)列”
2. 阻塞式地從消息隊(duì)列中接收消息并進(jìn)行處理的“線程”
3. 可發(fā)送的“消息的格式”
4. “消息發(fā)送函數(shù)”

以上四要素在Android中實(shí)現(xiàn)對(duì)應(yīng)的類如下:
1. 接收消息的“消息隊(duì)列” ——【MessageQueue】
2. 阻塞式地從消息隊(duì)列中接收消息并進(jìn)行處理的“線程” ——【Thread+Looper】
3. 可發(fā)送的“消息的格式” ——【Message<Runnable被封裝在Message中>】
4. “消息發(fā)送函數(shù)”——【Handler的post和sendMessage】
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市竿秆,隨后出現(xiàn)的幾起案子启摄,更是在濱河造成了極大的恐慌,老刑警劉巖袍辞,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鞋仍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡搅吁,警方通過(guò)查閱死者的電腦和手機(jī)威创,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)谎懦,“玉大人肚豺,你說(shuō)我怎么就攤上這事〗缋梗” “怎么了吸申?”我有些...
    開(kāi)封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)享甸。 經(jīng)常有香客問(wèn)我截碴,道長(zhǎng),這世上最難降的妖魔是什么蛉威? 我笑而不...
    開(kāi)封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任日丹,我火速辦了婚禮,結(jié)果婚禮上蚯嫌,老公的妹妹穿的比我還像新娘哲虾。我一直安慰自己,他們只是感情好择示,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布束凑。 她就那樣靜靜地躺著,像睡著了一般栅盲。 火紅的嫁衣襯著肌膚如雪汪诉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天谈秫,我揣著相機(jī)與錄音摩瞎,去河邊找鬼拴签。 笑死,一個(gè)胖子當(dāng)著我的面吹牛旗们,可吹牛的內(nèi)容都是我干的蚓哩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼上渴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼岸梨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起稠氮,我...
    開(kāi)封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤曹阔,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后隔披,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赃份,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年奢米,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抓韩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鬓长,死狀恐怖谒拴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涉波,我是刑警寧澤英上,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站啤覆,受9級(jí)特大地震影響苍日,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窗声,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一相恃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫌佑,春花似錦、人聲如沸侨歉。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)幽邓。三九已至炮温,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間牵舵,已是汗流浹背柒啤。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工倦挂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人担巩。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓方援,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涛癌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子犯戏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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