天天看點

寫給Rikka自己的Handler源碼說明書

1.Handler概述

Handler是Android的一套消息傳遞機制。

為什麼要使用Handler呢?隻有一點原因:在界面可視之後,子線程不能更新UI,是以子線程需要一種手段來通知UI線程資料已更新,這個手段就是Handler。

1.1 Handler的組成

它有四個非常重要的對象來完成這套機制:

  • ​Handler​

    ​ 負責消息的發送和處理
  • ​MessageQueue​

    ​ 消息隊列,負責存放消息
  • ​Message​

    ​ 消息載體
  • ​Looper​

    ​ 負責消息的輪詢

上面的四個東西都是處于同一個線程,在一個線程裡面實作了Handler機制。

這裡畫一個圖來看看他們之間的關系:

寫給Rikka自己的Handler源碼說明書

這是一個非常簡單了解的版本,下面來解讀一下

  1. 任何一個線程隻有一個Looper,任意一個Looper裡面隻有一個MessageQueue
  2. MessageQueue維護的是一個 Message連結清單,裡面的Message有明确的指向
  3. 一個線程可以持有有多個Handler

2. Handler機制源碼

2.1 Handler構造函數

先來看下Handler的構造函數:

// Handler.java
    private static final boolean FIND_POTENTIAL_LEAKS = false;
    final Looper mLooper;
    final MessageQueue mQueue;
    final Callback mCallback;
    final boolean mAsynchronous;
    IMessenger mMessenger;
   
    public interface Callback {
        public boolean handleMessage(Message msg);
    }
   
    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 that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }      

Handler主要實作上面兩種構造方法。分别對 mLooper、mQueue、mCallback、mAsynchronous進行了傳參。

  • mCallback:​

    ​Callback​

    ​​類型,是一個實作了​

    ​handleMessage()​

    ​的對象,由構造時外部代入,可以說是回調函數,或者說Handler機制的出口。
  • mLooper:​

    ​Looper​

    ​類型,每個Handler都會持有綁定一個Looper
  • mQueue :​

    ​MessageQueue​

    ​類型,作為Handler中消息隊列的存在,它是Looper的消息變量,是以會從 mLooper中取出。
  • mAsynchronous :是否異步

上面兩個構造函數的差別是,前者沒有在傳參時帶入Looper,是以目前的 Looper為 ​

​Looper.myLooper()​

​。為了搞懂這個方法、對象是什麼意義?我們需要先知道Looper。

2.2 Looper從哪裡冒出來的

首先每個線程隻有一個Looper,在Handler機制的場景下,我們要得到就是 主線程(即UI線程)的那個Looper。

那麼主線程的Looper是怎麼來的,在 應用程式程序的啟動的學習中(如果沒學過請參考 《Android進階解密-應用程式程序啟動過程》),在應用程式程序被建立時,會建立UI線程ActivityThread,并調用它的 main():

// ActivityThread.java
 public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();  // 1
        ActivityThread thread = new ActivityThread(); 
        thread.attach(false);  
        ...
        Looper.loop();  // 1
        ...
    }      

注釋1:建立一個 主線程Looper。

注釋2:開啟這個Looper。

先暫且不看第二個方法,去看看第一個方法裡面做了什麼:

// Looper.java
    public static void prepareMainLooper() {
        prepare(false); // 1
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper(); // 2
        }
    }      

注釋1:調用了 ​

​prepare(flase)​

​​ 注釋2:同樣的,也調用了 ​

​myLooper()​

​,并把傳回結果指派給了 sMainLooper,聽名字就知道它是 主線程的Looper。

來看看prepare方法做什麼:

// Looper.java
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
    }

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

到這個方法裡,我們看到了 UI線程的Looper,就是在這個地方(​

​prepare()​

​​)建立的。

它通過構造函數,持有了目前UI線程的引用mThread,并且建構了一個新的 ​​

​MessageQueue​

​​mQueue,且為非異步。

并把這個 UI線程Looper 通過 ​​

​ThreadLocal.set()​

​設定到了 sThreadLocal中:

// ThreadLocal.java
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }      

這裡出現了一個叫做 ​

​ThreadLocalMap​

​​的東西,而set方法就是把 (threadLocal, UiLooper)作為鍵值對存進去的,也就是說 ThreadLocalMap是一個存放 ​

​ThreadLocal - Looper​

​的集合,我們可以看看 ThreadLocalMap這個類,它是ThreadLocal的内部類:

// ThreadLocal.java
 static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            // 1
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        // 2
        private Entry[] table;
        
        private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            // 3
            int i = key.threadLocalHashCode & (len-1);
            // 4
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                if (k == key) {   // 5
                    e.value = value;
                    return;
                }
                if (k == null) {  
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value); // 6
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }      

來解析一個 ThreadLocalMap:

注釋1:一個 ​​

​Entry​

​​包含一個弱引用的 ThreadLocal作為key,一個Object作為value,這個Object我們前面帶進來的就是Looper。

注釋2:表明 ThreadLocalMap的資料結構是一個 ​​

​Entry​

​​數組

注釋3:ThreadLocalMap的set方法借鑒了HashMap得做法,元素的存儲位置是 ​​

​key的hash值mod哈希桶的長度​

​​ 注釋4、注釋6:通過注釋3,拿到存儲位置 i,如果i位置存在有效元素,也就是說發生了哈希沖突,這個時候 i取 ​

​nextIndex(i, len)​

​,即i 的下一個下标,然後拿新的i去判斷這個位置有沒有元素,直到找到為空的元素,然後放入這個Looper。

注釋5:如果i下标存在元素,但是 它的key和拿來存放資料的 key是一樣的,則覆寫value。

到這裡我們就知道ThreadLocalMap是做什麼的了:

ThreadLocalMap總結:

​​

​TheradLocalMap​

​​存放 ​

​ThreadLocal - Looper​

​的鍵值對,其中ThreadLocal是弱引用,它采用hash的方式存取,效率比較高,如果出現了hash沖突,則采用線性探測的方法解決。它維護了目前ThreadLocal所持有的Looper。

回到 ​

​ThreadLocal.set()​

​​中,UI線程的Looper和Looper的成員變量 sThreadLocal被作為 k-v已經被存放到這個 ThreadLocalMap中了。然後又回到 ​

​prepareMainLooper()​

​​的注釋2中: sMainLooper = myLooper(),這裡的重點是 ​

​myLooper()​

​方法,我們來看看這個方法做了什麼:

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

myLooper() 是傳回 ​

​ThreadLocal.get()​

​:

// ThreadLocal.java
      public T get() {
        Thread t = Thread.currentThread(); // 1
        ThreadLocalMap map = getMap(t);  // 2
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this); // 3
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }      

注釋1、2:擷取到目前線程的 ​

​ThreadLocalMap​

​​。

注釋3:得到 ThreadLocalMap中目前 ThreadLocal所綁定的Looper。

是以 ​

​ThreadLocal.get()​

​擷取的是目前線程的Looper

而在目前場景中,我們拿到的是主線程的Looper。

這裡也就得出了Handler構造函數中Looper從哪裡來的結論:

每個線程隻有一個Looper,如果不在Handler構造函數中帶入Looper,那麼Looper就是:Handler所線上程的Looper。

我們知道Looper是怎麼出生的,但是我們還不知道它是做什麼的,在這節的開頭,我們通過 ​

​prepareMainLooper()​

​​建立了UI線程的Looper後,我們又通過 ​

​Looper.loop()​

​開啟了Looper的輪詢,這個方法非常重要,我們需要深入它。

2.3 Loop.loop()

來看下 loop()代碼:

// Looper.java
    public static void loop() {
        final Looper me = myLooper();  // 1
        ...
        final MessageQueue queue = me.mQueue;  // 2
        ...
        for (;;) {  // 3
            Message msg = queue.next();   // 4
            if (msg == null) {  // 5
                //  No message indicates that the message queue is quitting.
                return;
            }
            ...

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            ...
            try {
                msg.target.dispatchMessage(msg);  // 6
            } finally {
                ...
            }
            ...
            msg.recycleUnchecked();
        }
    }      

上面濾去了不重要的代碼。

注釋1:拿到目前線程的Looper(即ui線程的Looper)

注釋2:從Looper中拿出 ​​

​MessageQueue​

​​ 注釋3:開啟死循環

注釋4:從 ​

​MessageQueue.next()​

​中擷取一個 Message出來

注釋5:如果取出的Message為null,則跳出死循環

注釋6:根據 Message的target來通過 ​

​dispatchMessage(msg)​

​來發送這個消息。

也就是說 ​

​Looper.loop()​

​​開啟了一個死循環,來處理其 MessageQueue裡面的每一個Message。

直到 Message為null了才跳出循環,而英文注釋表示 當隻有在 ​​

​MessageQueue​

​退出的時候 Message才為空,而MessageQueue的生命和線程一樣,也就是說:線程終止了,這個循環才會結束。

是以也進而驗證了,這個類為什麼叫Looper(循環者,不是某冠軍上單)。

這裡有兩個問題,也是面試的時候面試官喜歡問的:

(1)為什麼開了死循環,App卻感受不了卡頓,也沒有ANR?

逆推回去,既然App沒有卡頓,也就是說 ​​

​MessageQueue​

​有源源不斷的message供主線程處理,這是因為像 AMS、WMS這些SystemServer程序在程式運作時會一直提供功能的支援,通過Binder機制,向主程序的主線程中發送消息,是以 Looper的死循環一直是工作狀态,是以并不會導緻卡頓。

(2)那這樣一直開着死循環,不會很占CPU資源嗎?

這裡就要繼續看代碼了,我們看看上述循環中 ​​

​MessageQueue.next()​

​做了什麼.

2.4 MessageQueue.next()

因為 next()代碼有點長,是以把其中死循環的部分分成三個部分進行講解:

Part1 無消息處理時挂起

// MessageQueue.java
  private native void nativePollOnce(long ptr, int timeoutMillis); 
    
    Message next() {
        ...
        int pendingIdleHandlerCount = -1; 
        int nextPollTimeoutMillis = 0;
        for (;;) {   // 1
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);  // 2  
            ....
       }
     }      

注釋1中:可以看到的, ​

​next()​

​​也開了個死循環,WTF?什麼鬼,loop()已經是死循環,這裡還來套一個死循環了???

注釋2:調用 ​​

​nativePollOnce(ptr, nextPollTimeoutMillis)​

​​,這個方法非常滴重要,它是一個 native方法,我這裡就不再深入到 JNI層去看C的代碼了,我這邊用大白話來解釋這個代碼的作用:

在主線程打開Looper循環前,會打開Android系統底層的一個I/O管道(​​

​Linux epoll​

​​),如果你身邊有個背景人員,你可以問他關于 ​

​epoll​

​的知識,epoll做的事情就是監視檔案描述符的I/O事件,它是 事件驅動模型,換言之,它就是一個NIO。在沒有事件時主線程挂起,有事件時主線程喚醒處理。

關于C++層的源碼可以參考下這一篇:​​​Android 中 MessageQueue 的 nativePollOnce​​​。

函數中傳入了 nextPollTimeoutMillis,有點像 ​​

​Thread.sleep(mills)​

​。這個nextPollTimeoutMillis會在延時任務中起到作用。

這裡也就回答了上節末尾的問題,為什麼開死循環不會特别占用CPU的資源,是因為在沒有消息的時候主線程已經挂起,有消息時才會喚醒。

Part2 傳回一個消息

// MessageQueue.java
...
   synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;   // 1
                ...
                if (msg != null) {
                    if (now < msg.when) {  // 2
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;  // 3
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    nextPollTimeoutMillis = -1;
                }
....      

這段代碼比較好了解,不過我們要先知道 ​

​MessageQueue​

​​裡面維護的是一個 ​

​Message​

​​連結清單

注釋1:拿到目前的​​

​Message​

​​mMessage。

注釋2:判斷 msg是否是延時任務,如果是的話則不處理,并更新 ​​

​nextPollTimeoutMillis​

​​ 的時間

注釋3:如果 msg不是延時任務,則把 mMessage指向目前 msg在連結清單中的下一個。然後return目前的msg。

Part3 IdleHandler

...
          synchronized (this) {
              ....
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();   // 1
                }
                if (pendingIdleHandlerCount <= 0) {   // 2
                    mBlocked = true;
                    continue; 
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; // 3
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 4
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; 
                boolean keep = false;
                try {
                    keep = idler.queueIdle();  // 5
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            pendingIdleHandlerCount = 0; // 6
            nextPollTimeoutMillis = 0; // 7
        }
    }      

這段代碼涉及到了 ​

​IdleHandler​

​,它是MessagQueue的内部類,我們先來看看他是做啥的:

// MessagQueue.java
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    public static interface IdleHandler {
        boolean queueIdle(); 
    }
    
    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }      

可以發現它是一個接口,通過 ​

​addIdleHandler()​

​​可以添加實作了 ​

​queueIdle()​

​​方法的對象進來,儲存在 ​

​mIdleHandlers​

​的List中。

我們回到part3的代碼中來解析一下:

注釋1:如果pendingIdleHandlerCount < 0 并且目前沒有Message或者是延時任務,則把 ​​

​mIdleHandlers​

​​的大小指派給pendingIdleHandlerCount 。

注釋2:如果指派過後的pendingIdleHandlerCount <= 0,則說明目前線程沒有可以執行的任務,那麼continue,在下一個循環中将目前線程挂起。

注釋3:如果mPendingIdleHandlers為null,則new一個IdleHandler出來指派給它,并将其轉換成一個 數組。

注釋4:周遊 mPendingIdleHandlers

注釋5:取出其中的每個 ​​

​IdleHandler​

​​并執行它的 ​

​queueIdle()​

​​方法。

注釋6、注釋7:清空 pendingIdleHandlerCount 和 nextPollTimeoutMillis 。因為如果是延時任務,早就已經continue了。另外一個事情就是,如果第一次打開死循環,Message連結清單是空的,這時候主線程可以做些别的事情(就是 ​​

​IdleHandler​

​),做完之後,之後的循環就不會再去做這樣的操作了(除非我們自己加IdleHandler進來)。

到這裡 ​

​MessageQueue.next()​

​就解析完了,這裡做一個總結,它大概做了三件事情:

  1. 開啟死循環
  2. 如果目前沒有Message可以處理,則調用​

    ​nativePollOnce()​

    ​挂起主線程,不占用CPU資源。 等到有消息可以處理時喚醒主線程。
  3. 如果存在非延時的消息,則把該消息傳回到Looper中。
  4. 如果是循環的第一次,可能會沒有消息,這個時候可以 處理​

    ​IdleHandler​

    ​的消息,做一些别的事情,這相當于提升了性能,不讓主線程一上來就因為沒有Message而挂起,然後下個循環又馬上被喚醒。

2.5 Message是如何被加入到MessageQueue中的?

在上一節中,我把MessaqeQueue和Messaqe的關系籠統的概括為: MessageQueue中維護了一個Message連結清單。

僅僅這樣了解是不夠的,我們需要搞清楚,一個Message是怎麼放到 MessageQueue中的。入口方法就是我們常用的 ​​

​Handler.sendMessage()​

​​或者 ​

​Handler.sendMessageDelayed()​

​​還是 ​

​Handler.postDelay()​

​​,他們最終都會調用 ​

​sendMessageAtTime()​

​:

// Handler.java
    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()​

​​,然後傳入目前 Handler的 ​

​MessageQueue()​

​ mQueue,我們在開篇講過,mQueue是構造函數中就被建立,它是傳入的 Looper的MessageQueue。

// Handler.java
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;  // 1
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis); // 2
    }      

注釋1:把​

​msg.target = this​

​,就是說這個消息發送給目前Handler的。

注釋2:調用 ​​

​MessageQueue.enqueueMessage()​

​​,傳入消息體和延時時間。這個動作就是把消息加入到 MQ中了。

來看一下加入過程:

// MessageQueue.java
    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) {  //1
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {   // 2
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }      
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }      

注釋1:如果目前的Message是非延時任務,或者說比清單最後一個Message的執行時間要快,那麼它就插隊,插到前面去。

注釋2:否則的話,就往清單後邊找,找到 末尾或者第一個Message執行時間比目前的要晚的,然後插進去。

這段代碼很簡單,要麼目前任務 插到前面,要麼插到後面最早執行的位置。

到這裡,一個Message的入隊就講完了。

2.6 關于消息分發 msg.target.dispatchMessage(msg)

回到Looper中,在死循環裡,它會調用 ​

​msg.target.dispatchMessage(msg)​

​​, 我們知道,msg.target是發送消息的Handler,那麼這裡調用了 目标Handler的​

​dispatchMessage()​

​把Message發送出去,來看看這個方法:

// Handler.java
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);  // 1
        } else {
            if (mCallback != null) {  // 2
                if (mCallback.handleMessage(msg)) { // 3
                    return;
                }
            }
            handleMessage(msg);  // 4
        }
    }
   
    private static void handleCallback(Message message) {
        message.callback.run();
    }      

注釋1:如果​

​msg.callback​

​​不為空,則直接調用這個Callback的 ​

​run()​

​​,這個情況是我們在Message中傳入了一個Callback的方法,它在處理時,會直接調用這個Callback方法。

注釋2、注釋3:如果在建立 Handler(構造函數中)時帶入了 一個 Callback,則直接調用這個 Callback,這個構造函數我們在開篇看過。

注釋4:如果都不是上述情況,則調用 ​​

​handleMessage()​

​。

// Handler.java   
    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }      

​handleMessage()​

​ 是一個空方法,英文文檔的解釋為: 子類必須實作這個方法

這就是為什麼我們在自己使用的時候,一定要重寫 ​​

​handleMessage()​

​​,然後對發送過來的消息做處理。

當這個消息被調用時,也說明了 Message被分發成功了。

到這裡,handler的源碼也講解的差不多了。

3. 在子線程中更新UI

這裡畫一個圖便于了解:

寫給Rikka自己的Handler源碼說明書

這裡手寫一個簡單例子,在子線程通過Handler去更新UI線程:

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    private static final int COUNT = 0x00001;

    private Handler handler = new Handler() {  // 1
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == COUNT) {
                textView.setText(String.valueOf(msg.obj));
            }
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.btn);

        new Thread() {  // 2
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        Thread.sleep(500);
                        Message msg = new Message();
                        msg.what = COUNT;
                        msg.obj = "計數:" + i;
                        handler.sendMessage(msg);   // 3
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}      

注釋1中:我們在主線程建立了一個Handler,那麼其Looper自然就是UI線程的Looper

注釋2:開啟一個子線程

注釋3:在子線程中,持有一個主線程的Handler,然後給這個Handler發送消息。

上面代碼非常容易了解,但是可能會出現記憶體洩漏,是以這裡隻是做一個子線程更新UI的認知。

4. 主線程更新子線程、子線程更新其他子線程

開篇我說過,Handler的出現是子線程不能更新UI,是以Handler起到了這個作用。

這一章是來自于我實習面試的時候,面試官在Handler這點上問我:那主線程怎麼更新子線程?或者子線程怎麼更新其他的子線程?

我當時由于沒有細讀Handler的源碼,是以我粗略的回答:隻要持有對方線程的Handler,就可以更新了。

其實我回答也沒錯,但是這談不上及格的答案,面試官也不會喜歡,下面我将用代碼來示範一遍:

4.1 主線程更新子線程

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private MyThread thread;

    class MyThread extends Thread {
        private Looper looper;

        @Override
        public void run() {
            Looper.prepare();  // 1
            Log.d(TAG, "子線程為->" + Thread.currentThread() + "");
            looper = Looper.myLooper();   // 2
            Looper.loop();  // 3
        }
    }

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "主線程為->" + Thread.currentThread() + "");
        thread = new MyThread();
        thread.start();
        try {
            sleep(2000);  // 4
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        mHandler = new Handler(thread.looper) {   // 5
            @Override
            public void handleMessage(Message msg) {
                Log.d(TAG, "目前線程為->" + Thread.currentThread() + "");
            }
        };
        mHandler.sendEmptyMessage(0);
    }
}      

主線程要更新子線程,是以主線程要持有子線程的Handler,是以需要用子線程來構造一個Handlder

注釋1:為子線程建立一個Looper

注釋2:擷取子線程Looper

注釋3:打開子線程Looper的輪詢

注釋4:主線程睡一下,避免 子線程還沒有完全起來的時候就在主線程擷取子線程的Looper了

注釋5:通過子線程的Looper建立屬于子線程的Handler,然後在主線程中使用Handler發送消息。

列印如下:

寫給Rikka自己的Handler源碼說明書

說明消息已經從主線程傳遞到子線程當中去了。

4.2 子線程更新其他子線程

方法同上。下面寫兩個線程 ​

​MyThread1​

​​和 ​

​MyThread2​

​,然後讓2線程給1線程發消息:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private MyThread1 thread1;
    private MyThread2 thread2;

    private Handler thread1Handler;

    class MyThread1 extends Thread {

        @Override
        public void run() {
            Log.d(TAG, "子線程1為->" + Thread.currentThread() + "");
            Looper.prepare();
            thread1Handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.d(TAG, "這個消息是從->" + msg.obj + "線程發過來的,在" + Thread.currentThread() + "線程中擷取的");
                }
            };
            Looper.loop();
        }
    }

    class MyThread2 extends Thread {
        @Override
        public void run() {
            Log.d(TAG, "子線程2為->" + Thread.currentThread() + "");
            Message msg = thread1Handler.obtainMessage();
            msg.obj = Thread.currentThread();
            thread1Handler.sendMessage(msg);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        thread1 = new MyThread1();
        thread2 = new MyThread2();
        thread1.start();
        try {
            Thread.sleep(500);  // 讓Thread1完全起來
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }      

列印結果如下:

寫給Rikka自己的Handler源碼說明書

是以一開始的問題:如果在主線程中更新子線程,在子線程中更新另外的子線程的解答:

(1)要持有目标線程的Handler

(2)因為非UI線程是不會自己開啟輪詢的,是以要手動在子線程中啟動 ​​

​Looper.prepare()​

​​和 ​

​Looper.loop()​

​ another question: HandlerThread

在上面的手動寫法中,我們還要寫 ​

​Thread.sleep​

​​保證子線程啟動起來,并且我們還要手動在子線程中手寫 ​

​Looper.prepare()​

​​、​

​Looper.loop()​

​​,這無疑有一些麻煩,Android提供了一個便于在子線程開啟Handler、Looper的類,就是 ​

​HandlerThread​

5. HandlerThread

HandlerThread顧名思義,就是使用 Handler的線程。它的源碼非常簡單,來看看:

先來看看其構造函數:

// HandlerThread.java
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
}      

​HandlerThread​

​​是繼承Thread的,在構造函數中,傳入線程名稱和優先級。

接下來看看其 run方法:

// HandlerThread.java
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }      

run方法中,寫了 ​

​Looper.prepare()​

​​,然後鎖住對象去擷取Looper,通過 ​

​Process.setThreadPriority()​

​​來設定線程優先級。

然後調用 ​​

​onLooperPrepared()​

​,這是個空方法,表示我們在自己使用HandlerThread時,可以通過重寫這個方法來在Looper準備完成後做一些想做的事情。

如何使用HandlerThread來發送消息呢?HandlerThread實作了下面的方法:

@NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }      

通過 ​

​getThreadHandler()​

​​我們可以得到目前線程的Handler,并且它會在 ​

​getLooper()​

​​中保證Looper是起來的。

是以這也避免了我們手寫 wait()、sleep()的麻煩。

這裡總結一下HandlerThread:

HandlerThread是實作了Thread的線程,它start後會進行 Looper.prepare()、Looper.loop(),并且在我們想要使用其Handler時,能保證Handler不為空。它适合的場景是:主線程需要更新子線程的資料、子線程更新另一個子線程的資料。被更新的子線程可以使用​​

​HandlerThread​

​。

6. 關于Message的擷取

寫這一章節也是基于面試時比較喜歡問的,Message的擷取有兩種方法:

  1. 通過 new 方法擷取
  2. 通過 Message.obtain()

上述第二種方法就是 Handler中的 ​

​obtainMessage()​

​​裡調用的方法,兩種方法是有差別的,哪一種更好?

我們來看看 ​​

​Message.obtain()​

​:

// Message.java
    private static Message sPool;
    private static int sPoolSize = 0;
    
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }      

可以看到 ​

​obtain()​

​​中會去 sPool中擷取一個Message,如果sPool為空,則傳回一個new的Message,否則把sPool指派給m,并将m傳回,然後 sPool取其next。

在結合一下下面的方法:

// Message.java
    void recycleUnchecked() {
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }      

這個方法是在一個消息被使用後,對其進行回收:将所有資料置空,然後将其做為sPool,原來的sPool放到新的後面。

在 Looper.loop()的死循環中,每個循環的最後都會用這個方法。

是以可以輕易的得出結論: 使用 ​

​obtainMessage()​

​要比new一個Message好,但是它要和 ​

​recycleUnchecked()​

​一起使用。

因為 ​

​obtainMessage()​

​​維護了一個Message池(Message連結清單),如果池裡沒有就會建立一個Message,如果有就将其取出來。在用完之後,調用​

​recycleUnchecked()​

​​将使用後的消息資料置空,然後将這個Message放回池裡,下次還可以用它,達到了重複利用的效果,節省了空間。但是如果​

​recycleUnchecked()​

​,就不會有Message置空,池子永遠都是空的,是以不配合這個方法一起使用,其實就和new出來的一樣了。

7. 消息屏障

在讀Looper的源碼時,有一個詞一直出現,就是 barrier,意思是屏障。

Handler中除了可以 sendMessage/postDelay消息體之外,還有一個方法: MessageQueue的 ​​

​postSyncBarrier()​

​:

// MessageQueue.java
    private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;
            ... // 插入到隊列的方法同 enqueueMesage()
        }
    }      

它的方法和 ​

​enqueueMesage()​

​差不多,但是我們發現它的傳參,沒有Message,它構造的Message,隻有when,它沒有傳入tartget,也就是說 消息屏障是發送一個target為null的消息,在 MessageQueue的​

​next()​

​的死循環中中,一上來就是這麼一個處理:

// MessageQueue
 ...
       synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) { // 1
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous()); // 2
                }
                ...      

注釋1:判斷目前的消息是否是屏障消息

注釋2:通過 do-while語句拿到MQ中第一個異步的消息并取出來