Android Handler源碼分析

前言

做爲Android開發,Handler想必你們都不陌生了,並且是比較基本的知識,在面試中通常也會說起到,並且很容易使用,可是若是錯誤使用也會形成內存泄漏的問題,這裏暫時不作解析,如不知道請自行翻閱handler內存泄漏的文章。言歸正傳,做爲Android開發要想進一步提高本身,知道原理也是理所應當的,相似的文章也不少,想必你們都看過,寫這篇文文章目的也是回顧下原理,同時也但願能幫助到有須要的人,若有錯誤,歡迎指正。android

1、Handler

做爲一名andorid開發人員,若是提到消息的都會知道handler,或者使用evenbus,不過它的底層也是基於handler實現的,要想知道它的原理就首先要知道它的做用,這樣更有助於理解源碼,咱們都知道它的做用是要將其餘線程要進行的UI操做必須切換到主線程下進行UI操做,由於在android早期的版本中規定執行UI操做只能在ui線程,若是不這樣作,就能夠在不一樣線程調用對UI進行操做,可能會出現併發問題,由於UI線程是屬於線程不安全的,並且調用狀況比較複雜,在併發下調用ui更新操做會出現不少不可想像的問題。面試

繼續迴歸咱們的消息機制,Handler消息機制並非獨立完成的,而是和MessageQueue,Looer,三者共同完成整個消息機制的,裏邊還會涉及到ThreadLocal、message的相關知識,它們也是相當重要的。而咱們平時在UI線程中使用handler並無發現使用這兩個方法,而實際上在ActivityThread 建立的最初時,zygote孵化出子進程以後在main方法裏進行的初始化Looper.prepareMainLooper()的操做而且在ActivityThread 建立了Handler 而這個Handler就負責四大組件的啓動和結束的消息傳遞等。數組

2、Handler ,MessagerQueue,Looer 及存儲類ThreadLocal的詳解。

1.ThreadLocal

ThreadLocal 主要做用在不一樣線程中會存在不一樣副本也就是說不一樣線程的間的數據存儲子在這裏是互不影響的,咱們在作多線程的業務是也不妨使用它,它會簡化不少代碼並且避免不少沒必要要麻煩。在Looper、ActivityThread、AMS中都有使用。接下來就說下它的好處,第一種就是當Handler要獲取當前線程的Looper時,那麼存在ThreadLocal中獲取就很方便取,若是不這樣作可能須要作一個全局的HashMap用來存儲looper用來存儲並查詢,若是存在多個Looer而且多個線程裏,或許得添加一個Looper的管理類來管理這麼多的looper,這樣很麻煩寫代碼誰不想寫簡單點,第二種狀況,在複雜的邏輯下的傳遞對象無疑是個痛苦的決定,例如寫一個監聽器他是全局的,那麼若是不用Thread就會須要一個全局靜態變量,若是多個線程須要,那麼須要建立多個靜態變量,這樣顯然不如Threadlocal來的方便。ThreadLocal內部實現原理:當在使用時會根據線程不一樣而生成不一樣的數組,再根據索引找到值,這正是在不一樣線程中有能夠獲取不一樣數據的根本緣由。
接下來咱們看下TheadLocal的實現, 它是一個泛型類能夠存多種不一樣數據,首先咱們開看他的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);
    }

複製代碼

根據當前線程來獲取一個ThreadLocaMap,(ThreadLocaMap也是個至關於map,內部有個Entry即是存儲ThreadLocal的) 若是不爲空則進行存貯,爲空那麼新建。bash

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
} 
複製代碼

能夠看出新建也是在當前線程下進行的。 接下來看下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();
}
複製代碼

一樣也是根據當前線程獲取存儲的數組map,若是發現爲空那麼就從新初始化。多線程

源碼以下:併發

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
複製代碼

接下來進入ThreadLocalMap的map.set()方法:async

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

存儲在一個Entry的數組裏邊,如何存在這個值就替換掉,如不存在須要往數組裏新增。ide

2.MessageQueue 原理解析

MessageQueue的原理最主要有兩個方法,即插入和讀取。enqueueMessage()的做用是向隊列中插入一條消息,而next()則是在隊列中讀取一條數據而且刪除這條消息。MessageQueue根據表面意思是消息隊列,然而實現卻不是隊列形式,經過觀察內部源碼看到它,

msg.next = p; // invariant: p == prev.next
    prev.next = msg;
複製代碼

是採用單鏈表數據結構來維護消息列表的。 先插入一段Hhandler的源碼,下面的msg.tatarget=this表明的是handler對象後邊會用到。

源碼以下:

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

而後繼續看最後調用的MessageQueue的enqueueMessage方法:

boolean enqueueMessage(Message msg, long when) {
...
        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) {
                // 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.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

複製代碼

上面就是根據鏈表插入最新數據,而後跳出循環,接下來再看下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;
            }

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

上面是一個死循環當有消息時,next()方法跳出這個會返回消息並清除這個消息,若是沒有消息那麼這個next()方法會阻塞到這裏。

3.Looper 的工做原理

Looper的功能就是循環消息取出,當有消息來到時就當即處理它,沒有消息就一直在那阻塞着,由於MessageQueue的next()方法也在阻塞在哪裏,後邊代碼會講到緣由。 那麼咱們先看下Looper構造方法:

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

我能夠看到在它的構造法中建立了消息隊列對象MessageQueue和存儲當前線程mThread。而咱們都知道在子線程中,若是使用Handler發送消息,由於子線程沒有Looper對象因此會在發送消息時候是拋出異常。 Looper 提供了兩個建立消息循環的方法即Looper.prepare()和Looper.PreparMainLooper() 源碼以下:

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

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
複製代碼

Looper.prepare()由源碼能夠知道在當前線程建立新的looper而且存儲到threadlocal中。 Looper.PreparMainLooper()由源碼可知初始化當前線程的looper並標記爲主線程Looper。 Looper 還提供了一個方法getMainLooper 用來獲取主線程Looper,Looer 還提供了兩個退出looper方法分別爲 quit(safe:false),quitSafely(safe:ture),來看MessageQueue的quit()方法以下:

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

quit方法執行完當即離開,而quitSafely爲標記安全退出,當前消息隊列處理完畢後,安全退出,爲了減小篇幅,後面代碼就不貼了。 當在子線程建立消息循環時候,若是looper正在退出時,這時handler發送消息就會返回false,查閱MessageQueue的enqueueMessage可知道這裏再也不說,這裏若是沒有消息而且還不調用Looper的退出方法,那麼Looper.loop()則會一直阻塞在當前線程裏,也就是說這個線程不會被關閉,因此建議在子線程處理完全部消息完畢後最好關掉消息循環,用來保證線程的正常退出,從而減小性能問題,或者偶爾會出現內存泄漏等問題,上面的結論在下面源碼中便可找到答案。

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;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        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);
            }
        }
    
        msg.recycleUnchecked();
    }
}
複製代碼

從源碼能夠看出loop()是一個死循環函數只有當msg=null時候才跳出循環,只有當looper.quit被調用時,messagequeue的quit()纔會被調用,看下源碼他是調用的尾部函數,中間函數不貼了。

private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }
複製代碼

這時 mMessages才置爲空這時looper()纔會跳出循環而且退出,不然會一直走循環這裏。方法內部Looper方法會調用messagequueue的next方法,若是沒有新消息那麼會一直阻塞 在那裏,若是有消息到來,looper就會處理這條消息。

msg.target.dispatchMessage(msg);      
複製代碼

這個msg.target也就是發送消息的handler(前邊說過這個msg.target)對象,那麼它會調用dispatchMessage(msg)這個方法是在建立handler的looper線程中調用的,這樣也就切換到了handler所在線程來處理事件了。

4.Handler的原理

最後來看下handler的做用,它主要用於消息的發送和處理,源碼以下:

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

看源碼handler 發送消息僅僅將消息插入一個隊列中,由於Looper一直在取消息,一旦有消息,而後調用messagerQueue.next()就會返回消息給looper()方法並,且looper()就會處理這條消息而且交由msg.target的dispcathMessage來處理。

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

處理函數中有兩個回調方法,若是msg.callback不爲空,那麼就調用handlercallback,msg.callback是一個Runable對象,也就是當調用handler的post方法發送消息實際上也就是 調用Runable的run方法,而後在一個就檢查mCallback(它是接口被handler實現的)方法不爲空調用 handler的handlerMessage(Messagemsg),若是mCallback也爲空那麼執行handler的handleMessage(msg)方法,最後說明一點Callback的建立用途,這點很是好理解,可用過 Handler handler=new Handler(Callback)這個建立,Callback這樣寫的意義,就是能夠再不派生子類的狀況下在構造函數下直接經過new Callbcak來重寫handlermessage()方法實現的,這也是簡化可代碼的很好方式。

最後咱們來看下爲何不能再子線程不建立looer的狀況下使用handler跑出異常的緣由了。

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()");
    }
 ...
}
複製代碼

Handler上邊的構造方式若是當前線程沒有Looper對象的話就會拋出"Can't create handler inside thread that has not called Looper.prepare()"),這就解釋了線程沒建立handler拋出異常的緣由了,而主線程在不會拋異常(開篇已經解釋緣由了)。

總結

本文主要講解了Handler基本原理,是由Handler、Messagequeue、Looper及ThredLocal這個線程存儲利器,還有他們之間的調用關係,最後但願你們能夠一塊兒保持學習精神,不管是哪一個行業,只要不斷學習才能讓你不斷進步。

相關文章
相關標籤/搜索