史上最全的 Handler 源碼解析(Java 層)

微信公衆號:碼小豬
碼小豬博客:www.hchstudio.cnnode

從很早開始就認識到 Handler 了,只不過那時修爲尚淺,瞭解的不夠深入,也沒有應用自如。不過隨着工做時間的增加,對 Handler 又有了更深層次的認識,因而有了這篇博客,但願儘量的總結出多的知識點。緩存

Handler 在 Java 層源碼主要有 4 個類:Looper、MessageQueue、Message、Handler。我概括了他們的幾個主要知識點:安全

  • Looper:sThreadLocal、Looper.loop();
  • Message:數據結構、消息緩存池;
  • MessageQueue:enqueueMessage、next、管道等待、同步消息隔離、idleHandler;
  • Handler:send/post、dispatchMessage 消息處理優先級。

Looper

Looper 數據結構

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class

final MessageQueue mQueue;

// sThreadLocal
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) { throw Exception ... }
    sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

// sMainLooper
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) { throw Exception ...}
        sMainLooper = myLooper();
    }
}
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

// mQueue
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
}
複製代碼
  • sThreadLocal:靜態常量,保證一個線程只有一個 Looper;
  • sMainLooper:靜態變量,在 prepareMainLooper 中賦值當前線程 Looper;
  • mQueue:變量,Looper 構造函數中初始化,由於一個線程只有一個 Looper,因此也一樣只有一個 mQueue。

經過以上分析,咱們能夠總結出一下特性:微信

  1. Looper、MessageQueue 是線程惟一的;
  2. 一個進程只有一個 sMainLooper;
  3. 根據 ThreadLocal 的特性,可經過 myLooper 方法獲取當前線程的 Looper。

Looper.loop()

public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) { 
        Message msg = queue.next();
        ...
        msg.target.dispatchMessage(msg);
        ...
        msg.recycleUnchecked();
    }
}
複製代碼

Looper.loop() 方法雖然看起來不少,其實他主要就作了三件事:數據結構

  1. 從消息隊列中獲取下一個消息;
  2. msg.target 就是 handler,經過 dispatchMessage 方法把消息分發下去,這個方法下面會有說到;
  3. 消息回收,放到消息緩存池裏。這裏須要注意的是 Message 對象並無釋放,會緩存起來。

Message

Message 數據結構

public int what, arg1, arg2;
public Object obj;
public Messenger replyTo;
int flags;
long when;      // 消息發送時間
Bundle data;
Handler target;
Runnable callback;

Message next;

private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;

private static final int MAX_POOL_SIZE = 50;
複製代碼
  • 看到 next 變量,咱們會想到單鏈表,其實 Message 就至關於單鏈表的 node,MessageQueue 就是一個單鏈表了,會持有表頭的引用;
  • what、arg一、arg二、obj、data 就是咱們發送的一些信息;
  • 值得注意的是 target,他是 Handler 類型,就是本消息的 Handler,會在 Handler 發送消息的時候賦值;
  • 後面的四個對象,都是和消息緩存池有關的。

Message 消息緩存池

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

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 爲鏈表頭引用,最大容量爲 50;
  • 回收消息時,會把消息裏面全部參數重置,並把當前消息設爲鏈表頭;
  • 獲取消息時,返回當前鏈表頭,並把 next 置空。

MessageQueue

插入隊列

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) {
            // 做爲表頭,若是隊列是阻塞狀態則須要喚醒
            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; // 插入消息
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
複製代碼

主要做爲插入隊列的方法,有下列幾個特性:異步

  • 把消息加入消息隊列,若是當前表頭爲空,則把消息做爲表頭引用;若是不爲空,則會根據時間的順序,插入到對應的時間中;
  • nativeWake 是調用底層在管道中寫操做以喚醒,在隊列不是阻塞的狀態下是不須要喚醒的;
  • 另外注意其中用了 synchronized 關鍵字,說明消息隊列的插入是線性安全的,刪除也是線性安全的,以後咱們會說到。

MessageQueue.next()

for (;;) {
    nativePollOnce(ptr, nextPollTimeoutMillis);
    synchronized (this) {
        final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) {
            // 若是有同步消息隔離,則會優先查找異步消息
            do {
                prevMsg = msg;
                msg = msg.next;
            } while (msg != null && !msg.isAsynchronous());
        }
        if (msg != null) {
            if (now < msg.when) {
                // 計算距離下一個消息的時間
                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 {
            // 沒有更多消息的時候,nextPollTimeoutMillis 會置爲 1。
            nextPollTimeoutMillis = -1;
        }

        ...
    }

    // 若是目前沒有消息,已經處在空閒狀態,則執行 idler.queueIdle
    for (int i = 0; i < pendingIdleHandlerCount; i++) {
        final IdleHandler idler = mPendingIdleHandlers[i];
        mPendingIdleHandlers[i] = null; // release the reference to the handler

        boolean keep = false;
        try {
            keep = idler.queueIdle();
        } catch (Throwable t) {
            Log.wtf(TAG, "IdleHandler threw exception", t);
        }

        if (!keep) {
            synchronized (this) {
                mIdleHandlers.remove(idler);
            }
        }
    }
    ...
}
複製代碼

此方法會從消息隊列中讀取下一個消息返回,主要作了如下操做:函數

  • nativePollOnce 函數會調用底層管道操做函數,nextPollTimeoutMillis 爲 -1 時,會阻塞,爲 0 時不會阻塞,大於 0 時,會阻塞相應的時間;
  • 若是有同步消息隔離,則會優先查找異步消息;
  • 獲取當前時間隊列的消息,並返回;
  • 若是隊列沒有任何消息,則會執行 idler.queueIdle,通知監聽者當前隊列處於空閒狀態。

同步消息隔離

上面咱們有提到了同步消息隔離,這裏咱們介紹一下。同步隔離,有時候也能夠叫異步消息,說的是一個意思。在源碼中主要用於優先更新 UI。oop

private IdleHandler[] mPendingIdleHandlers;

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // 向消息隊列中加入一個 handler 爲空的消息
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}
複製代碼

如上 postSyncBarrier 函數中會向消息隊列中加入一個 handler(即 Message 的 target) 爲空的消息做爲標識。在咱們上面 MessageQueue.next() 的函數中,當 msg.target == null 時,會優先獲取異步消息並返回。
所以想要使用異步消息有兩個條件:post

  1. 消息爲異步消息,即 msg.isAsynchronous() 返回 false;
  2. 須要獲取當前隊列並運行 postSyncBarrier() 函數。

IdleHandler

Handler 還提供了消息隊列空閒狀態通知。ui

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}
複製代碼

IdleHandler 的源碼比較簡單,就是一個 ArrayList,而後進行增長刪除操做。注意,這個也是線性安全的。

Handler

post/sendMessage

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

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

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

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
  • sendMessage 和 post 最本質的區別是以後處理任務時的優先級,post 會處理 Runnable 中的任務,而 sendMessage 會回調給 handler 處理;
  • 他們最終都會走 enqueueMessage 方法,並設置當前 Handler 爲 msg.target。

dispatchMessage

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

任務執行時就會運行這個函數,主要是一個優先級的問題:

  1. callback 優先級最高,也就是 post 發送的消息
  2. mCallback.handleMessage(msg),優先級第二
  3. handleMessage(msg),優先級第三

關注咱們
相關文章
相關標籤/搜索