Handler通信機制源碼解讀

工作中,我們可能直接使用我們需要的工具方法,但是不曾瞭解其中的原理內涵,這樣並不能很好的讓我們理解其運行機制,在複雜項目和疑難問題時無從入手。作爲開發想要提高並設計架構,一是要先學習優秀的設計理念,再就是了解其內部原理,爲自己在複雜使用場景和發生疑難問題時能夠透過表象看到本質。
Handler,Message,looper 和 MessageQueue 構成了安卓的消息機制,handler創建後可以通過 sendMessage 將消息加入消息隊列,然後 looper不斷的將消息從 MessageQueue 中取出來,回調到 Hander 的 handleMessage方法,從而實現線程的通信。
從兩種情況來說,第一在UI線程創建Handler,此時我們不需要手動開啓looper,因爲在應用啓動時,在ActivityThread的main方法中就創建了一個當前主線程的looper,並開啓了消息隊列,消息隊列是一個無限循環,爲什麼無限循環不會ANR?因爲可以說,應用的整個生命週期就是運行在這個消息循環中的,安卓是由事件驅動的,Looper.loop不斷的接收處理事件,每一個點擊觸摸或者Activity每一個生命週期都是在Looper.loop的控制之下的,looper.loop一旦結束,應用程序的生命週期也就結束了。我們可以想想什麼情況下會發生ANR,第一,事件沒有得到處理,第二,事件正在處理,但是沒有及時完成,而對事件進行處理的就是looper,所以只能說事件的處理如果阻塞會導致ANR,而不能說looper的無限循環會ANR
另一種情況就是在子線程創建Handler,此時由於這個線程中沒有默認開啓的消息隊列,所以我們需要手動調用looper.prepare(),並通過looper.loop開啓消息
主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,並且往管道文件寫數據,主線程即被喚醒,從管道文件讀取數據,主線程被喚醒只是爲了讀取消息,當消息讀取完畢,再次睡眠。因此loop的循環並不會對CPU性能有過多的消耗。

Handler源碼部分解讀:

初級程序員使用handler時有時會出現異常。Can’t create handler inside thread that has not called Looper.prepare()
爲什麼呢。
在core/java/android/os/Handler.java 中

 @hide
     */
    public Handler(@Nullable 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.myLooper();通過檢測mLooper是否爲空,爲空則報上述異常。如果不爲空, 再通過mQueue = mLooper.mQueue;獲取消息隊列,可以看到messageQuene對象是在Looper中獲取的。
再往下看,Handler中提供獲取主線程Handler的方法。

  /** @hide */
    @UnsupportedAppUsage
    @NonNull
    public static Handler getMain() {
        if (MAIN_THREAD_HANDLER == null) {
            MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
        }
        return MAIN_THREAD_HANDLER;
    }

之後,我們我們發現了我們常用的obtainMessage方法。爲何建議使用obtainMessage而不是new Message()呢,從obtainMessage的註釋中也可以看出來,其會從一個全局message pool中返回一個新的message,比新建一個實例更有效率。另外的比如obtainMessage(int what)和obtainMessage(int what, @Nullable Object obj)則同步設置了message.what和obj消息。

/**
     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
     *  If you don't want that facility, just call Message.obtain() instead.
     */
    @NonNull
    public final Message obtainMessage()
    {
        return Message.obtain(this);
    }

然後是各種發送消息的方法,包括髮送message消息的和post(runnable)的方法。可以看到無論是send還是post還是帶時間參數的,最終都走到了queue.enqueueMessage。這個messagequeue在下邊的小節會解讀。
Handler中消息發送方法調用

Looper源碼解讀:

在Handler源碼中我們看到其Looper是用Looper.myLooper獲取的,這裏looper相當於消息的傳送帶,不斷的循環檢測是否有消息發過來。
在Looper類文件的開頭我們就看到了谷歌給我們的註釋和一個例子,清楚的說明了普通現場默認是沒有looper的,需要手動創建,然後創建的方法就是Looper.prepare(),然後下面的例子也清楚的解釋了上節中報錯信息如何防止,Can’t create handler inside thread that has not called Looper.prepare()。然後啓動looper循環用Looper.loop(),以啓動消息循環,直到loop被停止。

/**
  * Class used to run a message loop for a thread.  Threads by default do
  * not have a message loop associated with them; to create one, call
  * {@link #prepare} in the thread that is to run the loop, and then
  * {@link #loop} to have it process messages until the loop is stopped.
  *
  * <p>Most interaction with a message loop is through the
  * {@link Handler} class.
  *
  * <p>This is a typical example of the implementation of a Looper thread,
  * using the separation of {@link #prepare} and {@link #loop} to create an
  * initial Handler to communicate with the Looper.
  *
  * <pre>
  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }</pre>
  */

然後prepare的方法定義,可以看到sThreadLocal.get() != null的判斷,這裏可以看出,一個線程只能創建一個looper,但是如何保證的呢,代碼如何實現一個線程一個looper的呢。這裏就需要看代碼中的sThreadLocal的get和set方法了。

/** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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,當然會走到set那,然後看變量初始化
ThreadLocal sThreadLocal = new ThreadLocal()
通過網絡查詢我們知道,ThreadLocal類提供了線程局部 (thread-local) 變量,多線程數據不能共享。
而其set方法源碼爲

public void set(T value) {
//獲取當前線程
        Thread t = Thread.currentThread();
//在ThreadLocal 中,以t來獲取當前線程的一個ThreadLocalMap
        ThreadLocalMap map = getMap(t);
//如果ThreadLocal中有這個map,就把當前類和對應的值傳進去
        if (map != null)
            map.set(this, value);
//如果沒有這個map,就新建一個t線程的ThreadLocalMap,並將value值傳進去
        else
            createMap(t, value);
    }

可見,是用hashmap類似的結構存儲了當前線程id,和Looper的對應關係,保證一個線程一個looper, 關於ThreadLocal還有很多知識點,這裏我們只簡單帶過,之後有時間新開一章詳細解讀。
然後根據上述threadlocal的set方法,這裏獲取這個map中此線程對應的唯一looper,這裏就是Handler中獲取looper的實現,保證了唯一性。

 /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

之後看Looper.loop的代碼,這個是消息傳送帶的動力來源,如果不調用這個方法,消息是沒法獲取到的。

/**
     * 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();
        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
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ........
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
            .........

從代碼中可以看到,一個for(; ; ),代表開始了一個無線循環,然後從queue.next中獲取消息,如果爲空則返回。另注意queue.next後註釋了 might block, 這裏是不是類似socket方法中的read,讀不到消息就阻塞在這,這個在下一節中尋找答案。之後我們發現msg.target.dispatchMessage(msg);通過msg對象中的target,分發了消息出去。
最後是looper的quit方法和quitSafely方法,二者區別是,前者立即停止當前消息隊列中消息的處理,後者是待現存消息隊列中的消息處理完後再推出消息循環。
然後懷着激動的心情,我們來看messageQueue的源碼。

MessageQueue源碼解讀

首先把類的註釋貼下,谷歌的註釋最能完整體現這個類的意義了。

/**
 * Low-level class holding the list of messages to be dispatched by a
 * {@link Looper}.  Messages are not added directly to a MessageQueue,
 * but rather through {@link Handler} objects associated with the Looper.
 *
 * <p>You can retrieve the MessageQueue for the current thread with
 * {@link Looper#myQueue() Looper.myQueue()}.
 */
public final class MessageQueue {

看源碼的開始部分,沒那麼好看,很多和native code交互的方法和變量,

@UnsupportedAppUsage
Message mMessages;
@UnsupportedAppUsage
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;

// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
.....
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
@UnsupportedAppUsage
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

這裏我們應該可以不用去太詳細瞭解其內涵,我們需要尋找上述小節中調用到的messagequeue的方法。

 @UnsupportedAppUsage
    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();
            }

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

首先,在其 synchronized (this) 代碼塊中,保證messagequeue的隊列不被多線程更改。然後獲取下一條消息時,先獲取當前時間final long now = SystemClock.uptimeMillis();我們可以想到前邊handler中各種sendattime或者postdelay等消息也帶着時間參數。
這裏看到檢測如果msg不爲空,並且msg.target也不爲空,這裏又出現了target變量,什麼東西,我們繼續看。

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

一個do-while句式不斷的尋找下一個消息msg.next,這裏可以看出這是隊列或鏈表的獲取下一個的方法。註釋顯示,尋找下一個異步方法。如果msg.isAsynchronous()爲真則退出循環。
之後就是now 和msg.when的比較,如果時間未到,則設一個超時時間。如果msg可以被處理,然後
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
這裏又可以看出來,如果prevMsg不爲空,就把下一個的指針指向當前處理msg的下一個消息,否則,這個類的mMessage直接設置爲msg.next.可以看出這裏的意思是在隊列中刪除這個msg,相當於出隊列。然後這個出隊的msg就不需要next節點信息了,置空,然後msg.markInUse,字面意思是標記在使用,至此,next()方法返回了消息。
之後是enqueueMessage,在Handler中我們看到,最終消息發送是走到這個方法的,所以這個方法也比較重要。

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 don't 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.target爲空,會報異常,又一次出現target,一會兒一定要找下是幹啥的。
第二句就是 if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
有些同學應該出現過這個異常,即在handleMessage時,收到的消息再次發送,會報這個錯誤,就是因爲這個標記。
然後在類的同步塊中,檢測是否當前線程looper在quit狀態中,是的話則報異常並回收msg。
之後msg.when = when, 發送消息時傳過來的時間參數放到了msg裏。
然後如果當前mMessages變量爲空或者延時時間爲0,或者新插入的msg消息的延時時間小於當前消息的時間,則把新來的消息插入到當前消息的前面作爲隊列頭部。

  if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;

否則,不斷的循環在隊列中查找延時時間大於新插入消息延時時間的節點。然後插入到隊列,這裏可以看出,此隊列是按時間順序排列的隊列,時間最短的在隊列最底部,時間長的在尾部,然後先進先出,即延時最少的消息先處理。
prev即

				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;

新消息插入隊列如圖所示,原P指針在最下方,現由於判斷條件新加入消息的when爲時間2,由prev保存原p節點,p節點= p.next, 然後找到msg2消息的延時時間小於Msg3,則插入到當前位置。

Message類源碼

這裏看出Message類繼承了Parcelable方法,是android推薦的序列化方法,用在跨進程傳輸數據上,常見的又aidl中的序列化。而這裏message需要不同的線程間切換,打到從一個線程傳到另一個線程處理的目的。

 *
 * Defines a message containing a description and arbitrary data object that can be
 * sent to a {@link Handler}.  This object contains two extra int fields and an
 * extra object field that allow you to not do allocations in many cases.
 *
 * <p class="note">While the constructor of Message is public, the best way to get
 * one of these is to call {@link #obtain Message.obtain()} or one of the
 * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
 * them from a pool of recycled objects.</p>
 */
public final class Message implements Parcelable

然後我們發現了令我們疑惑的
Handler target;
然後我們尋找Handler中的代碼,發現所有的obtainMessage中已經將Handler.this參數傳到Message, 如果不是obtainMessage,在最終發送消息時,即如下的enqueueMessage,也已經將msg.target 賦值this,即當前Handler。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();

if (mAsynchronous) {
    msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);

}
其實Handler的整個源碼還有待新的解讀,但是目前我們能瞭解這些已經算是足夠用了,下次有疑問的時候可以自己翻看源碼,這樣在我們以後的工作中,如果遇到什麼異常或者其他相關問題,能夠從原理上說出這種問題的原因,對我們的工作和個人水平會有很大的提高。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章