Handler源碼探析

前言

一句話總結 Handler ,它是Android消息機制中一個輔助工具,用於子線程與主線程進行通訊。git

本文的源碼將基於Android10,SdkVersion-29面試

1、Handler具體能作什麼

1.1 看下源碼的註釋

  • A Handler allows you to send and process {@link Message} and Runnable
  • objects associated with a thread's {@link MessageQueue}.

Handler讓你能夠發送和處理與某個線程的MessageQueue(消息隊列)相關聯的Message(消息)和Runnable對象app

  • Each Handler instance is associated with a single thread and that thread's message queue.

每個Handler實例都關聯一個線程和這個線程的MessageQueue(消息隊列)異步

  • When you create a new Handler, it is bound to the thread message queue of the thread that is creating it
  • -- from that point on, it will deliver messages and runnables to that message queue and execute
  • them as they come out of the message queue.

當你建立了一個新的Handler時,它就綁定到建立它的線程的MessageQueue(消息隊列),從那時起,Handler將Message和Runnable對象傳遞到消息隊列,並在它們從隊列中返回的時候處理async

  • There are two main uses for a Handler:ide

  • (1) to schedule messages and runnables to be executed at some point in the future;函數

    安排Message和Runnable在未來某個時間執行工具

  • (2) to enqueue an action to be performed on a different thread than your own.oop

  • 在不一樣的線程中執行操做post

1.2 Handler原理解析

源碼的註釋中寫的很明白 Handler是綁定了建立它的線程之後進行處理Messager和Runnable對象的。

首先看Handler構造函數

在Handler構造函數中,能夠看到上面註釋提到的,建立一個新Handler實例都會關聯一個線程和這個線程持有的 MessageQueue,經過 Looper.myLooper()獲取當前線程的Looper,拿到Looper以後將Handler中的mQueue賦值

接着寫個調用Handler發消息的例子

開啓一個新線程調用Handler發送消息的函數,以 sendMessage(Message msg)函數爲例,追蹤一下這個函數裏都作了什麼(除了圖中的函數, post(Runnable r)也會經過 getPostMessage(Runnable r)把Runnable對象添加到Message對象後調用send函數)。

調用順序:sendMessage()->sendMessageDelayed()->sendMessageAtTime()->enqueueMessage() 這裏重點看一下後面2個函數都作了什麼

sendMessageAtTime()這個函數中拿到了實例Handler時的MessageQueue對象,上文提到過的 mQueue,而後調用 enqueueMessage()函數。在這個函數中首先給msg的target變量賦值爲當前的Handler對象,接着調用MessageQueue的enqueueMessag()將消息插入到消息隊列。

2、MessageQueue

2.1 消息入隊

部分源碼

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) {
            // New head, wake up the event queue if blocked.
            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;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        //調用nativeWake,以觸發nativePollOnce函數結束等待
        if (needWake) {
            nativeWake(mPtr);
        }
    }
複製代碼

}

消息入列就是根據Message的when屬性的大小進行排序,先執行的放在隊列的前面或者遍歷消息隊列,把當前進入的消息放入合適的位置。

2.2 消息出隊

部分源碼

```

 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.
    //若是退出消息消息循環,那麼就直接退出
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
         //執行native層消息機制層,
        //nextPollTimeoutMillis參數爲超時等待時間。若是爲-1,則表示無限等待,直到有事件發生爲止。
        //若是值爲0,則無需等待當即返回。該函數可能會阻塞
        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) {
                // 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;
            }

            // 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();
            }
            //若是空閒的Ddel handler個數爲0,繼續讓線程阻塞
            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.
        將空閒處理程序計數重置爲0,再也不運行。
        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.
        喚醒Native消息機制層
        nextPollTimeoutMillis = 0;
    }
}
複製代碼

到這裏能夠看到 MessageQueue是消息的管理者,提供了入隊和出隊的方法,具體的調用是在 Looper進行的。
ps:說實話這部分源碼看的懵懵的,這塊須要去深刻了解一下Native消息機制

3、Looper

3.1 Looper是如何建立的

以前看Handler構造函數的時候,能夠發如今實例化的時候,會判斷當前線程mLooper是否存在,也就是說建立一個Handler時也要建立一個Looper。

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

經過這個函數得到當前線程的Looper,在主線程中系統已經幫咱們建立好了。

ActivityThread就是Android主線程或UI線程,在ActivityThread的 main()函數中Looper會經過Looper.prepareMainLooper()函數建立sMainLooper

Looper建立完成以後,在main()中會調用 Looper.loop()構建消息循環。

子線程中建立Handler

3.2 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));//建立Looper對象,放入ThreadLocal中。這裏的boolean參數詳見3.3 }


ThreadLocal的做用
源碼註釋

  • This class provides thread-local variables. These variables differ from
  • their normal counterparts in that each thread that accesses one (via its
  • {@code get} or {@code set} method) has its own, independently initialized
  • copy of the variable. {@code ThreadLocal} instances are typically private
  • static fields in classes that wish to associate state with a thread (e.g.,
  • a user ID or Transaction ID).

ThreadLocal提供線程局部變量。這些變量與正常變量不一樣,每個線程訪問自身的局部變量時,有它本身的,獨立初始化的變量副本。ThreadLocal變量一般是與線程關聯的私有靜態字段(例如user ID或Transaction ID)。

回過頭來看一下sThreadLocal.set(new Looper(quitAllowed))set(T valye)函數

獲取到了當前線程,而後獲取當前線程的ThreadLocalMap,若是map不爲空,直接調用map.set賦值,不然新建一個ThreadLocalMap。這裏就不繼續向下看了。

總結一下,Looper是經過ThreadLocal來關聯主線程的,而且經過ThreadLocal儲存保證其線程的私有性。其實不光是主線程,每一個線程的Looper都是這樣關聯的。

3.3 Looper和MessageQueue怎麼關聯的,循環能夠退出嗎?

Looper既然能夠開始構建消息循環,那麼必然提供了退出的函數。
首先看下Looper的構造函數

在構造函數的參數能看到,在Looper內部建立了一個MessageQueue對象與之關聯,還有一個很明顯的參數 quitAllowed,是否容許退出。這時須要去看下這個boolean變量在MessageQueue是怎麼用到的

這裏發現退出循環的方法也是MessageQueue給Looper提供的,在Looper中調用
public void quit() { mQueue.quit(false); }停止循環。
注意: 3.2提到的boolean參數,在主線程中建立Looper時, prepare(false)傳入的是flase,主線程是不容許Looper退出的。緣由也很好理解,退出就意味着App掛了。子線程建立Looper默認傳入是true

3.4 Looper的消息循環

/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */

public static void loop() {
    final Looper me = myLooper();//獲取當前線程的Looper
    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 調用上文提到的MessageQueue提供的next()方法,
        此方法可能會阻塞
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
      省略部分代碼
      long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
        try {
        //調用Handler的dispatchMessage函數分發消息(Message對象)
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
          省略部分代碼
        }

        msg.recycleUnchecked();
    }
}

 總結一下就是無條件循環獲取MessageQueue中的消息,而後經過msg.target(爲Handler對象)調用
 dispatchMessage函數進行消息的分發。最終又回到了Handler處理消息
複製代碼

Handler.dispatchMessage(msg)

這裏引用一張圖流程圖吧

最開始寫了個例子發送了消息,如今須要重寫handerMessage函數處理消息

Handler Looper Message MessageQueue 關係圖解

Handler經過sendMessage()發送Message到MessageQueue隊列; Looper經過loop(),不斷提取出達到觸發條件的Message,並將Message交給target來處理; 通過dispatchMessage()後,交回給Handler的handleMessage()來進行相應地處理。 將Message加入MessageQueue時,處往管道寫入字符,能夠會喚醒loop線程;若是MessageQueue中沒有Message,並處於Idle狀態,則會執行IdelHandler接口中的方法,每每用於作一些清理性地工做。

四.相關知識點總結

1.Handler持有MessageQueue對象的引用,經過enqueueMessage函數將消息入隊,同時也持有Looper對象的引用,經過Looper.loop()構建消息循環。Looper持有MessageQueue的對象引用,調用MessageQueue的next()無限取出消息交給Handler的dispatchMessage()函數處理
2.Looper能夠調用MessageQueue提供的quit(boolean safe)函數退出,可是主線程的 Looper 不容許退出。
3.子線程用Handler,必須調用Looper.prepare() 函數而且調用Looper.loop()或者在實例化的時候傳入Looper.getMainLooper()。
4.ThreadLocal每一個線程有單獨一份存儲空間,它具備線程隔離的效果,只有在線程內才能獲取到對應的值,線程外則不能訪問到想要的值。

總結這篇也是由於最近在準備面試,但願能從新梳理一下Android的消息機制。
Handler面試總結

感謝

GityuanAndroid消息機制1-Handler(Java層)
AndyJenniferAndroid Handler機制
秉心說深刻理解 Handler 消息機制

相關文章
相關標籤/搜索