每日一題: Handler源碼

每日一題: Handler源碼

深入了解handler

面試率: ★★★☆☆

面試技巧與建議

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

面試建議

handler涉及很廣,我們可以選擇適合自己的深度來局部掌握該面試題,為什么局部掌握,因為全局去了解耗費精力太大,而且原理邏輯繁雜,不適合快速吸收.因此下面我給出了兩種方案:
深入者如Looper、Handler、Message三者關(guān)系
淺入者如handler性能優(yōu)化,常見方法使用與區(qū)別,handler與子線程等相關(guān)問題.

面試技巧

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

面試題

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

一個線程可以有幾個Lopper實例,為什么?

一個線程中只有一個Looper實例.

sThreadLocal是一個ThreadLocal對象,可以在一個線程中存儲變量??梢钥吹?,在第5行,將一個Looper的實例放入了ThreadLocal,并且2-4行判斷了sThreadLocal是否為null,否則拋出異常。這也就說明了Looper.prepare()方法不能被調(diào)用兩次,同時也保證了一個線程中只有一個Looper實例.

源碼

        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)建了一個MessageQueue(消息隊列)

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了消息隊列

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

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

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

Looper主要作用:

  1. 與當(dāng)前線程綁定,保證一個線程只會有一個Looper實例,同時一個Looper實例也只有一個MessageQueue。
  2. loop()方法,不斷從MessageQueue中去取消息,交給消息的target屬性的dispatchMessage去處理。

首先Looper.prepare()在本線程中保存一個Looper實例,然后該實例中保存一個MessageQueue對象;因為Looper.prepare()在一個線程中只能調(diào)用一次,所以MessageQueue在一個線程中只會存在一個。

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

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

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

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

一目了然,for (;;)指令少,不占用寄存器,而且沒有判斷跳轉(zhuǎn),比while (true)好。

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

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

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

public Handler(Callback callback, boolean async) {
  //部分源碼省略...
  
  //獲取當(dāng)前線程保存的Looper實例
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
//通過上面mLooper獲取了實例中保存的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中,我們并沒有顯示的調(diào)用Looper.prepare()和Looper.loop()方法,為啥Handler可以成功創(chuàng)建呢?

這是因為在Activity的啟動代碼中,已經(jīng)在當(dāng)前UI線程ActivityThread調(diào)用了Looper.prepare()和Looper.loop()方法。

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

可以的,可以在一個子線程中去創(chuàng)建一個Handler,然后使用這個handler實例在任何其他線程中發(fā)送消息,最終處理消息的代碼都會在你創(chuàng)建Handler實例的線程中運行。

詳情見下面實例:

class LooperThread extends Thread {
    public Handler mHandler;

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

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

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

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

看看下面代碼

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

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

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

解決方案

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

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

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

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

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

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

詳情見

總結(jié)

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

以上四要素在Android中實現(xiàn)對應(yīng)的類如下:
1. 接收消息的“消息隊列” ——【MessageQueue】
2. 阻塞式地從消息隊列中接收消息并進(jìn)行處理的“線程” ——【Thread+Looper】
3. 可發(fā)送的“消息的格式” ——【Message<Runnable被封裝在Message中>】
4. “消息發(fā)送函數(shù)”——【Handler的post和sendMessage】
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容