Handler 使用以及源碼全面解析(二)

前言

上一篇Handler 使用以及源碼全面解析(一)
簡(jiǎn)述了Handler的使用之后,這篇從源碼上來分析Handler的原理。

分析源碼時(shí)我們可以發(fā)現(xiàn)很多我們未曾注意到的小細(xì)節(jié)。

比如我們都知道,每個(gè)使用Handler的線程都要有自己Looper, 而使用Looper.myLooper()就可以得到當(dāng)前線程的Looper. 怎么這么神奇呢? 那么每個(gè)線程各自的Looper是如何管理的呢?

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

從源碼可以看到,用的是ThreadLocal來保存的,通過ThreadLocal.get()就獲得當(dāng)前線程Looper對(duì)象。所以進(jìn)入Handler的正題之前,不妨先來了解下What is ThreadLoacl。

1、ThreadLocal

每個(gè)線程都可能需要有自己的數(shù)據(jù)副本,比如有的線程需要Looper,有的不需要,用腳指頭想一下,Looper是不可能作為成員變量定義在Thread中。而這種一一對(duì)應(yīng)的映射關(guān)系,很自然而然我(們)想到了map的形式----Map<Thread,Looper>,通用點(diǎn)就用上泛型,Map<Thread, T>

是的,ThreadLocal內(nèi)部大致是Map這種形式,但并不是Map<Thread, T>。我的想法并沒有被谷歌采用,好羞愧。(google:?_?該吃藥了),因?yàn)轱@然如果以Thread為key, 存于一個(gè)靜態(tài)通用的Map中,當(dāng)線程掛了之后,管理不當(dāng)很容易出現(xiàn)內(nèi)存泄漏。

Thread雖然沒有Looper變量,但是它持有ThreadLocalMap變量??!Map中的每個(gè)映射關(guān)系是這樣的---Entry<ThredLocal, T>,即Thread擁有y由很多個(gè)Entry<ThredLocal, T>組成的map,也就是有很多個(gè)T的資源(這表述怎么怪怪的)

而通過ThreadLocal所謂能存取不同線程的副本的原因,正是每次進(jìn)行g(shù)et和set方法時(shí),ThreadLocal都會(huì)用Thread.currentThread()來獲取當(dāng)前線程ThreadLocalMap,把該ThreadLocal作為key,從ThreadLocalMap進(jìn)行相應(yīng)的存取Value操作

ThreadLocal.java 

    public T get() {
        Thread t = Thread.currentThread();
        //Thread中的threadLocals變量
        ThreadLocalMap map = getMap(t);  //return t.threadLocals;
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue(); // 可以實(shí)現(xiàn)initialValue()方法去設(shè)置初始值
    }
    
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

這個(gè)ThreadLocalMap還有一點(diǎn)不同的是,并不是我們常見的HashMap。HashMap的setValue原理,我們都知道,Key的hash值對(duì)數(shù)組長(zhǎng)度取余就得到Key在數(shù)組的位置,若該位置已經(jīng)有其它Key存在,那就會(huì)生成一個(gè)指針,與相同位置的形成一條單向直鏈,鏈長(zhǎng)度過長(zhǎng)時(shí)還會(huì)轉(zhuǎn)化為紅黑樹的結(jié)構(gòu)(java 1.8)。

而ThreadLocalMap并不不存在指針,也就沒有鏈。在取余得到的位置被占有后,則會(huì)從該位置起對(duì)數(shù)組進(jìn)行遍歷,知道找到空位為止。

當(dāng)然我們一般情況下我們也不會(huì)去使用ThreadLocal,當(dāng)某些數(shù)據(jù)在以線程為作用域且不同線程具有不同的數(shù)據(jù)副本的時(shí)候,就可以考慮采用ThreadLocal??赡茏x者至今都沒有用過(哈哈,我在業(yè)務(wù)需求上也沒有用過~)

再舉一個(gè)例子,AMS中的使用:

//記錄不同線程的pid和uid
private class Identity {
    public final IBinder token;
    public final int pid;
    public final int uid;

    Identity(IBinder _token, int _pid, int _uid) {
        token = _token;
        pid = _pid;
        uid = _uid;
    }
}

private static final ThreadLocal<Identity> sCallerIdentity = new ThreadLocal<Identity>();

再多說一句,Android 5.0到7.0之間,Android SDK 中ThreadLocal的代碼跟JDK是不一致的,是所謂優(yōu)化過的,同樣用數(shù)組,偶數(shù)下標(biāo)存Key,奇數(shù)下標(biāo)存Value

    values[index] = key //(ThreadLocal<?>.refrence,弱引用)
    values[index+1] = value//()

7.0及之后的就保持一致了,真香。

接下里進(jìn)入正題了,那接下來我可要放大招了 要貼源碼了。

Looper是某個(gè)餐廳(Thread)的專職外賣小哥,Handler是某個(gè)點(diǎn)外賣的宅男。
MessageQueue相當(dāng)于一個(gè)線上下單的餐廳,會(huì)根據(jù)顧客需求把所有訂單按出餐的時(shí)間排序(為什么不是下單時(shí)間,是因?yàn)橛行╊櫩蜁?huì)讓餐廳定時(shí)延后送過來)。

開始賞析外賣小哥與宅男之間的大戲吧

2、Looper

Looper 是用于為線程提供一個(gè)消息循環(huán)的機(jī)制。

線程默認(rèn)不提供Looper,而主線程是在應(yīng)用初始化的時(shí)候就為主線程提供了Looper. 證據(jù)何在呢?

餐廳默認(rèn)不提供外賣,也就沒有招聘外賣小哥。

當(dāng)系統(tǒng)啟動(dòng)一個(gè)應(yīng)用時(shí),入口函數(shù)即ActivityThread.main(String[] args)。

ActivityThread.java 

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();//接入外賣系統(tǒng),招聘外賣小哥。
        ...
        Looper.loop(); // 系統(tǒng)運(yùn)轉(zhuǎn)
        throw new RuntimeException("Main thread loop unexpectedly exited");
}
    

真像大白,嘖嘖。

主線程開了家餐廳,就同時(shí)做了外賣。率先吃了螃蟹,成為第一批富起來的資本家。

looper初始化:開餐廳的準(zhǔn)備工作

    創(chuàng)建主線程的Looper
    public static void prepareMainLooper() {
        prepare(false); //
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
    // 私有方法,參數(shù)為,是否允許該線程退出loop;公有方法prepare()默認(rèn)true。
    // 這個(gè)參數(shù)會(huì)傳給MessageQueue
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            // 不要貪得無厭,一個(gè)老板只能開一家餐廳。
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    Looper.loop(); // 。外賣系統(tǒng)開啟。

進(jìn)入循環(huán),loop() :餐廳外賣系統(tǒng)開始運(yùn)轉(zhuǎn)

通過for(;;)循環(huán)的MessageQueue.next()讀取下一條消息,將消息分配給對(duì)應(yīng)的handler執(zhí)行。

讀取下一條消息可能會(huì)發(fā)生阻塞,一是消息隊(duì)列沒有消息,等待下一個(gè)消息入列;二是消息還未到執(zhí)行的時(shí)間(消息延遲執(zhí)行。)

外賣小哥送外一單外賣回來就繼續(xù)干下一單,取單時(shí)可能出現(xiàn),當(dāng)前沒人點(diǎn)單,或者還沒到送貨時(shí)間,于是就做家門口抽起了煙,等待資本家跟他說可以開始配送了。

    // 源碼太長(zhǎng), 刪除一些,是系統(tǒng)關(guān)于每條消息執(zhí)行時(shí)間的一些log以及分析。

public static void loop() {
    for(;;) {
        Message msg = queue.next(); 
        if (msg == null) { // 返回的消息為空,說明線程退出looper。
                // No message indicates that the message queue is quitting.
                return;
            }
        msg.target.dispatchMessage(msg); // 配送
    }
}

for(;;) 無限的取訂單,并且派送,直到老板明確告訴今天關(guān)門了不接單了。

退出循環(huán):餐廳今日休業(yè)

休業(yè)分兩種,第一種直接關(guān)閉外賣系統(tǒng)。第二種把超時(shí)的訂單送完再直接關(guān)閉外賣系統(tǒng)。

    
    public void quit() {
        mQueue.quit(false);
    }
    
    public void quitSafely() {
        mQueue.quit(true);
    }
    
    直接調(diào)用MessageQueue.quit(boolean) 方法,false,即刪除隊(duì)列中所有消息,
    
    true則對(duì)比消息隊(duì)列中when與當(dāng)前的時(shí)間,msg.when小于當(dāng)前時(shí)間的依舊會(huì)被保留并執(zhí)行。比如:
    handler.sendEmptyMessage(A)
    handler.sendEmptyMessageDelay(B,1000)
    looper.quitSafely()
    則A會(huì)被執(zhí)行,B則被刪除。

3、Handler

發(fā)消息:下訂單

Handler中發(fā)消息有很多種方式。但殊途同歸,最終會(huì)走到sendMessageAtTime(msg,upTimeMillis)

    //uptimeMillis  消息應(yīng)當(dāng)被執(zhí)行的時(shí)間。用于消息的排序。
    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);
    }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; // 記住 target
        if (mAsynchronous) {  // 讓我們也先記住這個(gè)變量。vip訂單。稍后再分析
            msg.setAsynchronous(true);
        }
        // 進(jìn)入消息隊(duì)列。待分析MessageQueue再看
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

也就是每一個(gè)宅男腐女在同一家餐廳下的單,都會(huì)有下單時(shí)間或者定時(shí)送餐時(shí)間這個(gè)時(shí)間屬性

收消息: 配送過程

當(dāng)每一條消息從消息隊(duì)列取出來要被執(zhí)行時(shí),msg.target.dispatchMessage(msg), 上面的enqueueMessage()這個(gè)方法已經(jīng)告訴我們target就是相對(duì)應(yīng)的Handler!

也就是系統(tǒng)是知道每個(gè)訂單是對(duì)應(yīng)哪一個(gè)宅男的.然后根據(jù)指定的配送方式開始配送.

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) { // ①
            handleCallback(msg); // 也就是執(zhí)行  message.callback.run();
        } else {
            if (mCallback != null) { // ② 怎么又有個(gè)CallBack...
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    

聊一下這兩個(gè)CallBack吧。

第一個(gè)msg.callBack其實(shí)就是下面這種:


    handler.post(Runnable {   
            //本質(zhì)依然是Message, Message還有個(gè) Runnable變量,系統(tǒng)直接執(zhí)行Runnable中代碼,無需設(shè)置what之類的
        })
        
    相當(dāng)于:
    
    val msg = Message.obtain(handler, Runnable {...})
    handler.sendMessage(msg)

    msg.callBack 就是個(gè) Runnable

handler.mCallBack 就不一樣了。是Handler中定義的接口。

    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

我們知道實(shí)例化Handler的普通方式是,Handler() 或者 Handler(Looper),然后實(shí)現(xiàn)handlerMessage()方法。

而實(shí)際上,在這兩種方法基礎(chǔ)上還可以添加 CallBack參數(shù),比如說、

Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        //do sth by Message ... 
        return false;
    }
}); 

這里的CallBack.handlerMessage就會(huì)優(yōu)先于Handler.handlerMessage()執(zhí)行。

如果CallBack.handlerMessage 返回true,則不再執(zhí)行Handler.handlerMessage()

說到構(gòu)造函數(shù),這里回頭來說下mAsynchronous這個(gè)變量。年少健忘的你,趕緊往上翻一下。

這個(gè)變量其實(shí)也是Handler構(gòu)造函數(shù)的一個(gè)參數(shù),默認(rèn)為false,用于表示這個(gè)Handler發(fā)出的消息是否為異步消息(true為異步),影響MessageQueue在出隊(duì)時(shí)的順序。

    public Handler() {
        this(null, false);
    }
    
    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;
    }

所以我們可以這么理解,收消息,是外賣小哥的配送過程,各種CallBack是宅男指定的配送方式(空運(yùn)啊,水運(yùn)或者瑪莎拉蒂專送)。

而異步消息,就是宅男是個(gè)Vip客戶,發(fā)出的每條訂單都是vip訂單,在特殊情況下會(huì)有優(yōu)先配送的特權(quán)。

而所謂的特殊情況,稍后分析。

移除消息:取消訂單

能移除指定的消息或者全部消息。

    
Handler實(shí)例 A,調(diào)用下述的移除消息的方法時(shí),只能移除 msg.target == A 的msg

也就是只能取消自己下的訂單。

removeMessages(int what) { // msg.what == what
    mQueue.removeMessages(this, what, null); // MessageQueue的邏輯
}
removeMessages(int what, Object object) // msg.what == what && (msg.obj == object|| obj==null)

// token為null,則移除所有(msg.target ==  此handler)的msg
removeCallbacksAndMessages(Object token) 

4、MessageQueue

其實(shí)上述我們可以看到,Looper和Handler的部分邏輯都是在寫在MessageQueue里面的。

所以這里如果還提外賣小哥和宅男,就跟上面的描述重復(fù)了。我們就看看枯燥的source code吧。

MessageQueue,消息隊(duì)列。 存有當(dāng)前線程的所有未執(zhí)行的消息列表。這個(gè)列表以單鏈表形式存在。

鏈表的節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)正是 Message, Message有一個(gè) Message next = null變量用于指向下一個(gè)節(jié)點(diǎn)。

可通過Looper.myQueue()獲取當(dāng)前線程的MessageQueue實(shí)例。

MessageQueue提供了很多方法來對(duì)這個(gè)消息鏈表進(jìn)行操作。挑幾個(gè)重要的說一說。
也就是出列、入列以及退出循環(huán)。

出列

出列,即從消息隊(duì)列中讀取表頭的消息。Looper.loop()方法無限循環(huán)通過MessageQueue.next()獲取下一條消息。

next()內(nèi)部也有使用了for(;;)的無限循環(huán)迭代,直到鏈表為空的時(shí)候會(huì)阻塞直到下個(gè)消息的入列。

這里先拋出一個(gè)疑問,loop()已經(jīng)是無限循環(huán)了,為何這里也需要循環(huán)?

    Message next() {
        ...

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            
            // ①
            //JNI 方法,當(dāng)前線程在nextPollTimeoutMillis 后再喚醒執(zhí)行
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // ②
                if (msg != null && msg.target == null) { // 注意這里Target==null
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    // 找到第一個(gè)異步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                // ③
                if (msg != null) { 
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        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;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // ④ 
                //If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

這個(gè)代碼有點(diǎn)長(zhǎng),主要分為四個(gè)步驟:

  1. 當(dāng)前線程進(jìn)入阻塞,指定nextPollTimeoutMillis時(shí)間之后被喚醒,-1表示一直阻塞(消息隊(duì)列為空)
  2. 如果消息列表的表頭的target為null,則獲取列表中的第一條異步消息。進(jìn)入步驟三。
  3. 如果當(dāng)前消息的執(zhí)行時(shí)間when 大于當(dāng)前時(shí)間,則nextPollTimeoutMilli等于兩者的差值,線程可能進(jìn)入阻塞狀態(tài),并進(jìn)入步驟四。否則next()方法直接返回當(dāng)前消息。
  4. 來到步驟四,要么是當(dāng)前消息列表為空,要么當(dāng)前要執(zhí)行的消息還沒到時(shí)間。也就是屬于空閑狀態(tài),所以會(huì)執(zhí)行IdleHandlers的任務(wù)。這里若IdleHandlers若為空,則進(jìn)入步驟1,否則執(zhí)行完IdleHandlers之后進(jìn)入步驟2.

從執(zhí)行步驟上來看,在步驟四上,是存在多次迭代甚至一直循環(huán)的可能性,所以這里用for(;;)來處理。就一般來說,for(;;)會(huì)很快在步驟三就退出循環(huán)

這里有兩個(gè)知識(shí)點(diǎn):IdleHandler 是什么,message.target什么時(shí)候回等于null。留著最后講解。

入列

根據(jù)handler.sendMessageXXX()調(diào)用的時(shí)間以及延遲的時(shí)間獲得執(zhí)行的時(shí)間,即msg.when,根據(jù)when,將message插在鏈表適當(dāng)?shù)奈恢?,可能是第一個(gè)。

    boolean enqueueMessage(Message msg, long when) {
        ...
        // 刪除一些判斷條件
        ...
        synchronized (this) {
            if (mQuitting) {  // 執(zhí)行了 Looper.quit()
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;  // mMessage,當(dāng)前消息鏈表的頭結(jié)點(diǎn)
            boolean needWake;
            if (p == null || when == 0 || when < p.when) { //當(dāng)前消息插在表頭
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

退出循環(huán)

在Looper.quit()我們就提到,其內(nèi)部實(shí)現(xiàn)調(diào)用的是 MessageQueue.quit(boolean)

void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                // 把
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

5、小結(jié)

線程是餐廳,Looper該餐廳的是外賣小哥,Handler是某個(gè)點(diǎn)外賣的宅男。
MessageQueue相當(dāng)于一個(gè)線上下單的餐廳,會(huì)根據(jù)顧客需求把所有訂單按出餐的時(shí)間排序(為什么不是下單時(shí)間,是因?yàn)橛行╊櫩蜁?huì)讓你定時(shí)送過來)。

外賣小哥負(fù)責(zé)按時(shí)把外賣按照指定的配送方式送到宅男手上。則宅男可以下單、指定配送方式以及取消訂單。

而IdelHandler就像沒人下單,老板很閑,就去接了點(diǎn)私活。

異步消息呢,就像是Vip訂單,當(dāng)出現(xiàn)特殊情況(target
==null),我們稱之為會(huì)員活動(dòng)日,當(dāng)會(huì)員活動(dòng)日到來的時(shí)候,外賣小哥就會(huì)優(yōu)先派送Vip訂單,除非已經(jīng)沒有Vip訂單了或者活動(dòng)結(jié)束了才會(huì)派送普通訂單。

當(dāng)老板想停止?fàn)I業(yè)了,可以發(fā)出兩種指令,一種就是把所有的剩下的訂單全部丟掉直接關(guān)門,還有一種就是由于訂餐量太多,很多超時(shí)沒能派送出去的訂單,老板會(huì)把派完再關(guān)門,至于那些還沒到時(shí)間派送的訂單,就只能全部自動(dòng)退訂!

6、異步消息與同步消息:會(huì)員活動(dòng)日

前面我們已經(jīng)有解釋過同步異步消息了,就是在Handle對(duì)象初始化時(shí)構(gòu)造參數(shù)boolean async的區(qū)別。

會(huì)員活動(dòng)日的到來

一般來說這個(gè)參數(shù)不會(huì)有任何作用,直到會(huì)員活動(dòng)日的到來。

也就是餐廳老板,在門口掛上牌子,MessageQueue.postSyncBarrier()

MessaegQueue.java 

    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

根據(jù)需求在指定的時(shí)間when,宣布會(huì)員活動(dòng)的到來。即插入一條target==null 的message!

同時(shí)返回一個(gè) token值,即 本次是 第幾屆 會(huì)員活動(dòng)日。

會(huì)員活動(dòng)日的結(jié)束。

如果會(huì)員活動(dòng)一直不結(jié)束的話,每送完一單,外賣小哥都是會(huì)先看看還有沒Vip單,然后優(yōu)先派送Vip,如果這個(gè)訂單還沒有到時(shí)間,那么外賣小哥就會(huì)先停下來抽個(gè)煙。。直到可以派送Vip單。。這樣效率就很低了。所以,需要及時(shí)的取消活動(dòng)日。

    public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

根據(jù)token 結(jié)束對(duì)應(yīng)的該屆的活動(dòng)。

7、IdleHandler :接私活

雖然不在飯點(diǎn),不用送外賣,作為老板自然要有點(diǎn)上進(jìn)心,引進(jìn)點(diǎn)其他業(yè)務(wù)坐也是提升營(yíng)收的好辦法嘛。比如既然大家平常下訂單都喜歡備注多加米飯,趁現(xiàn)在多煮一鍋飯嘛。或者直接跟別人三缺一,打個(gè)四圈,也是勞逸結(jié)合嘛。

在講述MessageQueue.next()已經(jīng)提過源碼了,再把相關(guān)的貼一下吧。

    public Message next() {
        int pendingIdleHandlerCount = -1;
        for(;;) {
                ...// msg == null
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {  // 注意結(jié)合代碼塊最后面的注釋
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            // 設(shè)置為0之后,在這個(gè)for(;;)里面或者這次的next()的調(diào)用中,上面的這個(gè)idle的for循環(huán)不會(huì)再次運(yùn)行了
            pendingIdleHandlerCount = 0;
            
        }

只有在 當(dāng) 消息隊(duì)列為空或者 第一條消息還沒到執(zhí)行的時(shí)間,IdleHandler才會(huì)被執(zhí)行,跟普通的Message不同的是,每條IdleHandler可能會(huì)被執(zhí)行多次,如果這個(gè)IdlerHandler被定義為保留的話-- keep = idler.queueIdle() == true

當(dāng)然在同一次空閑時(shí)間內(nèi),Idles只會(huì)被執(zhí)行一次。

那么如何接私活呢?

先看下私活模板長(zhǎng)啥樣。。

MessageQueue.java

    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }

私活的內(nèi)容寫在queueIdle()里面,同時(shí)用返回值告知老板,這個(gè)是不是長(zhǎng)期任務(wù)。

然后通過訂單系統(tǒng)MessageQueue.addIldeHandler()把私活加入到私活列表上!

MessageQueue.java
   /**
     * Add a new {@link IdleHandler} to this message queue.  This may be
     * removed automatically for you by returning false from
     * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
     * invoked, or explicitly removing it with {@link #removeIdleHandler}.
     *
     * <p>This method is safe to call from any thread.
     *
     * @param handler The IdleHandler to be added.
     */
    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }

后記

好啦到這里尾聲了。修修改改,偷偷懶懶。我太難了。。找時(shí)間再把,同步消息以及異步消息的例子給補(bǔ)上。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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