Android Handler-帶你從源碼理解消息機制

前言

在我們的Android日常開發中,我們經常會遇到一些比較耗時的I/O操作或者訪問網絡的操作,而我們都知道Android系統規定不能在主線程中執行以上等操作,所以我們只能開啟一個子線程來執行這些操作;然而為了確保UI操作的安全性,Android系統又規定不得在非主線程中操作UI。那么當我們在一個子線程中操作完一個事件后想要通知主線程去更新UI元素的話該怎么辦呢?Handler的出現就完美的解決了這個問題。

關于Handler

1.定義:

一個Handler允許你發送、處理和運行與線程相關的對象。它的主要作用有兩個,第一,排入一個消息并且在未來的某個時間點上運行處理這個消息;第二,在不同于你所處的線程上排入一個將要被執行的操作。

2.基本用法:

關于Handler的用法,這里我列出一個最常見的使用方式,那就是在主線程中創建一個Handler,然后在子線程中發送消息給Handler,讓Handler去處理消息。代碼如下:

public class HandlerTest {
    private static final String TAG = "Handler";
    // 創建Handler對象處理Message消息
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    Log.d(TAG, msg.obj + "");
                    break;
                default:
                    break;
            }
        }
    };
    /**
     * 此方法暴露給外部Activity調用,內部開啟一個子線程用于模擬執行耗時操作并通過handler發送一個Message消息
     */
    public void create() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 假設這里開始執行一段耗時操作
                // ==========
                // 耗時操作執行完畢,需要通知主線程更新UI
                Message message = Message.obtain();
                message.what = 1;
                message.obj = 100;
                handler.sendMessage(message);
            }
        }).start();
    }
}

打開某一個Activity創建HandlerTest對象,并調用其create() 方法,就會在控制臺打印出一條100的日志。以上就是一個很常見的Handler的使用方式。

3.重要角色:

其實Handler之所以能完成發送消息,處理消息等一系列的操作,這和幾個重要的“幫手”是分不開的。他們分別是Message,MessageQueue,Looper以及Handler,它們之間整體的協同工作流程可以比作成一個郵寄信件的過程。

3.1.Message:

定義:一個可以被發送給Handler的消息,它可以包含一段描述和具體的對象數據。
Message可以看成是每一封信件。

3.2.MessageQueue:

定義:一個持有將要被Looper分發出去的消息(Message)列表的底層階級的類。
MessageQueue就像是一個郵箱,里面存放著信件(Message)。

3.3. Looper:

定義:一個用于為一個線程執行消息循環的類。
我們假設投遞的郵箱是一個非常高級的郵箱,它的內部有一個可以自動將投遞進來的信件取出并放置到郵遞員取信區域的機器。當有信件投遞進郵箱時,它就工作;當郵箱中沒有信件時,它就暫停工作,直到有新的信件被投遞進來后它再繼續工作。而Looper就是這么一個非常“智能”的角色。

3.4.Handler:

定義這里就不再贅述,而其本身就是充當信使這么一個角色,它要發送和處理信件。用一張圖概括他們之間協同工作的關系就如下圖所示:


handler.png

4.走進源碼:

這里我打算先把每一個“幫手”的實現原理(源碼)先簡單介紹下,最后再解讀一個消息從創建到發送再到處理的整個過程。這樣在理解整個過程中,就不會對某一個類的變量或者方法有疑問了。

4.1.Message:

關于Message,它是一個消息實體類。它的內部有幾個比較重要的變量:

1.what:用來區分Message的一個標志。當handler處理消息的時候,知道是哪一個Message。
2.arg1和arg2:兩個int類型的成員變量,當一個Message只需要攜帶少量整型值的時候可以用他們存儲。
3.obj:可以被Message攜帶的任意類型的對象。(當用于跨進程通信時,如果它是一個framework層的類那么它一定不能為空)
4.data:存儲數據的Bundle對象。
5.target:就是當前處理它的handler,根據字面意思就是目標的意思,創建一個Message的目的就是讓handler處理它,這樣更好理解些。
6.when:當前Message交付到目標handler的具體時間節點(后面會更詳細的介紹)。
7.next:一個Message對象,可以理解為當前Message的下一個Message,和鏈表有些相似。
8.sPool:本身也是一個Message對象,根據命名我們也可以理解為一個消息池(后面在講解Message的創建方式時會再次提到)。

在Message類的內部,提供了很多創建它的方法,這里我們看一下其中的一個obtain()方法的源碼:

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 * 從一個全局池中返回一個Message實例,避免讓我們在多種情況下創建新的對象實例。
 */
public static Message obtain() {
    synchronized (sPoolSync) { // 1.通過同步代碼塊的方式來保證線程操作消息池時的安全性。
        if (sPool != null) { // 2.判斷sPool是否為空,如果不為空,直接復用sPool并返回。
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message(); // 3.如果sPool為空,那么就返回一個新的消息實例。
}

還有幾個obtain()方法的重載,內部也是調用了obtain()方法;另外,Message還有一個無參的構造函數,但是源碼中也在此方法的注釋部分聲明建議使用obtain()方法來獲取一個Message對象,至于原因就是和obtain()方法的注釋一樣,避免創建多個對象實例浪費內存空間。另外,其內部還有些get、set方法,這些方法聞其名知其意,這里不過多描述。

4.2.MessageQueue:

關于MessageQueue,我們在調用handler發送和處理消息的時候并不會直接調用到它,而是在handler的內部才能看到它的身影。它的源碼相對于Message來說還是較復雜的,但這里我們挑重點的說。其實MessageQueue在整個過程中的作用就是插入消息和取出消息,插入消息對應的方法是enqueueMessage方法,而取出消息對應的方法是next方法。在閱讀這兩個方法之前,我們先了解幾個比較重要的成員變量。

4.2.1.mQuitAllowed

首先說到的是mQuitAllowed變量,這個變量的含義是當前的MessageQueue是否允許被退出。它在MessageQueue的構造方法中被賦值,其構造方法如下:

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

而MessageQueue的構造方法是在Looper中被調用的,在Looper被創建的時候,它會同時創建一個MessageQueue。如果是主線程創建的Looper那么傳入這個MessageQueue構造方法的參數就為false,如果是其他線程的話就為true(具體的源碼實現后面在講解Looper的源碼時會看到)。

4.2.2.mQuitting

接下來是mQuitting變量,這個變量的含義是當前的MessageQueue是否可以退出了。它默認為false,只有當MessageQueue的quit方法被調用后才會置為true,quit方法如下所示:

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true; // 這是唯一可以將我置為true的地方
        if (safe) {
            removeAllFutureMessagesLocked(); // 清空所有的延遲消息
        } else {
            removeAllMessagesLocked(); // 清空所有的消息
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

這里要注意的是,千萬不要把這個變量和剛剛說的mQuitAllowed變量弄混,它們兩個從命名上很相似。但仔細看quit方法的第一行我們就可以知道,只有當mQuitAllowed變量為true的前提下,quit方法才得以正常的執行下去,mQuitting變量也才可以有機會被置為true;如果mQuitAllowed變量為false的話,那么會拋出非法狀態異常。而這個quit方法是在Looper的quit方法或quiteSafely方法中被調用的。

4.2.3.mBlocked

接下來是mBlocked變量,它也是布爾類型的,它的含義是作為MessageQueue中next方法是否以一個非零的超時時長被阻塞在pollOnce()方法中的一個標志,簡單的可以理解為當前消息隊列是否被阻塞的標志。它的使用和賦值的地方就在enqueueMessage和next方法中,后面在講解兩個方法的時候會具體提到。

4.2.4.mMessages

最后說一下比較重要的mMessages變量,它是一個Message類型的變量。還記得剛剛我們介紹Message時說它里面有幾個比較重要的變量,其中有一個叫next的變量,它也是一個Message類型的變量。而MessageQueue雖然被稱作消息隊列,但它的實質并不是一個隊列,而是一個依靠Message自身的鏈表特性而連接起來的一個鏈表結構。在接下來要講到的enqueueMessage方法中會更清晰的驗證這一點。

4.2.5.enqueueMessage方法

現在,一起來看一下向隊列中插入消息的方法。方法的源碼如下(方法比較長,這里我在每一個操作的前面添加相應的解釋):

/**
 * 方法的調用是在Handler中
 */
boolean enqueueMessage(Message msg, long when) {
    // 這里首先判斷msg的target是否為空,如果為空,直接拋出異常(沒有目標,就不知道最后要把你發送給誰)。
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    // 這里判斷msg是否已經被使用,如果已經被使用,直接拋出異常(isInUse方法在Message源碼中可以看到) 
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {
        /**
         * 這里用到了剛剛說到的mQuitting變量,如果發現mQuitting為true,就說明quit方法已經被順利調用;
         * 當前的消息隊列就不會再接收任何消息了,也就意味著這時再調用handler發送消息已經沒有用了;
         * 同時拋出一個異常,并將msg對象回收置空。
         */
        if (mQuitting) {
            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標記為已經被用過
        msg.when = when; // 將方法中的第二個參數設置成msg的when變量
        Message p = mMessages; // 創建臨時變量p并指向mMessages變量
        boolean needWake; // 創建一個布爾變量用來標記是否需要喚醒當前隊列
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            // 當滿足此條件時將msg添加到消息隊列的頭部,并且根據mBlocked的值來決定是否喚醒隊列
            msg.next = p; // 將msg的next指向剛才的mMessages
            mMessages = msg; // 再將mMessages指向msg
            /**
             * 進入此條件有兩種可能,第一種是消息隊列為空,這時隊列處于阻塞態,所以mBlocked的值肯定為true,這時需要喚醒;
             * 第二種就是消息隊列不為空,但是本次插入的消息是調用了handler的sendMessageAtFrontOfQueue發送的msg;
             * 此時隊列本身就就未處于阻塞態,mBlocked為false,所以無需再次喚醒
             */
            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();
            // 下列操作就是根據msg的when變量的值的排序來將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;
        }
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr); // 這里是真正的喚醒隊列的操作
        }
    }
    return true;
}

以上就是向隊列中插入消息的過程,其實簡單的可以概括為三步。第一步,判斷消息是否滿足入隊的條件并且檢測隊列是否能夠插入消息;第二步,根據隊列和消息的狀態來決定消息插入的方式以及確定是否需要喚醒隊列;第三步就是根據值來決定是否需要喚醒隊列。經過剖析這個方法,是不是也可以確定了消息隊列的實質其實就是一個鏈表,而非隊列。

4.2.6.next方法

看完了消息入隊的方法,我們再來研究一下消息出隊的方法。next方法源碼如下:

/**
 * 此方法的重點是看它返回Message的邏輯,有些和Message不相關的地方,就不貼出來了。
 */
Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    // 如果消息循環已經退出并且釋放,那么直接在這里返回;這種情況發生在程序退出后重新啟動looper(這種操作是不支持的)
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; // -1 only during first iteration(僅在第一次迭代的時候為-1)
    // 這里創建一個int類型的變量,個人理解它的含義為到當前消息被返回的時長
    // 這里也提前指出,在即將進入的循環中,這個變量會根據不同的條件被賦值;
    // 當它的值為-1的時候,代表已經沒有消息了,方法就會阻塞在nativePollOnce方法那里
    // 直到有新的消息入隊,重新喚醒隊列
    int nextPollTimeoutMillis = 0;
    for (;;) { // 進入一個沒有任何判斷條件的死循環
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 這是一個native層方法,當nextPollTimeoutMillis為-1時,將阻塞在這里
        nativePollOnce(ptr, nextPollTimeoutMillis); 
        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis(); // 獲取當前時間
            Message prevMsg = null; // 創建變量prevMsg
            Message msg = mMessages; // 創建msg變量并指向mMessages
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                // msg的target為空,不合法,就丟掉它尋找下一個異步消息
                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,接下來的操作就是從隊列中返回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; // 消息為空時將nextPollTimeoutMillis置為-1
            }
            // Process the quit message now that all pending messages have been handled.
            // 當其他的所有消息都處理完,處理退出的消息
            if (mQuitting) {
                dispose();
                return null;
            }
            // 后面還有一些代碼,但是和主要邏輯無關,這里不貼出來了,感興趣的可以自己閱讀下
            ............
        } 
        ............
    }
}

以上就是從消息隊列中取出消息的方法,這個方法的源碼雖然比較多,但是真正和取消息相關的代碼其實并不多。那么取出消息的過程也可以簡單分成三步,第一步,判斷隊列是否已經退出以及消息是否合法;第二步,根據隊列中消息的順序確定要返回的消息;第三步,將返回的消息從隊列中移除。
另外,這里還要強調一個知識點,就是上面我貼出的代碼的最后,當發現mQuitting變量為true的時候,返回一個null,next方法就此結束。而這里要說的就是,當我們在一個子線程中創建了一個Looper,并且調用了它的loop方法開啟了消息循環,那么當線程中消息隊列里面的消息處理完成之后,線程就會一直阻塞在next方法中,以至于線程無法終止。因此如果我們想要線程在執行完全部的消息之后可以正常的終止的話,那么就應該在執行完全部的消息之后調用Looper的quit或者quitSafely方法來退出Looper,這樣MessageQueue中的mQuitting變量就會變為true,從而解除線程阻塞態。

4.3.Looper:

這個Looper也是在Handler內部被調用的,就是它把消息發送到了Handler的手里。其實關于Looper,之所以我們平時在使用Handler的時候不會直接用到它,是因為通常情況下我們都是在主線程中使用的Handler,而主線程默認為我們做了關于Looper的一些初始化操作。在文章的開頭,我們在主線程中直接new了一個Hander并沒有出任何的問題,而如果采用同樣的方式在一個子線程中直接new一個Handler的話,程序會立即崩潰,并且在控制臺我們可以看到崩潰日志為:

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

就是說,一個線程中如果沒有調用Looper.prepare()方法,那么是無法創建一個Handler的。至于具體原因后面講解Handler的源碼時候就能明白了。現在我們先從Looper的prepare方法說起,首先看下這個方法:

/**
 *方法內部調用重載的prepare方法
 */
public static void prepare() {
    prepare(true);
}
/**
 * 方法中的sThreadLocal是一個ThreadLocal類型的成員變量
 * 這里看下方法中的參數,應該有印象吧,這個參數其實就是最終要傳遞到MessageQueue中的那個隊列是否允許被退出的標志
 */
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) { // 如果get方法返回不為空,說明當前線程已經創建了Looper
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed)); // 如果沒有創建,那么就創建一個Looper并存儲到sThreadLocal中
}
/** 
 * Looper的構造方法,內部創建一個MessageQueue并指定當前的線程(mQueue和mThread為兩個成員變量)
 */
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

剛剛我們提到了一個ThreadLocal類,這個類的原理這里就不深入講解了,它是一個在線程內部存儲數據的類,當在指定的線程中存儲了數據以后,只有在指定的線程中才能獲取到存儲了的數據,而在其他的線程是無法獲取到的。prepare方法的邏輯還是很清晰的,主要用于在一個線程中創建一個Looper,要注意不能在一個線程中多次調用,否則會導致程序崩潰的。
現在我們回過頭來說主線程,在主線程ActivityThread的main方法中,系統默認為我們調用了Looper的prepareMainLooper方法,這個方法的源碼如下:

/**
 * 方法的內部也是調用了prepare方法,并且傳入的參數為false。同時,這個方法也不能被多次調用;
 * 方法的源碼注釋也說到,一個程序的主線程的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();
    }
}

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

這就是我們在主線程直接創建一個Handler的時候不會崩潰的原因,因為系統默認為我們調用了Looper的prepareMainLooper方法。其實真正創建一個Looper對象的地方是在prepare的帶有一個參數的重載方法中,它接收的那個布爾類型的參數是最終賦值給MessageQueue中的mQuitAllowed變量的。通過剛才了解子線程和主線程創建Looper對象的方式后,我們知道子線程是調用prepare方法,其內部調用prepare的重載方法并傳入的參數值為true;而主線程是調用prepareMainLooper方法,其內部也是調用prepare的重載方法并傳入的參數值為false。這里也和MessageQueue中的quit方法中的邏輯相吻合,當mQuitAllowed為false的時候,程序會崩潰并輸出異常信息如下:

Main thread not allowed to quit. 
重點--loop方法

在簡單介紹下Looper在使用時的一些注意事項后,我們來看一下它重要的一個方法loop,方法的源碼如下:

/**
 * 此方法中,這里只貼出關鍵代碼,我們只需關心Looper是如何把消息發給Handler的就可以了
 */
public static void loop() {
    // 首先檢測Looper是否為空,如果Looper為空,會導致程序崩潰
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 創建queue變量指向當前的消息隊列
    final MessageQueue queue = me.mQueue;
    ........
    for (;;) { // 進入死循環遍歷消息隊列中的消息
        Message msg = queue.next(); // might block 這里的next方法已經講過
        if (msg == null) { // 如果msg為空,就終止循環退出當前loop方法
            // No message indicates that the message queue is quitting.
            return;
        }
        ........
        try {
            // 走到這里說明msg你不為空,那么就調用msg的target變量(綁定的Handler)的dispatchMessage方法
            msg.target.dispatchMessage(msg);  // 這樣消息現在就已經發給了Handler了
            ........
        } catch (Exception exception) {
            ........
        } finally {
            ........
        }
        ........
        msg.recycleUnchecked(); // 消息發送后,重置消息對象,清空之前的數據
    }
}

這里貼出來的代碼其實也就是源碼中的差不多十分之一吧,不過關鍵的步驟就這些。loop方法的流程也可以概括為三步,第一步,檢測Looper對象是否已經創建;第二步,在循環中通過調用MessageQueue的next方法從消息隊列中遍歷出消息;第三步,調用Handler的dispatchMessage方法將消息發送到Handler的手中然后將消息重置。
在方法的內部有一處注釋(msg==null條件語句里),大致意思為“沒有消息代表消息隊列正在退出”。前面我們通過閱讀MessageQueue的next方法,我們可以知道要想next方法返回null,只有一個條件,那就是MessageQueue中的mQuitting變量為true時(還有一種情況,就是MessageQueue中的mPtr變量值為0時,而mPtr為0的情況有兩種,一種就是mQuitting變為true以后會執行dispose方法將它置為0,另一種就是dispose方法在程序終止時被調用)。所以這里要強調的還是如果在一個線程中不再使用Looper了,記得調用它的quit或者quitSafely方法,這樣loop方法才能結束的。
另外一定要記得,如果在子線程中創建Handler,除了要手動調用Looper的prepare方法之外,還要調用loop方法來開啟消息循環,否則消息是無法發送到Handler的(主線程中也默認調用了這個方法)。

4.4.Handler

終于到了關鍵角色Handler了,前面也簡單描述過它的職責,在閱讀完前面的內容后,現在我們應該更進一步的了解它的職責了。它就是負責把消息發送到隊列,然后處理從looper中發送給它的消息。為了更全面的理解它的職責,下面就來看一下它的源碼。

4.4.1.構造方法

Handler有7個構造方法,這里我們只列出兩個,其中一個是它的無參構造(我們平時最常用的),另一個就是這個無參構造內部調用的接收兩個參數的構造方法,代碼如下:

/**
 * 默認的無參構造,內部調用下面的兩個參數的構造方法
 */
public Handler() {
    this(null, false);
}
/**
 * 第一個參數是一個定義在Handler內部的接口;
 * 當我們在實例話一個Handler的時候,為了避免去實現一個Handler的子類,我們可以選擇傳入一個Callback接口;
 * 日常開發中我想大多數人都是直接采用匿名內部類的方法初始化一個Handler,因此這個也可以不傳。
 * 第二個參數會賦值給Handler中的mAsynchronous變量,如果為true的話那么Handler中所有的消息都會被設置成異步消息
 */
public Handler(@Nullable Callback callback, boolean async) {
    // 這里警告如果一個繼承Handler的類在使用時未被定義成靜態變量,會有造成內存泄漏的危險
    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(); // 將mLooper指向當前線程的Looper實例
    if (mLooper == null) { // 這里就是為什么創建Handler之前必須創建Looper的原因了,因為你不創建會拋異常
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue; // 將mQueue指向當前線程的消息隊列
    mCallback = callback; 
    mAsynchronous = async;
}

在構造方法中,就是檢驗當前線程是否具備開啟Handler的條件,如果具備的話,就將Handler和當前線程的Looper以及MessageQueue進行關聯綁定。

4.4.2.enqueueMessage方法

這個方法的名字是不是很熟悉?沒錯,前面講過的MessageQueue中也有這么一個同名的方法,而其實Handler中的這個方法最終內部就是調用了MessageQueue中的enqueueMessage方法來將消息插入隊列的。我們先來看一下這個方法的源碼:

/**
 * 第一個參數就是當前線程的消息隊列
 * 第二個參數就是即將安排入隊的消息
 * 第三個參數最終會設置成第二個參數msg的when變量的值(前面在講MessageQueue中的enqueueMessage方法時說過)
 */
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
    msg.target = this; // 將當前的Handler設置成msg的target變量
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    if (mAsynchronous) { // 這個變量在構造方法中被賦值
        msg.setAsynchronous(true); // 如果為true,那么入隊的消息將全部變成異步消息
    }
    return queue.enqueueMessage(msg, uptimeMillis); // 調用真正的入隊方法
}

之所以說到這個方法,是因為其實我們在使用Handler發送消息到隊列時,無論我們調用的是Handler的sendMessage方法、sendMessageDelayed方法或者是post方法,最終都會調用到這個enqueueMessage方法。這里我打算貼出幾個我們經常會用到的發送消息的方法的源碼,但是不做講解,因為一看就明白:

/**
 * 以下這三個方法,我們可以在外部直接調用,從上而下方法依次被調用,最終會調用enqueueMessage方法;
 * 它們的區別就是最終消息被處理的時間可能會有所不同
 */
// 消息發出就處理
public final boolean sendMessage(@NonNull Message msg) { 
    return sendMessageDelayed(msg, 0);
}
// 設置一段延時再處理消息
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { 
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 設置指定時間去處理消息
public boolean sendMessageAtTime(@NonNull 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); 
}

以上三個方法的區別就是最終設置給msg的when變量的值可能會不同。

4.4.3. dispatchMessage方法

這個方法是Handler分發消息去處理的方法,在前面講Looper的loop方法中提到過。當消息被傳入到此方法后,方法內部會根據消息對象的屬性以及Handler的屬性來決定如何分發這個消息。方法的源碼如下:

public void dispatchMessage(@NonNull Message msg) {
    // 如果msg的callback不為空,那么就調用handleCallback方法
    if (msg.callback != null) {
        handleCallback(msg);
    } else { // 否則判斷mCallback是否為空
        if (mCallback != null) { // 如果不為空就回調這個接口的handleMessage方法
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg); // 執行handleMessage方法
    }
}
/**
 * 就是調用Message的callback變量的run方法;
 * 這個allback是Message中的一個Runnable類型的成員變量。
 */
private static void handleCallback(Message message) {
    message.callback.run();
}
/**
 * 此方法為一個空方法,源碼中注釋部分告訴我們Handler的子類必須實現此方法;
 * 而我們通常采用匿名內部類的方式直接重寫此方法,在方法中是如何處理消息的邏輯。
 */
public void handleMessage(@NonNull Message msg) {
}

其實到這里,關于Handler的整個消息機制就差不多講完了。不過,關于Handler還有一個方法這里要重點說一下。在閱讀它的源碼之前,我一直以為這個方法是新開了一個線程去執行事件,而閱讀后才發現根本不是的。這個方法就是post方法。

4.4.4.post方法

這里我先修改一下文章開頭寫的demo中的create方法中的代碼(別的代碼不變),在子線程中調用handler的post方法,然后在Runnable的run方法中操作UI。代碼如下:

public void create() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    // 操作UI
                }
            });
        }
    }).start();
}

如果方法一旦被執行,那么會導致程序崩潰嗎?答案是不會的。而又是為什么呢?難道是在post方法中new出了一個新的主線程?腦洞確實挺大,其實post方法并沒有創建任何的新線程,千萬不要被Runnable迷惑了雙眼。我們來看一下post方法的源碼:

/**
 * 方法內部調用了sendMessageDelayed方法,sendMessageDelayed方法不再贅述;
 * 主要看一下該方法的第一個參數的getPostMessage方法(下面)
 */
public final boolean post(@NonNull Runnable r) {
   return sendMessageDelayed(getPostMessage(r), 0);
}
/**
 * 方法內部創建一個新的Message對象,并將post方法傳進來的Runnable對象設置成Message的callback變量
 */
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

看完了這個方法的源碼是不是恍然大悟了,原來其本質也是發送一條消息給消息隊列,最終再由Looper發送給Handler,只不過這個方法發送的消息的callback屬性不為空。而在前面講dispatchMessage方法時說過,如果當前分發的消息對象的callback不為空,那么會執行到handleCallback方法中。而handleCallback方法內部其實就是調用了消息對象的callback的run方法,所以run方法是運行在Handler所創建的線程中的,本demo中Handler是在主線程中創建的。

5.重點回顧

1.任何一個線程若想啟動一個Handler,必須先創建Looper對象(主線程中系統默認創建)。
2.只有調用了Looper的loop方法后,線程的消息循環才能開啟。(主線程中系統默認調用)。
3.當一個線程中不再處理任何消息時,記得調用Looper的quit或quteSafely方法退出Looper。
4.一個Message對象一旦被Handler發送給消息隊列,那么這個消息對象就已經在Handler所處的線程中。

總結

終終終終終終終終終終于寫完了這篇文章,也是完成了一次挑戰。Android的Handler機制還是挺巧妙的,仔細閱讀相信一定可以理解其中的原理的。如果文章對您有幫助,還希望點個贊;有寫的不對或者不好的地方,還望提出指正,定虛心接受。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容