Handler 系列二:怎麼通訊

承接上一篇Handler系列一,上篇主要總結了Handler如何通訊,這篇來介紹Handler怎麼通訊。git

  • Handler的通訊機制
  • Handler,Looper,MessageQueue如何關聯

Handler 通訊機制

Handler 通訊機制

  • 建立Handler,並採用當前線程的Looper建立消息循環系統;
  • Handler經過sendMessage(Message)或Post(Runnable)發送消息,調用enqueueMessage把消息插入到消息鏈表中;
  • Looper循環檢測消息隊列中的消息,如有消息則取出該消息,並調用該消息持有的handler的dispatchMessage方法,回調到建立Handler線程中重寫的handleMessage裏執行。

Handler 如何關聯 Looper、MessageQueue

Handler及其關聯的類圖github

Handler及其關聯的類圖

以上類圖能夠快速幫助咱們理清Handler與Looper、MessageQueue的關係,如下從源碼的角度慢慢分析:segmentfault

一、Handler 發送消息

上一段很熟悉的代碼:數組

Message msg =Message.obtain();  //從全局池中返回一個message實例,避免屢次建立message(如new Message)
 msg.obj = data;
 msg.what=1;   //標誌消息的標誌
 handler.sendMessage(msg);

從sendMessageQueue開始追蹤,函數調用關係:sendMessage -> sendMessageDelayed ->sendMessageAtTime,在sendMessageAtTime中,攜帶者傳來的message與Handler的mQueue一塊兒經過enqueueMessage進入隊列了。less

對於postRunnable而言,經過post投遞該runnable,調用getPostMessage,經過該runnable構造一個message,再經過 sendMessageDelayed投遞,接下來和sendMessage的流程同樣了。async

二、消息入隊列

在enqueueMessage中,經過MessageQueue入隊列,併爲該message的target賦值爲當前的handler對象,記住msg.target很重要,以後Looper取出該消息時,還須要由msg.target.dispatchMessage回調到該handler中處理消息。ide

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

在MessageQueue中,由Message的消息鏈表進行入隊列函數

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

三、Looper 處理消息

再說處理消息以前,先看Looper是如何構建與獲取的:
  • 構造Looper時,構建消息循環隊列,並獲取當前線程oop

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
          mThread = Thread.currentThread();
    }
  • 但該函數是私有的,外界不能直接構造一個Looper,而是經過Looper.prepare來構造的:post

    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對象保存在sThreadLocal中,那sThreadLocal是什麼呢?

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    它是一個保存Looper的TheadLocal實例,而ThreadLocal是線程私有的數據存儲類,能夠來保存線程的Looper對象,這樣Handler就能夠經過ThreadLocal來保存於獲取Looper對象了。

  • TheadLocal 如何保存與獲取Looper?

    public void set(T value) {

    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);

    }

    public T get() {

    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) {
            return (T) table[index + 1];
        }
    } else {
        values = initializeValues(currentThread);
    }
    
    return (T) values.getAfterMiss(this);

    }

在 set 中都是經過 values.put 保存當前線程的 Looper 實例,經過 values.getAfterMiss(this)獲取,其中putgetAfterMiss都有keyvalue,都是由Value對象的table數組保存的,那麼在table數組裏怎麼存的呢?

table[index] = key.reference;
  table[index + 1] = value;

很顯然在數組中,前一個保存着ThreadLocal對象引用的索引,後一個存儲傳入的Looper實例。

接下來看Looper在loop中如何處理消息

loop中,一個循環,經過next取出MessageQueue中的消息

  • 若取出的消息爲null,則結束循環,返回。

    • 設置消息爲空,能夠經過MessageQueue的quit和quitSafely方法通知消息隊列退出。
  • 若取出的消息不爲空,則經過msg.target.dispatchMessage回調到handler中去。
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
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        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();
    }
}

四、handler處理消息

Looper把消息回調到handler的dispatchMessage中進行消息處理:

  • 若該消息有callback,即經過Post(Runnable)的方式投遞消息,由於在投遞runnable時,把runnable對象賦值給了message的callback
  • 若handler的mCallback不爲空,則交由經過callback建立handler方式去處理。
  • 不然,由最多見建立handler對象的方式,在重寫handlerMessage中處理。
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

總結

以一個時序圖來總結handler的消息機制,包含上述如何關聯Looper和MessageQueue的過程。

Handler-Looper-MessageQueue時序圖

Handler-Looper-MessageQueue時序圖

本文發表於我的博客:http://lavnfan.github.io/,歡迎指教。

相關文章
相關標籤/搜索