記一次討論總結,幫助你和Handler完全作一次告終

最近在羣裏的幾個好友一塊兒討論技術的時候說到了Handler,下面是對此次討論的總結,此次討論主要是對handler.post(Runnable r)這個方法來進行展開的,不論是handler.post(Runnable r)仍是handler.sendMessage(msg);其內部實現都是同樣的,就不重複分析了。不過相信通過此次總結你能對Handler機制來一次完全的告終。bash

一:handler.post(Runnable r)開啓了一個子線程(錯誤

這是一個錯誤的觀點,其實一部分人出現這個觀點的緣由是由於將Runnable和Thread的概念搞混淆了。Runnable只是開啓線程的一種方式,並非開啓了一個線程。爲何說這個觀點是錯誤的,下面代碼結果打印一下就知道了。less

Handler handler = new Handler();
Log.e("======>>>>>>>", "Thread: " + Thread.currentThread().getName());
handler.post(new Runnable() {
    @Override
    public void run() {
    Log.e("------>>>>>>>", "Thread: " + Thread.currentThread().getName());
    }
});
複製代碼

結果以下:不論是在裏面仍是在外面都是顯示主線程,因此handler.post(Runnable r)並無開啓一個線程 async

爲何會這樣呢?那就只能去源碼裏面找緣由了,咱們點擊post()這個方法進去

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}
複製代碼

這裏很容易理解就是發送一個延時的消息。繼續點進去ide

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

咦,sendMessageDelayed(Message msg, long delayMillis)的第一個參數是Message,那這個Message是哪裏的來的,咱們返回去看,有個getPostMessage(r)的方法,看看裏面幹了啥?oop

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
複製代碼

很簡單的代碼,就是生成了一個Message對象並將Runnable這個對象賦值給m.callback並將生成的這個Message對象返回去。回去繼續看sendMessageDelayed()。點sendMessageAtTime()方法進去看看post

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

這裏獲取了一個消息隊列,經過enqueueMessage()方法名能夠猜測應該是將Message這個對象加入隊列中。繼續點進去ui

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

這裏須要注意一下msg.target = this;這句代碼。繼續往下看,如今看看MessageQueue裏面的enqueueMessage()這個方法幹了什麼。this

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) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we do not 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();
            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;
}
複製代碼

參數msg是由咱們傳進去的Runnable轉換成的對象,when時是執行時間,當咱們調handler.post 時該值爲0,細心的朋友會發現mMessages這個對象,咱們能夠把它理解爲是待執行的message隊列,該隊列是按照when的時間排序的且第一個消息是最早執行。代碼中有一個if中有三個條件:若是mMessages對象爲空,或者when==0,或者新消息的when時間比mMessages隊列中的when時間還要早,符合以上一個條件就把新的msg插到mMessages的前面並把next指向它,等待loop的輪詢。不然經過一個for的死循環遍歷已有的message對象,其中if語句when < p.when,when是新消息的執行時間,p.when的是隊列中message消息的執行時間,若是找到比新的message還要晚執行的消息,就執行 msg.next = p;prev.next = msg;也就是插到該消息的前面,優先執行新的消息。這裏不難看出就是將消息加入到消息隊列中。spa

上面說到將消息加入到消息隊列中,那這個Handler的mQueue是哪裏來的呢?經過查找不難發現mQueue只有兩處被賦值,且都是被looper.mQueue賦值。因此Handler中的mQueue就是Looper的mQueue。再去Looper找,mQueue是在Looper的構造方法中被初始化線程

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

經過這個構造方法咱們能夠知道,一個Looper對應一個MessageQueue

既然存消息了,那取消息在哪裏呢?咱們稍微想一下就知道了,要拿到MessageQueue消息隊列中的Message對象確定會有個方法,並且這方法確定是返回Message對象,查看一下MessageQueue裏面的全部方法能夠找到一個next()的方法。點一下,咦,沒反應咋辦。別急,想想Handler機制就這個幾個類,確定逃不過這幾個類,就去這幾個類搜就就好了,發現只有Looper在調用這個方法。

for (;;) {
    Message msg = queue.next();
    ···
    msg.target.dispatchMessage(msg);
    ···
}
複製代碼

這是Looper.loop()在調用。代碼較多就不粘出來了,在源碼裏咱們看到這麼兩行,經過方法名能夠知道分發消息的意思。那msg.target是啥呢?感受像看到過呢?返回去找一下不難發如今Handler的enqueueMessage()中就有msg.target = this;這一行。那dispatchMessage()是幹啥的呢?去Handler類中找這個方法。

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

一眼就能明白的代碼,就是經過msg.callback爲不爲空來判斷,經過上面分析咱們知道msg.callback是不爲空的。啥?你是怎麼知道msg.callback怎麼不爲空的?提問的這位兄弟麻煩你回過頭去從新再來一遍吧。在上面分析getPostMessage(r)方法時不是就有m.callback = r;這麼一行麼。因此如今知道代碼是往handleCallback(msg);這裏走的,進去瞧瞧

private static void handleCallback(Message message) {
    message.callback.run();
}
複製代碼

好想來句:shit!!!原來就是r.run()啊!這不就是接口方法調用麼。咱們實際每次用的handler.post(Runnable r)就是對這個Runnable這個接口具體的實現而已。這和咱們自定義一個點擊事件有啥區別呢?因此不難看出Runnable就是一個普通到不能再普通的接口而已。通過上面的分析咱們不只知道了Runnable只是個普通的接口並且還一併弄清楚了Handlers是如何存消息取消息的。

經過分析Thread同樣能夠得出Runnable就是一個普通的接口。這裏就不作深刻分析了

new Thread(new Runnable() {
    @Override
    public void run() {
    Log.e("------>>>>>>>","Thread: "+Thread.currentThread().getName());
    }
}).start();
複製代碼

點進去看,能夠看到Runnable直接賦值給了成員變量Runnable target了,而target.run()在Thread的run()方法中,當調用start()後會致使線程的run()方法被執行。那麼也就能夠證明Runnable只是個普通接口了,是不會開啓線程的。

二:Handler是如何實現線程切換的?

Handler handler = new Handler();
Log.e("++++++>>>>>>>","Thread: "+Thread.currentThread().getName());
new Thread(){
    @Override
    public void run() {
        super.run();
        Log.e("======>>>>>>>","Thread: "+Thread.currentThread().getName());
        Looper.prepare();
        handler.post(new Runnable() {
            @Override
            public void run() {
            Log.e("------>>>>>>>","Thread: "+Thread.currentThread().getName());
            }
        });
        Looper.loop();
    }
}.start();

········

Log.e("++++++>>>>>>>","Thread: "+Thread.currentThread().getName());
new Thread(){
    @Override
    public void run() {
        super.run();
        Log.e("======>>>>>>>","Thread: "+Thread.currentThread().getName());
        Looper.prepare();
        new Handler().post(new Runnable() {
            @Override
            public void run() {
            Log.e("------>>>>>>>","Thread: "+Thread.currentThread().getName());
            }
        });
        Looper.loop();
    }
}.start();
複製代碼

分別打印結果以下

結果1

結果2
從結果1和結果2的比較能夠看出Handler從主線程切換到子線程中,而那Handler是如何切換線程的呢?從上面的代碼看只是初始化Handler的位置不一樣而已。那咱們來看看初始化Handler的過程幹了啥。

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();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
複製代碼

在這裏咱們能夠看到當mLooper爲空時會拋出一個異常,意思就是在當前線程沒有調用Looper.prepare()時是不容許建立Handler的,也就是不能new Handler()。這說明真正起到關鍵性做用的是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));
}
複製代碼

很簡單就是建立一個Looper並將這個looper設置到ThreadLocal中。看看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);
}
複製代碼

看到這裏應該能明白了,這個set()方法就是把當前線程和這個new出來的Looper綁定起來。而在咱們初始化Handler的時候不知道有沒有注意到一句 mLooper = Looper.myLooper();

public static @Nullable Looper myLooper() {
    return sThreadLocal.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();
}
複製代碼

這裏很好理解就是經過當前線程(key)來取出與當前線程綁定的Looper(value) 。這也正好和Looper.perpare()方法裏的存保持一致。因此能夠知道Handler裏面的Looper對象就是以前Looper.prepare()中建立出來的Looper了。綜合上面對一的分析,就是在調用Looper.perpare()的時候將當前線程、消息隊列和該Looper對象關聯起來了。這樣就能夠解釋了:Looper調用prepare()將Looper綁定到當前線程,當調用Looper.loop()時開啓循環輪詢消息,因此在當前線程調用Handler.post(),最終都會在指定線程被Looper.loop()輪詢出來。

三:handler.post(Runnable r)爲何能在子線程操做UI

看了上面第二點應該有部分人應該已經知道緣由了。其實咱們當初討論的時候是從第三點開始的,這裏之因此先從二開始講是能更深入理解爲何咱們經常使用的handler.post(Runnable r)就能在子線程操做UI。

在第二點中咱們知道了兩點影響因素,Looper.perpare()和new Handler(),當在主線程中去初始化一個Handler對象時咱們並無調用Looper.perpare()和Looper.loop(),那Looper是哪裏來的呢?經過對mLooper = Looper.myLooper(); 的分析咱們知道了,Looper其實就是當前線程的Looper,而咱們又是在UI線程初始化的Handler,因此咱們在new Handler()時拿到Looper就會是與UI線程綁定的Looper,通過上面一和二的分析,咱們已經知道了當前線程,Looper和MessageQueue是相互關聯起來的。因此UI線程初始化的Handler發送的消息最終會被UI線程的Looper.loop()輪詢出來。而這個Looper是在咱們初始化UI線程時,系統就已經幫咱們建立好的,因此咱們纔會不須要調用UI線程的Looper.perpare()和Looper.loop()。

總結

經過調用Looper.perpare()將當前線程和Looper以及MessageQueue關聯起來,Handler發送一個Message對象,並將這個Message對象經過enqueueMessage()方法存到MessageQueue消息隊列中,當調用Looper.loop()方法時就會去MessageQueue中輪詢消息取出Message對象,將取出的Message經過Handler的dispatchMessage()方法對Message進行處理。而這個dispatchMessage()會根據你傳進來的是Runnable仍是Message判斷到底最後是調用runnable.run()仍是handleMessage(msg),從而實現外部建立Handler對象的具體實現。

相關文章
相關標籤/搜索