生動形象理解Handler源碼

我知道關於Handler的文章在網上已經快被寫爛了/笑着哭,但是還是想寫出來,我覺得只有自己親自寫過這些東西,纔會更深入的理解這些。
雖然以前也寫過Handler,但是當時剛剛接觸Handler的源碼,所以寫的像屎一樣。。。。(在簡書上寫的,不忍直視)。現在本人自我感覺關於Handler已經有個相對有一點自己見解了,所以今天會更加細緻的寫這篇博客。
好了不多說了,開始我們今天的正題:

這個東西如果直接從源碼講起來會很亂,所以今天我們先從我們接觸到的一些類和方法開始講。


Handler使用(簡述)

關於Handler的使用這裏就不多說了,相信在看這篇文章的時候大家已經把Handler用爛了。簡述一下調用的幾個方法:
handler.postXXX(Runnable,。。。。);

還有handler.sendXXX(。。。。);這是我們使用Handler的兩類方法,大家應該都會用的,不用多說。

Handler的構造方法中出現的相關方法

我們使用Handler,首先要有一個Handler的對象,通常我們最簡單的方法,就是直接new ,然後重寫handlerMessage方法:

Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

當然也可以在參數裏面加上Callback回調來用(個人喜歡這種,因爲第一種warning是一坨黃色的,看着很煩-。+):

Handler handler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        return false;
    }
});

先進入Handler的第一個構造方法:

public Handler() {
    this(null, false);
}

這裏面的兩個參數,我們在Android Studio中可以發現,第一個就是我們的Callback,第二個是說是否異步(我們不管他這個東西)。

點進this看一下:

public Handler(Callback callback, boolean async) {
    ......

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

我省略掉了上面的一些代碼,只要看一下這一段就好:
這裏唯一一個比較陌生的就是Looper,其他的都能看懂。其實應該也不算陌生:因爲在學Handler時候,各個書上差不多都有一個簡述Handler的工作原理,裏面都有提到過Looper,這個Looper稱爲輪詢器,負責不停地輪詢,把我們的消息取出來,然後執行。這只是大概工作原理,其實如果真的喫透Handler機制會發現,真正的輪詢另有其人(下面會提到)。

既然他先出來的,那麼我們先看一下mLooper方法:

/**
 * 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();
}

這個方法返回了和當前線程關聯的looper對象。
先說一下這個sThreadLocal:
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();


這是在Looper中的一個靜態引用,這麼看來我們所有的Looper都要共享這個mThreadLocal了,我們可以把這個mThreadLocal理解爲一個大箱子,裏面放着很多的Looper對象。上面用到了ThreadLocal的get方法,我們看一下:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

他返回值是當前線程的某個值,現在我們把泛型指定爲Looper ,所以他肯定是拿到了當前線程的Looper,這個很容易理解。接着我們簡單說一下下面出現的這幾個類:
  • ThreadLocalMap
  • Entry
ThreadLocalMap是ThreadLocal的一個靜態內部類,而Entry又是ThreadLocalMap的一個靜態內部類,(個人感覺他們是組合關係,大家有不同意的可以評論交流一下)

首先通過getMap方法拿到map對象,我們看一下getMap方法:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

是返回了Thread中的一個屬性,這樣看來Thread中有ThreadLocalMap的引用。Thread和ThreadLocalMap可以關聯。

在上述代碼中我們注意到最後是從Entry e中拿到了value作爲返回值返回,這麼推理是Entry中存儲了我們的Looper。我們看getEntry方法:

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

table是ThreadLocalMap的一個屬性:

private Entry[] table;

我們通過table拿到Entry對象,然後返回這個e。(在一般不出意外的情況下)

現在我們跳回到上面的get方法中,目前我們已經拿到了e對象,如果不爲空會返回e存儲的值(value):

if (e != null) {
    @SuppressWarnings("unchecked")
    T result = (T)e.value;
    return result;
}

這是我們myLooper的方法。簡單總結一下:

我們調用了sThreadLocal的get方法,在get方法中首先獲取到當前線程(Thread.currentThread()),然後拿到對應線程的ThreadLocalMap對象(getMap),最後通過ThreadLocalMap拿到Entry對象(getEntry)返回e的value值。


我們在Handler的構造方法上耗時有點多了,開始掃尾:

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;

跳回我們Handler構造方法:在獲取到looper對象之後,我們就是給Handler的屬性賦值,這裏又出現了一個新的類,mQueue對應的類——MessageQueue,關於這個類我們下面詳細講,現在我們只需要Handler和Looper保留了它的引用。在Handler構造方法中進行了相關屬性賦值。到現在我們Handler的構造方法可以過了。

handler.sendXXX(。。。。);

上面說到Handler發送消息有兩種方式,我這裏就那sendMessage方法舉例了(其實最好會發現,所有方法都一樣-。+):

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

這是我們點進了sendMessage方法,我們看到他是嵌套了一個方法,我們再點進去看一下:

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

又是嵌套(其實還有好多嵌套呢-。+),接着點:

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

也是嵌套,但是這個嵌套有點東西:

MessageQueue queue = mQueue;
return enqueueMessage(queue, msg, uptimeMillis);

注意這兩行代碼,在接下來的方法中,我們又放入和一個MessageQueue引用做參數,現在我們再點進這個方法看:

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

好了,現在又有一個Message對象,是不是一個沒搞懂,又冒出來好幾個的感覺,大家淡定,先分析一下他的邏輯,只有這幾個類會在下面詳細介紹的:

我們將msg的target屬性設爲this,這個方法是在Handler中的,沒猜錯的話target應該是Handler類型的,點進去看一下:

/*package*/ Handler target;

果然如此哈哈!
最後返回的是MessageQueue的方法了,好我們就此打住。
總結handler.sendXXX():

經過層層方法嵌套,最後進入了MessageQueue的enqueueMessage方法。


接下來我們重點看一下剛纔提到的還有在Handler中幾個重點了類:

Message
MessageQueue
Looper

我們分別看一下:

Message

目前主要看這幾個屬性就好:

/*package*/ Handler target;
/*package*/ Runnable callback;

/*package*/ long when;
// sometimes we store linked lists of these things
/*package*/ Message next;

第一個是我們剛纔看到的target,我們在enqueueMessage方法中,將當前的Handler賦值給了msg的target。

callback:這個屬性在我們通過post方法發送消息的時候會用到,具體大家自己分析完post方法就知道了。
when:這個是一個用作記錄時刻的屬性,官方解釋爲運行回調的絕對時間。
next:相信大家都知道鏈表是個什麼東西,這個next就是一個指向鏈表下一元素的指針。

其他的我們等會兒遇到了再說,現在先知道這三個屬性就好。

MessageQueue

如果沒猜錯的話,這個類應該纔是我們最應該頭疼的一個類了,我們簡單看一下它的註釋:

/**
 * Low-level class holding the list of messages to be dispatched by a
 * {@link Looper}.  Messages are not added directly to a MessageQueue,
 * but rather through {@link Handler} objects associated with the Looper.
 *
 * <p>You can retrieve the MessageQueue for the current thread with
 * {@link Looper#myQueue() Looper.myQueue()}.
 */

我們只看其中一句很重要的:Messages are not added directly to a MessageQueue:我們的Message不是直接儲存到MessageQueue,這是神魔意思?我查找了一下MessageQueue的所有屬性,顧名思義,如果MessageQueue是用來存儲Message的,那麼首先肯定有用一個集合相關的屬性,來存儲Message,可是我看完了所有的屬性,。。。。。。真的沒有!

但是我們發現了熟悉的身影:mMessage屬性,他是Message類。還記得我們上面提到過的Message.next屬性嗎?我們是不是可以大膽地猜測一下:MessageQueue中放置了一個Message鏈表的頭結點,然後通過Message元素自身添加和刪除,實現Message的鏈式存儲。(目前這個只是咱們的猜測)
剛纔在Handler.sendXXX()中,最後跳轉到的就是MessageQueue的enQueueMessage方法,我們來看一下這個方法(前方高能!):

MessageQueue.enqueueMessage

    boolean enqueueMessage(Message msg, long when) {
        ......

        synchronized (this) {
            ......

         
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //原來mMessages爲鏈表頭,現在讓msg變成了鏈表頭,原來的mMessagges引用的Message對象在msg的後面。
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                ......

                /*
                * msg不是新的頭,所以要按照時間順序排列鏈表,查找對應的位置*/
                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;
            }

            ......
        }
        return true;
    }

爲了廣大的讀者,我已經刪除了一部分無用的代碼,但是還看着很多。我們逆着想一下:我們handler.sendXXX方法是把我的Message對象放入了隊列中,所以最後跳轉到的這個方法應該是一個存儲Message的過程,首先我們有着這樣的一個總體思路,然後接着看:

首先我們下面所有的代碼都寫入了同步鎖鎖住的代碼塊中,首先給傳入msg的when屬性賦值,新建了一個Message引用等於mMessages。
我們的進行了一個簡單的if判斷,無論鏈表爲空,還是絕對時刻爲0,或者是新插入的msg時刻比頭結點的時刻小(簡單理解是在它之前),這三種情況只要滿足其中一種,就把新插入的msg設爲新的頭結點。這三行代碼就是指定新的頭結點,自己看去。。。
如果這三種情況都不是,那麼就要查找msg應該對應的位置,標準就是絕對時刻(when):通過遍歷隊列,直到找到對應位置,如果到最後還沒有對應的位置,說明他的絕對時刻最大,所以放到最後。
最後返回true。
這是我們enqueueMessage方法的過程。對應代碼上又備註。
MessageQueue還有一個很重要的方法:next()(至於用處一會兒回用到):

MessageQueue.next():

    Message next() {
        ......
        for (;;) {
            ......

            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) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    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;
                }

                ......
            }

            ......

            ......
        }
    }

又刪除了百分之80的垃圾代碼-。+。剛纔我們說enqueueMessage方法是把Message放入,那麼next方法就是將Message取出的。我們來看一下:

    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());

我們先看這一段代碼:首先新建了prevMsg,一個新的Message引用,然msg指向頭結點。

接着把preMsg和msg移動到同步Message位置(因爲這段程序整個都在同步鎖中,異步的Message不受限制,這段話可以當做放屁-。+)。

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

這段是將msg取出,然後將鏈表斷開位置重新連接。(詳細邏輯如上,大家自己看)


Looper

這個類是我們需要說的最後一個類了,感覺很累。。

我們知道如果在子線程中使用Handler,我們還需要做額外的兩件事:Looper.prepare()和Looper.loop();這兩個方法有什麼作用呢?我們來看一下:

Looper.prepare()

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

相信到這裏大家看的應該比較輕鬆了,因爲幾乎都是我們剛剛瞭解的方法:
首先進行判斷,在sThreadLocal中是否存在當前線程的looper對象,在初次創建時候,是沒有的。
然後創建一個新的Looper對象,放入sThreadLocal中。
我們看一下set方法:

Looper.set():

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }


邏輯很清晰:

  1. 獲取當前線程對象。
  2. 獲取線程的ThreadLocalMap對象。
  3. set方法添加Looper對象。
我們來看一下這個set方法:

ThreadLocalMap.set(。。。):

        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

這個不怎麼重要,我們只需要知道最後我們把value添加給了Entry[]  table就好。

Looper.loop():

    public static void loop() {
        final Looper me = myLooper();
        ......
        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;
            }

            ......
            ......
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ......

        }
    }


loop方法是用來輪詢的的,在這裏我們也看到了一個for死循環,我們看一下方法邏輯:

  1. 獲取當前線程的looper對象。
  2. 進入死循環,不斷獲取當前線程的消息隊列中的Message對象。
  3. 分發:msg.target.dispatchMessage(msg);
如果有消息,我們會通知msg對應的handler執行dispatchMessage方法。我們一起看一下這個方法:

handler.dispatchMessage(Message msg):

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

這個方法有三條路:

  • 執行msg自己的Callback(這個是post發送消息最後的回調)
  • mCallback接口執行handleMessage方法(這個是我們在構造Handler時候添加和Callback情況下的回調)。
  • handleMessage(這個是我們重寫和Handler.handleMessage情況下的回調)。
相信這幾行代碼大家已經很親切了。

講到這裏,相信大家對我們Handler的運行機制,心裏已經透徹了一些,但是還沒完,我們要知道我們在主線程中(MainActivity中的實例變量爲例),他就不用調用Looper.prepare和Looper.loop方法,我們看一下ActivityThread的main方法就一目瞭然了:

    public static void main(String[] args) {
        ......

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        ......
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

我只留下了這兩個方法,和最後的異常:我們在運行之初,就已經給主線程進行了prepare(prepareMainLooper本質還是調用prepare,大家下去自己去看)和loop方法,所以說一旦loop方法調用之後,不會終止,這是一個真的無效輪詢過程。否則就會拋出如上異常。

大家還記得我之前說的嗎:相比looper,真正輪詢的工作另有其人。可能現在這麼說已經不太對了。就目前來看,我們在運行時有兩個無線輪詢:loop方法中和next方法中。如果說他們都是輪詢的工作的話,那麼looper是進行着不同線程之間的輪詢(通過獲取當前線程來獲取當前輪詢器,調用對應的消息隊列進行消息輪詢),而MessageQueue的輪詢,是當前線程的消息輪詢(如上所述-。=)。

Handler機制總結:

非主線程情況,需要調用Looper.prepaore和Looper.loop方法:

Looper.prepare方法給當前線程綁定一個Looper對象(本質是Thread中有ThreadLocalMap引用,而Looper中也有Thread引用,從而實現了雙向關聯)。Looper.loop方法進入無限輪詢,不斷地調用當前線程的Looper對象的mQueue對象的next方法(有點拗口)。當獲取到消息時,就分發給對應的handler去執行(通過三種回調,具體看上面)

幾張很有用的圖-。+:

這麼多文字,相信大家已經看得想吐了,最後給大家帶來幾張圖吧。
這張圖對Handler的輪詢機制是很形象,但是希望大家不要吐槽我的畫圖能力-。+:

傳送帶,電池。。。。都是大家能看懂的東西,領會精神哈!

這個是剛纔我們Looper相關類的類圖。

最後一張送上涵蓋我們上述的主要的類的一張類圖。(剛學UML,畫的可能有點迷,見諒!!)。

喜歡的朋友希望關注一下,如果有不同的觀點或者指出錯誤歡迎下方留言。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章