Handler虑瀑、Looper、MessageQueue詳解---配合面試題食用味道更佳

Handler

一滴须、成員與構(gòu)造函數(shù)

Handler有一個(gè)靜態(tài)成員值得注意:

private static Handler MAIN_THREAD_HANDLER = null舌狗;   

不出所料,有一個(gè)方法與其搭配:

public static Handler getMain() {
    if (MAIN_THREAD_HANDLER == null) {
        MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
    }
    return MAIN_THREAD_HANDLER;
}  

這里直接將其Looper設(shè)置為了MainLooper

構(gòu)造函數(shù)

Handler的構(gòu)造函數(shù)之間有回調(diào)關(guān)系扔水,最終都會(huì)分別調(diào)用兩種構(gòu)造函數(shù):

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

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}  

所以如果Looper對(duì)象不傳遞的話痛侍,Handler會(huì)自己調(diào)用Looper.muLooper()獲取Handler所在線程綁定的Looper對(duì)象

二魔市、Message的分發(fā)

Handler通過dispatchMessage分發(fā)Message:

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

private static void handleCallback(Message message) {
    message.callback.run();
}

可見主届,處理Message的優(yōu)先級(jí)為:

  • Handler的handleCallback優(yōu)先級(jí)最高
  • Handler的Callback回調(diào)第二優(yōu)先級(jí)
  • Handler的handleMessage方法優(yōu)先級(jí)最低

三、Message的產(chǎn)生

Handler本身有很多生成Message的方法:

public final Message obtainMessage()
{
    return Message.obtain(this);
}  

public final Message obtainMessage(int what)
{
    return Message.obtain(this, what);
}  

public final Message obtainMessage(int what, Object obj)
{
    return Message.obtain(this, what, obj);
}  

public final Message obtainMessage(int what, int arg1, int arg2)
{
    return Message.obtain(this, what, arg1, arg2);
}  

public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
{
    return Message.obtain(this, what, arg1, arg2, obj);
}  

平時(shí)都說待德,推薦使用Message.obtain()和Handler對(duì)象的obtainMessage方法來創(chuàng)建Message對(duì)象岂膳,且二者等價(jià),原因就在于handler對(duì)象的obtainMessage方法都是調(diào)用的Message.obtain()系列

注意:在Message.obtain方法中會(huì)將其target屬性初始化為Handler本身

四磅网、Message消息的發(fā)送

Handler中有很多方法可以發(fā)送消息,主要分為兩種:post和send

1. Post系列方法

這種方法都是直接post一個(gè)Runnable對(duì)象筷屡,所以首先來看看如何將一個(gè)Runnable對(duì)象封裝成一個(gè)Message:

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

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

很簡(jiǎn)單涧偷,就直接創(chuàng)建一個(gè)Message對(duì)象簸喂,然后將Runnable設(shè)置為Message的callback屬性

Q:post生成的Message沒有將handler對(duì)象綁定到其target屬性中?
對(duì)于該情況下的target賦值發(fā)生在handler的enqueueMessage方法中

現(xiàn)在我們來看post系列的方法:

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean postAtTime(Runnable r, long uptimeMillis)
{
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
{
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}    

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}    

public final boolean postDelayed(Runnable r, Object token, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r, token), delayMillis);
}  

public final boolean postAtFrontOfQueue(Runnable r)
{
    return sendMessageAtFrontOfQueue(getPostMessage(r));
}  

可以看到燎潮,Post方法都是回調(diào)的sendMessage系列方法

2. send系列方法

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}  

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

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

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
}  

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

可見喻鳄,大部分send方法都是調(diào)用的sendMessageDelayed方法,而sendMessageDelayed方法又是調(diào)用的sendMessageAtTime方法實(shí)現(xiàn)的确封,所以我們可以說除呵,Handler中所有發(fā)送Message的方法的實(shí)現(xiàn)都是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);
}    

而sendMessageAtTime的實(shí)現(xiàn)實(shí)際上就是回調(diào)了enqueueMessage

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

最終我們搞清楚了,原來Handler中所有的回調(diào)爪喘,都最終歸于queue.enqueueMessage方法颜曾。

而Handler中還有一個(gè)比較奇葩的方法:

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    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, 0);
}    

這個(gè)看起來和sendMessageXX沒啥不同啊,仔細(xì)發(fā)現(xiàn)秉剑,sendMessageDelayed在回調(diào)sendMessageAtTime的時(shí)候第二個(gè)參數(shù)做了手腳:

return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)  

其在delayMillis基礎(chǔ)上加了SystemClock.uptimeMillis(這個(gè)意義是手機(jī)從開機(jī)到當(dāng)下的時(shí)間間隔)泛豪,所以其永遠(yuǎn)都是一個(gè)大于0的數(shù);而sendMessageAtFrontOfQueue則把第二個(gè)參數(shù)直接置為0侦鹏,實(shí)際上其效果等同于:sendMessageAtTime(msg, 0)

注意:1. MessageQueue在執(zhí)行enqueue的時(shí)候會(huì)根據(jù)這個(gè)msg.when進(jìn)行插入诡曙,所以該值越小越先被MessageQueue.next方法返回,從而越先被Handler處理略水。 2. 為什么選擇使用SystemClock.uptimeMillis而不是System.currentMillis是因?yàn)楫?dāng)我們手動(dòng)更改系統(tǒng)時(shí)間的時(shí)候會(huì)影響后者的值价卤,導(dǎo)致MessageQueue在入隊(duì)插入的時(shí)候邏輯混亂

3. 小結(jié)

粗略下來,感覺post和send實(shí)現(xiàn)都一樣渊涝,為啥要有兩種api發(fā)送Message慎璧,雖然實(shí)現(xiàn)機(jī)制都一樣,但是在處理的時(shí)候就不一樣了驶赏。還記得handler的dispatchMessage中處理消息的優(yōu)先級(jí)嗎炸卑?沒錯(cuò):post系列方法的getPostMessage中將post的參數(shù)Runnable賦值給了Message的callback,所以導(dǎo)致在處理Post的消息的時(shí)候是通過最高優(yōu)先級(jí)的handleCallback方法處理的煤傍;而send系列方法盖文,在通常情況下是通過handler的callback屬性或者h(yuǎn)andleMessage方法處理消息。

五蚯姆、經(jīng)典面試題

Q1:子線程中創(chuàng)建Handler中報(bào)錯(cuò)是為什么五续?

在Handler的構(gòu)造器中有這么一段:

mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }  

因?yàn)闆]有執(zhí)行Looper.prepare()導(dǎo)致Looper.mylooper返回為null

Q2:如何在子線程創(chuàng)建Looper?

Looper.prepare()

Q3:為什么能通過Handler實(shí)現(xiàn)線程切換龄恋?

純屬個(gè)人理解:Handler的MessageQueue來自于Looper中的MessageQueue疙驾,而在Message.obtain中,Message的target被賦值為了Handler對(duì)象郭毕;Looper在從MessageQueue中取出Message后通過message.target來處理消息它碎,所以消息的處理是在Looper所綁定的線程,而Handler可以在任意線程往MessageQueue中發(fā)送消息,所以實(shí)現(xiàn)了線程的切換

Q4:Handler處理消息發(fā)送在那個(gè)線程扳肛,是Handler所在的線程還是Looper傻挂?

Looper所在的線程

Q5:Looper和Handler一定要處于一個(gè)線程嗎?子線程中可以用MainLooper去創(chuàng)建Handler嗎挖息?

不一定位于一個(gè)線程金拒;子線程可以通過MainLooper創(chuàng)建Handler:實(shí)現(xiàn)有兩種:

Handler h = new MyHandler(Looper.getMainLooper());  
Handler h = Handler.getMain();  // Handler的api  

Q6:Handler的post/send()的原理

最終都是調(diào)用的sendMessageAtTime -> enqueueMessage -> mQueue.enqueue

Q7:Handler的dispatchMessage()分發(fā)消息的處理流程?

優(yōu)先級(jí)為:

  • 如果msg.callback不為空套腹,調(diào)用handleCallback
  • 如果mCallback屬性不為空绪抛,則調(diào)用mCallback.handleMessage
  • 最后調(diào)用handleMessage方法

Q8:Handler為什么要提供Callback構(gòu)造方法?

正常使用Handler要使用一個(gè)子類繼承Handler电禀,然后再子類中實(shí)現(xiàn)handleMessage方法幢码,而使用Callback構(gòu)造函數(shù)則可以簡(jiǎn)化這一流程,一步到位鞭呕。

Looper

一蛤育、成員和構(gòu)造器

Looper的成員有三個(gè)需要關(guān)注:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class    
final Thread mThread;

其中的sThreadLocal就是實(shí)現(xiàn)Looper和線程綁定的原因

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

構(gòu)造器中直接初始化了mThread和mQueue

二葫松、prepare()瓦糕、myLooper()方法

該方法大家都比較熟悉:

public static void prepare() {
    prepare(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));
}  

就是在當(dāng)前線程中初始化了Looper對(duì)象,并且把它保存到了ThreadLocal中

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

myLooper()方法就是將Looper對(duì)象從ThreadLocal中取出來

三腋么、quit和quitSafely

二者的區(qū)別就在于:mQueue.quit參數(shù)是否為true

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

public void quitSafely() {
    mQueue.quit(true);
}  

通過注釋我們知道第一個(gè)方法可能導(dǎo)致還有沒有接受到的Message咕娄,可能會(huì)產(chǎn)生意外情況,所以推薦我們使用第二個(gè)方法

四珊擂、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;
    ....

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

        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...

        msg.recycleUnchecked();
    }
}  

這里只保留了關(guān)鍵的代碼:

  • 要想loop結(jié)束只有msg為null圣勒,而在MessageQueue中只有調(diào)用了quit方法才會(huì)導(dǎo)致其next方法返回null;而Looper的quit和quirSafely方法內(nèi)部就是回調(diào)了mQueue的quit方法摧扇,所以只有調(diào)用Looper的quit或者quitSafely方法才會(huì)終止loop()方法
  • msg.target.dispatchMessage(msg)中圣贸,msg的target即為handler對(duì)象,所以這里就實(shí)現(xiàn)了跨線程

五扛稽、經(jīng)典面試題

Q1:如何開啟消息循環(huán)吁峻?

Looper.loop()

Q2:Looper兩個(gè)退出方法有何區(qū)別?

quit會(huì)導(dǎo)致還未接收到的Message無法處理
quitSafely不會(huì)

Q3:如何終止消息循環(huán)在张?

quitSafely

Q4:Looper的loop流程

  • 獲取Looper和MessageQueue
  • 無限for循環(huán)取出msg
  • 執(zhí)行msg.target.dispatchMessage

Q5:MessageQueue的next什么情況下返回null

執(zhí)行了quit或者quitSafely

Q6:Android如何保證一個(gè)線程最多只能有一個(gè)Looper用含?

通過ThreadLocal保證只有一個(gè)Looper

MessageQueue

這里需要提一下其Message mMessages代表鏈表的表頭

我們這里只討論其enqueueMessage方法和next方法。

1. enqueueMessage方法

boolean enqueueMessage(Message msg, long when) {
    帮匾。啄骇。。
    synchronized (this) {
        
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

       is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}  

可以發(fā)現(xiàn)瘟斜,這里就是按照when的大小缸夹,將Message插入到鏈表中

2. next方法

Message next() {
   ...
    int nextPollTimeoutMillis = 0;
    for (;;) {
        ...
        //根據(jù)我們上面的分析痪寻,nextPollTimeMillis為0則不阻塞,也就是第一次循環(huán)不阻塞
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //這里判斷msg不為空但是target為空明未,而我們enqueueMessage的時(shí)候特意設(shè)置了target的
            //所以這里的msg不是我們?cè)O(shè)置而是系統(tǒng)在初始化的時(shí)候設(shè)置的屏障槽华,這里不再詳解
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            //這里是正常情況下的msg
            if (msg != null) {
                //未達(dá)到處理時(shí)間的,將會(huì)計(jì)算需要等待的時(shí)間趟妥,不超過整形的最大值
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                 //可以處理的則直接取出后返回
                 } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
             //這里是隊(duì)列中沒有消息
            } else {
                nextPollTimeoutMillis = -1;
            }
            //上面分別對(duì)消息隊(duì)列進(jìn)行判斷然后修改nextPollTimeoutMillis,而之前的分析可以看出這個(gè)值就是線程
            //需要阻塞的時(shí)長(zhǎng)佣蓉,有未達(dá)到處理時(shí)間的消息則阻塞對(duì)應(yīng)時(shí)間披摄,沒有消息則一直阻塞直到被喚醒
        ...
        }
        ...
    }       
}  

next方法詳細(xì)分析:MessageQueue的next方法詳解

最后編輯于
?著作權(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)離奇詭異,居然都是意外死亡璧函,警方通過查閱死者的電腦和手機(jī)傀蚌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蘸吓,“玉大人善炫,你說我怎么就攤上這事】饧蹋” “怎么了箩艺?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)宪萄。 經(jīng)常有香客問我艺谆,道長(zhǎng),這世上最難降的妖魔是什么拜英? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任静汤,我火速辦了婚禮,結(jié)果婚禮上聊记,老公的妹妹穿的比我還像新娘撒妈。我一直安慰自己,他們只是感情好排监,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布狰右。 她就那樣靜靜地躺著,像睡著了一般舆床。 火紅的嫁衣襯著肌膚如雪棋蚌。 梳的紋絲不亂的頭發(fā)上嫁佳,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音谷暮,去河邊找鬼蒿往。 笑死,一個(gè)胖子當(dāng)著我的面吹牛湿弦,可吹牛的內(nèi)容都是我干的瓤漏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颊埃,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蔬充!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起班利,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤饥漫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(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
  • 文/蒙蒙 一井濒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧列林,春花似錦瑞你、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至砌创,卻和暖如春虏缸,著一層夾襖步出監(jiān)牢的瞬間鲫懒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工刽辙, 沒想到剛下飛機(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)容