Handler消息機(jī)制之問題匯總一

一逼争、Handler

1茴厉、消息機(jī)制是什么姓言?Handler是什么邓萨?
1)在Android中,消息機(jī)制主要就是指Handler機(jī)制锨天。
2)Handler是Android中的消息機(jī)制毯盈。它可以發(fā)送和處理延時(shí)消息/任務(wù),并且可以把消息/任務(wù)發(fā)送到其他線程(線程切換)病袄。實(shí)際開發(fā)中搂赋,主要是用來解決在子線程中無(wú)法更新UI的問題。

2益缠、為什么不能在子線程中訪問UI脑奠?
ViewRootImpl會(huì)對(duì)UI操作進(jìn)行驗(yàn)證,禁止在子線程中訪問UI:

void checkThread(){
  if(mThread != Thread.currentThread()){
    throw new CalledFromWrongThreadException("Only th original thread that created a view hierarchy can touch its views");
  }
}

3幅慌、Android為什么要把UI更新限制在主線程宋欺,以及Handler的由來?
最根本的目的在于解決:多線程并發(fā)問題胰伍。
分析:如果在一個(gè)Activity中有多個(gè)線程齿诞,并且沒有加鎖,那么同時(shí)去操作UI時(shí)就出現(xiàn)界面錯(cuò)亂的問題骂租。但是如果對(duì)這些更新UI的操作都做加鎖處理祷杈,又會(huì)導(dǎo)致性能下降。出于對(duì)性能問題的考慮渗饮,Android提供了一套更新UI的機(jī)制——Handler但汞。我們只需要遵循這種機(jī)制,就可以方便的進(jìn)行UI操作互站,而不用再去關(guān)心多線程的問題私蕾,所有更新UI的操作,都是在主線程的消息隊(duì)列中去輪訓(xùn)的云茸。

4是目、在子線程中創(chuàng)建Handler報(bào)錯(cuò)是為什么?
子線程默認(rèn)是沒有Looper的谤饭,進(jìn)而也就沒有MessageQueue标捺。因此要在子線程中創(chuàng)建Handler,那么首先得在子線程中調(diào)用Looper.prepare()揉抵。

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

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

Looper.prepare()后亡容,當(dāng)前的線程就會(huì)和一個(gè)Looper關(guān)聯(lián),進(jìn)而和MessageQueue關(guān)聯(lián)冤今。然后又因?yàn)镠andler在創(chuàng)建時(shí)闺兢,會(huì)和Looper關(guān)聯(lián),所有到此Handler、Looper屋谭、MessageQueue脚囊、Thread之間一一對(duì)應(yīng)的關(guān)系也就建立了。

到這個(gè)階段桐磁,我們就可以使用Handler發(fā)送消息了悔耘。但是,現(xiàn)在只是可以把消息加入到MessageQueue中我擂,想要當(dāng)前的子線程會(huì)去取出消息衬以,還需要執(zhí)行Looper.loop()。

小結(jié):要在子線程中new Handler就分為三步:
1)Looper.prepare() --負(fù)責(zé)為Thread準(zhǔn)備MessageQueue校摩。
2)Looper.loop() ------負(fù)責(zé)從MessageQueue中取消息看峻。
3)new Handler()。 ---負(fù)責(zé)消息發(fā)送與處理衙吩。
其實(shí)在主線程也是這樣操作的互妓,只不過系統(tǒng)已經(jīng)幫我們做了前兩步了。

5坤塞、為什么通過Handler能實(shí)現(xiàn)線程的切換车猬?
Handler創(chuàng)建的時(shí)候會(huì)和Looper進(jìn)行關(guān)聯(lián),進(jìn)而關(guān)聯(lián)了MessageQueue和Thread尺锚,并且一個(gè)Handler對(duì)應(yīng)的Looper珠闰、MessageQueue和Thread是唯一的。因此瘫辩,不管Handler在什么地方使用(其他線程)伏嗜,它發(fā)送的消息都是加入到自己對(duì)應(yīng)的線程/MessageQueue中了,然后取消息也是在它對(duì)應(yīng)的線程伐厌,因此也就實(shí)現(xiàn)了跨線程承绸。

6、Handler.post的邏輯在哪個(gè)線程執(zhí)行的挣轨,是由Looper所在線程還是Handler所在線程決定的军熏?
這個(gè)問題,應(yīng)該可以分為兩步:
1)post方法本身是在調(diào)用post方法的線程中執(zhí)行的卷扮,一直執(zhí)行到把消息/Runnable加入到MessageQueue中
2)post出去的Runnable的run()方法荡澎,是在Looper所在的線程執(zhí)行的。在這個(gè)線程中Looper.loop()會(huì)一直取消息晤锹,取出后摩幔,調(diào)用當(dāng)前線程對(duì)應(yīng)的Handler.dispatchMessage執(zhí)行。

7鞭铆、Looper和Handler一定要處于一個(gè)線程嗎或衡?子線程中可以用MainLooper去創(chuàng)建Handler嗎?
Handler在任何線程創(chuàng)建都可以,它創(chuàng)建的時(shí)候默認(rèn)是和當(dāng)前線程的Looper關(guān)聯(lián)封断,但也可以指定Looper斯辰。
比如,在子線程中坡疼,可以創(chuàng)建一個(gè)Handler椒涯,然后指定Looper為MainLooper。那么此時(shí)這個(gè)Handler所發(fā)出的消息回梧,都是在主線程收到的废岂。

8、Handler的post/send()的原理
一系列post/send方法最終都是通過enqueueMessage()方法將msg加入到MessageQueue中狱意。

9湖苞、Handler的post方法發(fā)送的是同步消息嗎?可以發(fā)送異步消息嗎详囤?
1)用戶層面發(fā)送的都是同步消息财骨,不能發(fā)異步消息
2)異步消息只能系統(tǒng)發(fā)送。

疑問:\color{red}{什么是同步消息藏姐、異步消息隆箩?}

10、Handler的post()和postDelayed()方法的異同羔杨?
1)底層都是調(diào)用的sendMessageDelayed()
2)post()傳入的時(shí)間參數(shù)為0
3)postDelayed()傳入的時(shí)間參數(shù)是需要延遲的時(shí)間間隔捌臊。

11、Handler的postDelayed的底層機(jī)制
postDelayed --> sendMessageDelayed --> sendMessageAtTime --> enqueueMessage兜材。

12理澎、Handler的dispatchMessage()分發(fā)消息的處理流程?

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

13曙寡、Handler為什么要有Callback的構(gòu)造方法糠爬?
不需要派生Handler。

14举庶、Handler構(gòu)造方法中通過Looper.myLooper();是如何獲取到當(dāng)前線程的Looper的执隧?
myLooper()內(nèi)部使用ThreadLocal實(shí)現(xiàn),因此能夠獲取各個(gè)線程自己的Looper户侥。

15镀琉、主線程如何向子線程發(fā)送消息?
1)通過在主線程調(diào)用子線程中Handler的post方法添祸,完成消息的投遞滚粟。
2)通過HandlerThread實(shí)現(xiàn)該需求寻仗。
\color{red}{HandlerThread是什么刃泌?}

16、MessageQueue.next()會(huì)因?yàn)榘l(fā)現(xiàn)了延遲消息,而進(jìn)行阻塞耙替。那么為什么后面加入的非延遲消息沒有被阻塞呢亚侠?

二、MessageQueue

1俗扇、MessageQueue是什么硝烂?
1)消息隊(duì)列
2)內(nèi)部存儲(chǔ)結(jié)構(gòu)并不是真正的隊(duì)列,而是用單鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)消息列表
3)只能存儲(chǔ)消息铜幽,不能處理消息

2滞谢、MessageQueue的主要兩個(gè)操作是什么?有什么用?
1)enqueueMessage:往消息隊(duì)列中插入一條消息
2)next:取出一條消息除抛,并從消息隊(duì)列中移除
3)本質(zhì)采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護(hù)消息隊(duì)列狮杨,而不是采用隊(duì)列

3、MessageQueue的enqueueMessage()方法的原理到忽,如何進(jìn)行線程同步的橄教?

boolean enqueueMessage(Message msg, long when) {
    //1. 內(nèi)部是單鏈表的插入操作
    synchronized (this) {
        ......
    }
    return true;
}

1)單鏈表的插入操作
2)如果消息隊(duì)列被阻塞回調(diào)用nativeWake去喚醒。
3)用synchronized代碼塊去進(jìn)行同步喘漏。

4护蝶、MessageQueue的next()方法內(nèi)部的原理?

     /**
     * 功能:讀取并且刪除數(shù)據(jù)
     * 內(nèi)部無(wú)限循環(huán)翩迈,如果消息隊(duì)列中沒有消息就會(huì)一直阻塞持灰。
     * 一旦有新消息到來,next方法就會(huì)返回該消息并且將其從單鏈表中移除
    */
    Message next() {
        int nextPollTimeoutMillis = 0;
        for (;;) {
            /**======================================================================
             * 1负饲、精確阻塞指定時(shí)間搅方。第一次進(jìn)入時(shí)因?yàn)閚extPollTimeoutMillis=0,因此不會(huì)阻塞绽族。
             *   1-如果nextPollTimeoutMillis=-1姨涡,一直阻塞不會(huì)超時(shí)。
             *   2-如果nextPollTimeoutMillis=0吧慢,不會(huì)阻塞涛漂,立即返回。
             *   3-如果nextPollTimeoutMillis>0检诗,最長(zhǎng)阻塞nextPollTimeoutMillis毫秒(超時(shí))匈仗,如果期間有程序喚醒會(huì)立即返回。
             *====================================================================*/
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // 當(dāng)前時(shí)間
                final long now = SystemClock.uptimeMillis();
                Message msg = mMessages;
                /**=======================================================================
                 * 2逢慌、當(dāng)前Msg為消息屏障
                 *   1-說明有重要的異步消息需要優(yōu)先處理
                 *   2-遍歷查找到異步消息并且返回悠轩。
                 *   3-如果沒查詢到異步消息,會(huì)continue攻泼,且阻塞在nativePollOnce直到有新消息
                 *====================================================================*/
                if (msg != null && msg.target == null) {
                   // 遍歷尋找到異步消息火架,或者末尾都沒找到異步消息鉴象。
                    do {
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                /**================================================================
                 *  3、獲取到消息
                 *    1-消息時(shí)間已到何鸡,返回該消息纺弊。
                 *    2-消息時(shí)間沒到,表明有個(gè)延時(shí)消息骡男,會(huì)修正nextPollTimeoutMillis淆游。
                 *    3-后面continue,精確阻塞在nativePollOnce方法
                 *===================================================================*/
                if (msg != null) {
                    // 延遲消息的時(shí)間還沒到隔盛,因此重新計(jì)算nativePollOnce需要阻塞的時(shí)間
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 返回獲取到的消息(可以為一般消息犹菱、時(shí)間到的延遲消息、異步消息)
                        return msg;
                    }
                } else {
                    /**=============================
                     * 4吮炕、沒有找到消息或者異步消息
                     *==============================*/
                    nextPollTimeoutMillis = -1;
                }

                /**===========================================
                 * 5已亥、沒有獲取到消息,進(jìn)行下一次循環(huán)来屠。
                 *   (1)此時(shí)可能處于的情況:
                 *      1-沒有獲取到消息-nextPollTimeoutMillis = -1
                 *      2-沒有獲取到異步消息(接收到同步屏障卻沒找到異步消息)-nextPollTimeoutMillis = -1
                 *      3-延時(shí)消息的時(shí)間沒到-nextPollTimeoutMillis = msg.when-now
                 *   (2)根據(jù)nextPollTimeoutMillis的數(shù)值虑椎,最終都會(huì)阻塞在nativePollOnce(-1),
                 *      直到enqueueMessage將消息添加到隊(duì)列中俱笛。
                 *===========================================*/
                if (pendingIdleHandlerCount <= 0) {
                    // 用于enqueueMessage進(jìn)行精準(zhǔn)喚醒
                    mBlocked = true;
                    continue;
                }
            }
        }
    }

小結(jié)(原理:分為三種情況進(jìn)行處理):
1)如果是一般消息捆姜,會(huì)去獲取消息,沒有獲取到就會(huì)阻塞(native方法)迎膜,直到enqueueMessage插入新消息泥技。獲取到直接返回Msg。
2)如果是同步屏障磕仅,會(huì)去循環(huán)查找異步消息珊豹,沒有獲取到會(huì)進(jìn)行阻塞。獲取到直接返回Msg榕订。
3)如果是延時(shí)消息店茶,會(huì)計(jì)算時(shí)間間隔,并進(jìn)行精準(zhǔn)定時(shí)阻塞(native方法)劫恒。直到時(shí)間到達(dá)或者被enqueueMessage插入消息而喚醒贩幻。時(shí)間到后就返回Msg。

疑問:\color{red}{什么是同步屏障}

5两嘴、Looper.loop()是如何阻塞的丛楚?MessageQueue.next()是如何阻塞的?
通過native方法:nativePollOnce()進(jìn)行精準(zhǔn)時(shí)間的阻塞憔辫。

三趣些、Looper

1、Looper是什么贰您?
1坏平、輪詢器拢操。(消息循環(huán))
2、Looper以無(wú)限循環(huán)的形式去查找是否有新消息功茴,有就處理消息庐冯,沒有就一直等待著孽亲。

2坎穿、如何開啟消息循環(huán)?
Looper.loop()返劲。

3玲昧、Looper的構(gòu)造方法

private Looper(boolean quitAllowed) {
    //1. 會(huì)創(chuàng)建消息隊(duì)列: MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    //2. 當(dāng)前線程
    mThread = Thread.currentThread();
}

4、為線程創(chuàng)建Looper

//1. 在沒有Looper的線程創(chuàng)建Handler會(huì)直接異常
new Thread("Thread#2"){
    @Override
    public void run(){
        Handler handler = new Handler();
    }
}.start();

異常:
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()

//2. 用prepare為當(dāng)前線程創(chuàng)建一個(gè)Looper
new Thread("Thread#2"){
    @Override
    public void run(){
        Looper.prepare();
        Handler handler = new Handler();
        //3. 開啟消息循環(huán)
        Looper.loop();
    }
}.start();

5篮绿、主線程ActivityThread中的Looper的創(chuàng)建和獲取
1)主線程中使用prepareMainLooper()創(chuàng)建Looper
2)getMainLooper能夠在任何地方獲取到主線程的Looper

6孵延、Looper的兩個(gè)退出方法?有什么區(qū)別亲配?
1)Looper的退出有兩個(gè)方法:quit和quitSafely
2)quit會(huì)直接退出Looper
3)quitSafely只會(huì)設(shè)置退出標(biāo)記尘应,在已有消息全部處理完畢后才安全退出
4)Looper退出后,Handler的發(fā)送的消息會(huì)失敗吼虎,此時(shí)send返回false
5)子線程中如果手動(dòng)創(chuàng)建了Looper犬钢,應(yīng)該在所有事情完成后調(diào)用quit方法來終止消息循環(huán)

7、Looper.loop()的源碼流程?
1)獲取到Looper和消息隊(duì)列
2)for無(wú)限循環(huán)思灰,阻塞于消息隊(duì)列的next方法
3)取出消息后調(diào)用msg.target.dispatchMessage(msg)進(jìn)行消息分發(fā)

8玷犹、

//Looper.java
public static void loop() {
    //1. 獲取Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //2. 獲取消息隊(duì)列
    final MessageQueue queue = me.mQueue;
    ......
    for (; ; ) {
        //3. 獲取消息,如果沒有消息則會(huì)一直阻塞
        Message msg = queue.next();
        /**=================================
         * 4. 如果消息獲得為null洒疚,則退出循環(huán)
         * -Looper退出后歹颓,next就會(huì)返回null
         *=================================*/
        if (msg == null) {
            return;
        }
        ......
        /**==========================================================
         * 5. 處理消息
         *  -msg.target:是發(fā)送消息的Handler
         *  -最終在該Looper中執(zhí)行了Handler的dispatchMessage()
         *  -成功將代碼邏輯切換到指定的Looper(線程)中執(zhí)行
         *========================================================*/
        msg.target.dispatchMessage(msg);
        ......
    }
}

9、Looper.loop()在什么情況下會(huì)退出油湖?
1)next方法返回的msg == null
2)線程意外終止

10巍扛、Looper.quit/quitSafely的本質(zhì)是什么?
讓消息隊(duì)列的next()返回null乏德,依次來退出Looper.loop()

11电湘、Looper.loop()方法執(zhí)行時(shí),如果內(nèi)部的myLooper()獲取不到Looper會(huì)出現(xiàn)什么結(jié)果?

throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

12鹅经、Android如何保證一個(gè)線程最多只能有一個(gè)Looper寂呛?如何保證只有一個(gè)MessageQueue
1)Looper的構(gòu)造方法是private,不能直接構(gòu)造瘾晃。需要通過Looper.prepare()進(jìn)行創(chuàng)建:
2)在Looper.prepare()中會(huì)判斷sThreadLocal.get()是否為null贷痪,若不是,會(huì)拋出異常蹦误,從而保證了一個(gè)線程最多只能有一個(gè)Looper劫拢,也保證了只有一個(gè)MessageQueue肉津。

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

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

13、Handler消息機(jī)制中舱沧,一個(gè)looper是如何區(qū)分多個(gè)Handler的妹沙?
1)msg的target持有一個(gè)發(fā)送此消息的Handler引用。

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

2)Looper.loop()會(huì)阻塞于MessageQueue.next()
3)取出msg后熟吏,msg.target成員變量就是該msg對(duì)應(yīng)的Handler
4)調(diào)用msg.target的disptachMessage()進(jìn)行消息分發(fā)距糖。

14、主線程是如何準(zhǔn)備消息循環(huán)的牵寺?

//ActivityThread.java
public static void main(String[] args) {
    //1. 創(chuàng)建主線程的Looper和MessageQueue
    Looper.prepareMainLooper();
    ......
    //2. 開啟消息循環(huán)
    Looper.loop();
}
/**=============================================
 * ActivityThread中需要Handler與消息隊(duì)列進(jìn)行交互
 * -內(nèi)部定義一系列消息類型:主要有四大組件等
 * //ActivityThread.java
 *=============================================*/
private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    public static final int PAUSE_ACTIVITY          = 101;
    public static final int PAUSE_ACTIVITY_FINISHING= 102;
    ......
}

1悍引、ActivityThread通過ApplicationThread和AMS進(jìn)行IPC通信
2、AMS完成請(qǐng)求的工作后會(huì)回調(diào)ApplicationThread中的Binder方法
3帽氓、ApplicationThread會(huì)向Handler H發(fā)送消息
4趣斤、H接收到消息后會(huì)將ApplicationThread的邏輯切換到ActivityThread中去執(zhí)行

\color{red}{注:這個(gè)題還不懂。}

15黎休、主線程Looper一直循環(huán)查消息為何沒卡主線程浓领?
1)線程的阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán),暫時(shí)停止運(yùn)行势腮。直到線程進(jìn)入就緒狀態(tài)联贩,才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。
(2)主線程Looper從消息隊(duì)列讀取消息嫉鲸,當(dāng)讀完所有消息時(shí)撑蒜,主線程阻塞。子線程往消息隊(duì)列發(fā)送消息玄渗,并且往管道文件寫數(shù)據(jù)座菠,主線程即被喚醒,從管道文件讀取數(shù)據(jù)藤树,主線程被喚醒只是為了讀取消息浴滴,當(dāng)消息讀取完畢,再次睡眠岁钓。因此loop的循環(huán)并不會(huì)對(duì)CPU性能有過多的消耗升略。

四、ThreadLocal

1屡限、ThreadLocal是什么品嚣?有什么用?
ThreadLocal是線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類钧大,可以在指定線程中存儲(chǔ)數(shù)據(jù)翰撑,之后只有在指定線程中才可以讀取到存儲(chǔ)的數(shù)據(jù)

2、ThreadLocal的兩個(gè)應(yīng)用場(chǎng)景啊央?
1)某些數(shù)據(jù)是以線程為作用域眶诈,并且不同線程具有不同的數(shù)據(jù)副本的時(shí)候涨醋。ThreadLocal可以輕松實(shí)現(xiàn)Looper在線程中的存取。
2)在復(fù)雜邏輯下的對(duì)象傳遞逝撬,通過ThreadLocal可以讓對(duì)象成為線程內(nèi)的全局對(duì)象浴骂,線程內(nèi)部通過get就可以獲取。

3宪潮、ThreadLocal的使用

mBooleanThreadLocal.set(true);
Log.d("ThreadLocal", "[Thread#main]" + mBooleanThreadLocal.get());

new Thread("Thread#1"){
    @Override
    public void run(){
        mBooleanThreadLocal.set(false);
        Log.d("ThreadLocal", "[Thread#1]" + mBooleanThreadLocal.get());
    }
}.start();

new Thread("Thread#2"){
    @Override
    public void run(){
        Log.d("ThreadLocal", "[Thread#2]" + mBooleanThreadLocal.get());
    }
}.start();

1溯警、最終main中輸出true; Thread#1中輸出false; Thread#2中輸出null
2、ThreadLocal內(nèi)部會(huì)從各自線程中取出數(shù)組坎炼,再根據(jù)當(dāng)前ThreadLocal的索引去查找出對(duì)應(yīng)的value值愧膀。

4拦键、ThreadLocal的set()源碼分析

//ThreadLocal.java
public void set(T value) {
    //1. 獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    //2. 獲取當(dāng)前線程對(duì)應(yīng)的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //3. map存在就進(jìn)行存儲(chǔ)
        map.set(this, value);
    else
        //4. 不存在就創(chuàng)建map并且存儲(chǔ)
        createMap(t, value);
}
//ThreadLocal.java內(nèi)部類: ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) {
    //1. table為Entry數(shù)組
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    //2. 根據(jù)當(dāng)前ThreadLocal獲取到Hash key谣光,并以此從table中查詢出Entry
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //3. 如果Entry的ThreadLocal與當(dāng)前的ThreadLocal相同,則用新值覆蓋e的value
        if (k == key) {
            e.value = value;
            return;
        }
        //4. Entry沒有ThreadLocal則把當(dāng)前ThreadLocal置入芬为,并存儲(chǔ)value
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //5. 沒有查詢到Entry萄金,則新建Entry并且存儲(chǔ)value
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
//ThreadLocal內(nèi)部類ThreadLocalMap的靜態(tài)內(nèi)部類
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

5、ThreadLocal的get()源碼分析

public T get() {
    //1. 獲取當(dāng)前線程對(duì)應(yīng)的ThreadLocalMap
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //2. 取出map中的對(duì)應(yīng)該ThreadLocal的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            //3. 獲取到entry后返回其中的value
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //4. 沒有ThreadLocalMap或者沒有獲取到ThreadLocal對(duì)應(yīng)的Entry媚朦,返回規(guī)定數(shù)值
    return setInitialValue();
}
private T setInitialValue() {
    //1. value = null
    T value = initialValue();//返回null
    Thread t = Thread.currentThread();
    //2. 若不存在則新ThreadLocalMap, 在里面以threadlocal為key,value為值,存入entry
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

1氧敢、當(dāng)前線程對(duì)應(yīng)了一個(gè)ThreadLocalMap
2、當(dāng)前線程的ThreadLocal對(duì)應(yīng)一個(gè)Map中的Entry(存在table中)
3询张、Entry中key會(huì)獲取其對(duì)應(yīng)的ThreadLocal, value就是存儲(chǔ)的數(shù)值

6孙乖、ThreadLocal的原理

1、thread.threadLocals就是當(dāng)前線程thread中的ThreadLocalMap
2份氧、ThreadLocalMap中有一個(gè)table數(shù)組唯袄,元素是Entry。根據(jù)ThreadLocal(需要轉(zhuǎn)換獲取到Hash Key)能get到對(duì)應(yīng)的Enrty蜗帜。
3恋拷、Entry中key為ThreadLocal, value就是存儲(chǔ)的數(shù)值。

五厅缺、內(nèi)存泄漏

1蔬顾、Handler的內(nèi)存泄漏如何避免?

1湘捎、采用靜態(tài)內(nèi)部類:static handler = xxx
2诀豁、Activity結(jié)束時(shí),調(diào)用handler.removeCallback()窥妇、然后handler設(shè)置為null
3舷胜、如果使用到Context等引用,要使用弱引用

單獨(dú)寫一篇分析秩伞。

可以參考:
https://blog.csdn.net/qq_37321098/article/details/81535449?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param

https://blog.csdn.net/wsq_tomato/article/details/80301851?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.channel_param

參考:
1逞带、Handler消息機(jī)制(50題)
主要就是參考了他的博客欺矫,可以算是轉(zhuǎn)載了吧,他的源碼分析講的挺清楚的展氓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末穆趴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子遇汞,更是在濱河造成了極大的恐慌未妹,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件空入,死亡現(xiàn)場(chǎng)離奇詭異络它,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)歪赢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門化戳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人埋凯,你說我怎么就攤上這事点楼。” “怎么了白对?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵掠廓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我甩恼,道長(zhǎng)蟀瞧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任条摸,我火速辦了婚禮悦污,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘屈溉。我一直安慰自己塞关,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布子巾。 她就那樣靜靜地躺著帆赢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪线梗。 梳的紋絲不亂的頭發(fā)上椰于,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音仪搔,去河邊找鬼瘾婿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的偏陪。 我是一名探鬼主播抢呆,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼笛谦!你這毒婦竟也來了抱虐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤饥脑,失蹤者是張志新(化名)和其女友劉穎恳邀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灶轰,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谣沸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笋颤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乳附。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖椰弊,靈堂內(nèi)的尸體忽然破棺而出许溅,到底是詐尸還是另有隱情瓤鼻,我是刑警寧澤秉版,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站茬祷,受9級(jí)特大地震影響清焕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜祭犯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一秸妥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沃粗,春花似錦粥惧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至涡贱,卻和暖如春咏删,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背问词。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工督函, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓辰狡,卻偏偏與公主長(zhǎng)得像锋叨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宛篇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359