Handler,Looper,MessageQueue三者的關(guān)系

Android中,Handler雖然不是四大組件,但用的次數(shù)也不比Activity个唧,Service等四大組件少渠退。雖然大家都知道怎么使用Handler忙迁,但是我們不能僅僅停留在使用的層面脐彩,對(duì)其機(jī)制的分析會(huì)加深我們對(duì)Android的理解。當(dāng)然了姊扔,最終的目的就是在面試的時(shí)候碾壓面試官惠奸,本系列將會(huì)對(duì)Handler機(jī)制做詳細(xì)的分析。

由于篇幅可能比較長(zhǎng)恰梢,該系列將分文下面幾篇文章:

  1. Handler,MessageQueue,與Looper三者關(guān)系分析
  2. HandlerThread源碼分析
  3. IntentService源碼分析
  4. Handler常見(jiàn)應(yīng)用場(chǎng)景和常見(jiàn)問(wèn)題分析

Handler,MessageQueue,Looper的關(guān)系分析

要理清這三者的曖昧關(guān)系佛南,我們先分別簡(jiǎn)單介紹一下它們

Handler

A Handler allows you to send and process Message and Runnable
objects associated with a thread's MessageQueue.

如官網(wǎng)描述的,我們可以用Handler發(fā)送并處理Message對(duì)象或者一個(gè)線程對(duì)象删豺,并關(guān)聯(lián)到一個(gè)線程的消息隊(duì)列里共虑,即MessageQueue,而關(guān)聯(lián)的線程就是創(chuàng)建Handler的線程呀页。

Handler使用場(chǎng)景

  1. 在未來(lái)某個(gè)時(shí)間點(diǎn)上處理Message或者執(zhí)行Runnable
  2. 將需要執(zhí)行的操作通過(guò)Handler發(fā)送消息在其他線程里執(zhí)行

Lopper

通常線程默認(rèn)是不具備循環(huán)處理消息的能力的妈拌,而Looper就是用來(lái)給線程提供循環(huán)處理消息能力的類(lèi)。
當(dāng)我們需要循環(huán)處理消息時(shí)蓬蝶,我們?cè)趧?chuàng)建Handler的線程里調(diào)用Looper.prepare()準(zhǔn)備好Looper尘分,之后再調(diào)用Looper.loop()開(kāi)始循環(huán)處理消息。
如果我們是在主線程創(chuàng)建的Handler丸氛,就不需要做上面的兩個(gè)操作了培愁,因?yàn)橹骶€程已經(jīng)幫我們處理好了

一個(gè)經(jīng)典的用法如下

class LooperThread extends Thread {
        public Handler mHandler;
  
        public void run() {
            Looper.prepare();
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
  
            Looper.loop();
        }
    }

MessageQueue

顧名思義,消息隊(duì)列缓窜。持有Handler發(fā)送的消息列表定续,我們可以使用
Looper.myQueue()來(lái)拿到這個(gè)對(duì)象。

除了上面的三個(gè)大頭外禾锤,還有一個(gè)雖然也很重要私股,但我們不用對(duì)他做太多文章,因?yàn)樗黄鸬匠休d信息的作用恩掷,也就是我們的Message對(duì)象倡鲸。Handler發(fā)送和處理的對(duì)象都是它,可能你會(huì)說(shuō)Handler的方法里不一定要使用Message來(lái)發(fā)送呀黄娘,不著急峭状,等等分析源碼的時(shí)候我們就為什么這么說(shuō)了

工作原理

當(dāng)我們創(chuàng)建Handler時(shí),每個(gè)Handler實(shí)例都會(huì)關(guān)聯(lián)一個(gè)線程以及這個(gè)線程的消息隊(duì)列逼争,這個(gè)線程指的就是創(chuàng)建Handler的線程优床。

怎么理解呢? 其實(shí)很簡(jiǎn)單誓焦,Android里線程無(wú)非就是主線程(或UI線程)和子線程羔巢,從這點(diǎn)入手,我們就可以理解,如果在主線程創(chuàng)建Handler竿秆,會(huì)關(guān)聯(lián)到主線程以及主線程里的消息隊(duì)列, 子線程創(chuàng)建的Handler會(huì)關(guān)聯(lián)到子線程以及子線程的消息隊(duì)列

具體是怎么實(shí)現(xiàn)的呢启摄?我們從源碼入手看看端倪,

初始化

當(dāng)我們new Handler()時(shí)幽钢,會(huì)調(diào)用到下面這個(gè)構(gòu)造方法歉备,其中 Callback是null,async是false

 public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

溢出堅(jiān)持標(biāo)識(shí)FIND_POTENTIAL_LEAKS默認(rèn)是flase, 故會(huì)先執(zhí)行 Looper.myLooper, 發(fā)現(xiàn)就只調(diào)用了ThreadLocal的get方法返回一個(gè)Looper

    public static Looper myLooper() {
        return sThreadLocal.get();
    }

ThreadLocal里的get實(shí)現(xiàn)

/**
     * Returns the value of this variable for the current thread. If an entry
     * doesn't yet exist for this variable on this thread, this method will
     * create an entry, populating the value with the result of
     * {@link #initialValue()}.
     *
     * @return the current value of the variable for the calling thread.
     */
    @SuppressWarnings("unchecked")
    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);
    }
    
    /**
     * Gets Values instance for this thread and variable type.
     */
    Values values(Thread current) {
        return current.localValues;
    }

我們看到匪燕,獲取到當(dāng)前的線程并取得其ThreadLocal.Values成員變量, 而這個(gè)成員變量的初始化的地方也就是在這個(gè)get方法里調(diào)用initializeValues(currentThread)

    /**
     * Creates Values instance for this thread and variable type.
     */
    Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }

Value的構(gòu)造


    /**
     * Constructs a new, empty instance.
     */
    Values() {
        initializeTable(INITIAL_SIZE);
        this.size = 0;
        this.tombstones = 0;
    }
    
    private void initializeTable(int capacity) {
        this.table = new Object[capacity * 2];
        this.mask = table.length - 1;
        this.clean = 0;
        this.maximumLoad = capacity * 2 / 3; // 2/3
    }
    

ThreadLocal和Value功能就是提供一個(gè)Map功能的Object數(shù)組來(lái)存儲(chǔ)數(shù)據(jù)蕾羊。

初始化完線程的ThreadLocal.Value后,Looper.myLooper() 最后會(huì)調(diào)用 ThreadLocal get方法里的 values.getAfterMiss(this)

 /**
         * Gets value for given ThreadLocal after not finding it in the first
         * slot.
         */
        Object getAfterMiss(ThreadLocal<?> key) {
            Object[] table = this.table;
            int index = key.hash & mask;

            // If the first slot is empty, the search is over.
            if (table[index] == null) {
                Object value = key.initialValue();

                // If the table is still the same and the slot is still empty...
                if (this.table == table && table[index] == null) {
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;

                    cleanUp();
                    return value;
                }

                // The table changed during initialValue().
                put(key, value);
                return value;
            }
            ...
        }

因?yàn)槭堑谝淮纬跏蓟毖保瑵M足條件 table[index] == null龟再,會(huì)return key.initialValue(),而

protected T initialValue() {
    return null;
}

...

什么鬼尼变,看了這么久return一個(gè)null...好吧利凑,那就null吧。
我們回過(guò)頭來(lái)看Handler的初始化嫌术,會(huì)發(fā)現(xiàn)由于mLooper為null會(huì)拋出一個(gè)RuntimeException

Can't create handler inside thread that has not called Looper.prepare()

所以哀澈,我們不得不先調(diào)用 Looper.prepare()

/**
 * quitAllowed will be true
 **/
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));
}

可以看到,在這里給Looper里的ThreadLocal初始化了一個(gè)Looper度气,這樣當(dāng)我們調(diào)用 Looper.myLooper()的時(shí)候割按,其實(shí)就是從ThreadLocal里取出在這里初始化的Looper。
好磷籍,接著我們看Looper的初始化

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

Looper里創(chuàng)建了一個(gè)MessageQueue,并保存當(dāng)前的線程适荣,也就是我們之前一直提到的 Handler會(huì)關(guān)聯(lián)到一個(gè)線程。而Handler里的成員變量mQueue也就是Lopper里的mQueue院领。 到這里初始化就告一段落了

發(fā)送消息

初始化完成后束凑,我們就可以通過(guò)Handler的postxxx, sendxxx方法來(lái)發(fā)送消息
我們看sendEmptyMessage方法就可以發(fā)現(xiàn),層層嵌套最終調(diào)用的是 sendMessageAtTime 方法

sendEmptyMessage

public final boolean sendEmptyMessage(int what) {
    return sendEmptyMessageDelayed(what, 0);
}

sendMessageDelayed

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

如果是post一個(gè)Runnable栅盲,它還會(huì)調(diào)用下面這個(gè)方法,將Runnable復(fù)制給Message里的callback成員變量废恋,所以說(shuō)Handler發(fā)送和處理的都是Message對(duì)象

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

sendMessageDelayed

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

sendMessageAtTime

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

最終呢谈秫,就會(huì)調(diào)用enqueueMessage方法,將Message入棧到Looper里的MessageQueue

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

MessageQueue 隊(duì)列的實(shí)現(xiàn)鱼鼓,原理是使用Message鏈表拟烫,有興趣的同學(xué)可以自己去研究一下,我們就不做過(guò)多的闡述迄本。
這樣發(fā)送消息的過(guò)程到這里也基本完結(jié)了

處理消息

你還記得嗎硕淑?當(dāng)我們?cè)谧泳€程里創(chuàng)建Handler的時(shí)候,都會(huì)在run方法里調(diào)用Looper.loop()方法,異步處理Message對(duì)象置媳。如果是在主線程創(chuàng)建的Handler就不是異步了于樟,因?yàn)樗P(guān)聯(lián)的線程是主線程,Handler的handlerMessage回調(diào)是在主線程里執(zhí)行的拇囊。

最后迂曲,我們來(lái)看看Handler最核心的實(shí)現(xiàn),消息的處理 Looper.loop(),
忽略非消息處理的代碼后如下

 public static void loop() {
        ...
        // 獲取消息隊(duì)列
        final MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            
            msg.target.dispatchMessage(msg);

            ...

            msg.recycleUnchecked();
        }
    }

看著是不是挺簡(jiǎn)單的呢寥袭? 一個(gè)for死循環(huán)路捧,通過(guò)MessageQueue的阻塞方法next 讀取下一個(gè)Message。
取出來(lái)的Message會(huì)拿到target成員變量传黄,即Handler,調(diào)用dispatchMessage杰扫,分發(fā)消息。Handler里的分發(fā)呢膘掰?

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

分發(fā)給callback回調(diào)章姓,或者M(jìn)essage對(duì)象的callback,又或者Handler子類(lèi)里的handlerMessage炭序。
我們可以調(diào)用Looper的quit()方法啤覆,讓隊(duì)列安全的關(guān)閉,避免不必要的消耗惭聂。
或者等待Handler被銷(xiāo)毀窗声,讓系統(tǒng)自動(dòng)回收

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

這樣Handler的整體的工作原理就是這些了,再見(jiàn)Handler辜纲。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末笨觅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子耕腾,更是在濱河造成了極大的恐慌见剩,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扫俺,死亡現(xiàn)場(chǎng)離奇詭異苍苞,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)狼纬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)羹呵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人疗琉,你說(shuō)我怎么就攤上這事冈欢。” “怎么了盈简?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵凑耻,是天一觀的道長(zhǎng)太示。 經(jīng)常有香客問(wèn)我,道長(zhǎng)香浩,這世上最難降的妖魔是什么类缤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮弃衍,結(jié)果婚禮上呀非,老公的妹妹穿的比我還像新娘镜盯。我一直安慰自己岸裙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布速缆。 她就那樣靜靜地躺著降允,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艺糜。 梳的紋絲不亂的頭發(fā)上剧董,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音破停,去河邊找鬼翅楼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛真慢,可吹牛的內(nèi)容都是我干的毅臊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼黑界,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼管嬉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起朗鸠,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蚯撩,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后烛占,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體胎挎,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年忆家,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了犹菇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弦赖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浦辨,到底是詐尸還是另有隱情蹬竖,我是刑警寧澤沼沈,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站币厕,受9級(jí)特大地震影響列另,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜旦装,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一页衙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧阴绢,春花似錦店乐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至左电,卻和暖如春廉侧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背篓足。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工段誊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人栈拖。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓连舍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親辱魁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子烟瞧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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