手把手帶你解析Handler原始碼

陳呆賊發表於2019-04-01

Handler概述

關於Handler的使用無非就是線上程1中傳送一個message,執行緒2接收到到這個訊息便會線上程2裡執行任務程式碼。而我們使用Handler最主要的目的就是在子執行緒中更新主執行緒的UI。由於AndroidUI是單執行緒模型,所以只有在主執行緒中才能夠去更新UI,否則系統就會崩潰丟擲異常。對於Handler的使用我這裡就不在重複了,本次分析所使用的是Android8.1的原始碼,和舊版原始碼略有不同,但大致思想是一樣的,接下里就直接進入主題吧。

Handler原始碼分析

首先從Handler的構造方法開始

public Handler() {
        this(null, false);
    }
複製程式碼

呼叫了兩個引數的構造方法。

public Handler(Callback callback, boolean async) {
        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();//此處通過Looper.myLooper()獲取一個Looper
        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.myLooper()去獲取一個Looper,接著判斷如果Looper為null的話,就丟擲一個異常:

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

該異常告訴我們沒有呼叫Looper.prepare(),所以我們在建立Handler之前必須呼叫該方法。 接著為Handler的成員變數mQueue賦值,所賦的值就是我們從獲取的Looper中取出來的。 接著我們跳轉進Looper.myLooper()看看究竟是如何獲取Looper的。

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
複製程式碼

通過一個ThreadLocal的物件去獲取一個Looper,那這ThreadLocal是什麼呢?

public class ThreadLocal<T> 
複製程式碼

ThreadLocal是一個泛型類,可以儲存每個執行緒獨有的變數。比如你線上程1中通過ThreadLocal物件的set方法儲存了一個String變數“abc”,線上程2中儲存一個String變數“def”,那麼在這兩個執行緒中通過ThreadLocal物件的get方法獲取該變數時會因為在不同的執行緒中獲取不同的值。線上程1中就會得到“abc”,執行緒2中就會得到“def”。 由此可見,通過ThreadLocal去獲取的Looper也是執行緒獨有的,不同的執行緒擁有不同的Looper。 接著我們看看ThreadLocal的set方法。

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//獲取ThreadLocalMap
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
複製程式碼

首先通過Thread.currentThread()獲取當前執行緒,接著把當前執行緒傳入getMap方法來獲取一個當前執行緒的ThreadLocalMap物件。

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
複製程式碼

getMap方法相當簡潔,就是通過返回某執行緒Thread上的成員變數threadLocals來獲取ThreadLocalMap的。也就是說每個Thread內部都持有一個ThreadLocalMap物件用來存放執行緒間獨立的變數。 而ThreadLocalMap其實就是ThreadLocal的一個靜態內部類。在ThreadLocalMap的內部有一個陣列。

private Entry[] table;
複製程式碼
static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

複製程式碼

一個弱引用物件的陣列。每個Entry物件持有一個ThreadLocal的弱引用,而值存放在value欄位上。所以ThreadLocalMap存放了當前執行緒的獨立變數,他們全都放在table欄位中。

接著上面的 程式碼,如果map不為null的話就把值設定在這個ThreadLocalMap物件裡 我們來看看ThreadLocalMap的set方法。

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

            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();
        }
複製程式碼

根據ThreadLocal的hash值和陣列的長度做與運算得到的下標來作為value存放的位置。 如果下標所處的位置不為空,那說明當前下標不可以存放value,那就呼叫nextIndex來取得下一個下標,如果下一個下標所處的位置是null,那麼就可以把value存放在當前下標位置。大致邏輯就是這樣的。 接下來我們再看看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();
    }
複製程式碼

首先通過getMap獲取到ThreadLocalMap,接著通過ThreadLocalMap.getEntry這個方法獲取到存放在ThreadLocal當中的值。 到此我們可以做一下總結:

在每個Thread的內部都有一個ThreadLocalMap,這個ThreadLocalMap裡有一個名為table的Entry陣列,所以ThreadLocal裡存放的變數才獨立於每個執行緒。我們往ThreadLocal裡存放的物件都是存放進這個Entry陣列的(通過hash計算來決定下標值)。 所以把Looper存放在ThreadLocal當中就可以保證每個執行緒的Looper是獨立存在的。

當Handler獲取了Looper以後就可以正常工作了。我們使用Handler時一般會呼叫Handler的sendMessage方法

public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
}
複製程式碼

我們跳進sendMessageDelayed,發現最終會呼叫

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);
    }
複製程式碼

這裡的mQueue就是我們構造方法裡Looper的mQueue,所以Looper是必須的。 看看最後一行enqueueMessage。

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

在這裡msg.targe指向了this,也就是我們當前的Handler。 發現最後是呼叫的MessageQueue的enqueueMessage,我們繼續跟蹤。

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            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.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;
            }
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製程式碼

說是訊息佇列,其實MessageQueue是個連結串列,因為對於連結串列來說插入和刪除操作的時間複雜度是O(1),所以非常適合用來做訊息佇列,在enqueueMessage方法裡把新訊息插入隊尾,也就是下面這兩行程式碼。

msg.next = p; 
prev.next = msg;
複製程式碼

我們通過sendMessage方法把一個Message放進訊息佇列中,那麼是誰來處理訊息佇列中的訊息呢。那就需要Looper登場了,前面說過一個執行緒如果需要建立一個Handler那麼就必須要有一個Looper。我們來看看Looper的構造方法。

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
複製程式碼

在Looper的構造方法裡會新建一個MessageQueue,所以這個MessageQueue和Looper是繫結了的,同時會獲取建立Looper的那個執行緒。要使用Looper還必須要呼叫Looper.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;
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // 從訊息佇列中獲取訊息
            if (msg == null) {
                return;
            }

            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            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);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
            msg.recycleUnchecked();
        }
    }
複製程式碼

呼叫loop方法後會啟動一個無限迴圈,每次迴圈都會呼叫如下方法。從訊息佇列中獲取一個Message,如果訊息佇列中沒有訊息可以取了,該方法可能會阻塞。

Message msg = queue.next();
複製程式碼

接著會呼叫

msg.target.dispatchMessage(msg);
複製程式碼

這個target就是之前enqueueMessage時設定的handler,通過呼叫handler的dispatchMessage執行我們的任務。

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

如果建立Message時傳入了callback那就呼叫handleCallback執行任務,否則檢視mCallback是否可以呼叫,可以則呼叫。mCallback是繼承Handler時可選的傳入引數。

到此一個完整的Handler機制原理就講解完畢了。

Handler流程圖.png

前面說過一個執行緒中必須要有一個Looper才能使用Handler,否則就會奔潰。那為什麼我們在主執行緒中可以使用Handler呢?這是因為主執行緒ActivityThread已經幫我們做好了Looper的初始化工作。

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

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

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
複製程式碼

使用了Looper.prepareMainLooper();來初始化Looper,並使用了Looper.loop()開啟了訊息迴圈佇列。 還有一點需要注意的是,在使用Handler的時候,如果訊息都處理完畢了,應該呼叫Loop.quit或者Looper.quitSafely方法來停止輪詢。否則由於Looper會不斷的輪詢MessageQueue導致該Looper所在的執行緒無法被釋放。

總結

Handler在近幾年來已經算是非常基礎的知識了,在面試當中也是高頻題,關於Handler的使用其實是很容易造成記憶體洩漏的,這裡就不在多說了,有興趣的朋友可以搜尋一下Handler記憶體洩漏相關的知識。

相關文章