Handler機(jī)制(1)-Handler,MessageQueue,與Looper三者關(guān)系分析

主目錄見(jiàn):Android高級(jí)進(jìn)階知識(shí)(這是總目錄索引)
[written by Ticoo]

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閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件满败,死亡現(xiàn)場(chǎng)離奇詭異肤频,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)算墨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)宵荒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人净嘀,你說(shuō)我怎么就攤上這事报咳。” “怎么了挖藏?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵暑刃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我熬苍,道長(zhǎng)稍走,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任柴底,我火速辦了婚禮婿脸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柄驻。我一直安慰自己狐树,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布鸿脓。 她就那樣靜靜地躺著抑钟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪野哭。 梳的紋絲不亂的頭發(fā)上在塔,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音拨黔,去河邊找鬼蛔溃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛篱蝇,可吹牛的內(nèi)容都是我干的贺待。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼零截,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼麸塞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起涧衙,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤哪工,失蹤者是張志新(化名)和其女友劉穎奥此,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體正勒,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡得院,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了章贞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祥绞。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鸭限,靈堂內(nèi)的尸體忽然破棺而出蜕径,到底是詐尸還是另有隱情,我是刑警寧澤败京,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布兜喻,位于F島的核電站,受9級(jí)特大地震影響赡麦,放射性物質(zhì)發(fā)生泄漏朴皆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一泛粹、第九天 我趴在偏房一處隱蔽的房頂上張望遂铡。 院中可真熱鬧,春花似錦晶姊、人聲如沸扒接。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钾怔。三九已至,卻和暖如春蒙挑,著一層夾襖步出監(jiān)牢的瞬間宗侦,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工忆蚀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凝垛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓蜓谋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親炭分。 傳聞我的和親對(duì)象是個(gè)殘疾皇子桃焕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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