帶你手把手重讀 Handler 源碼,聊聊那些你所不知道一二三

大家應該都知道,Android 的消息機制是基於 Handler 實現的。還記得一年前的自己就看了幾篇博客,知道了 Handler、Looper、MessageQueue 就自以爲了解了 Handler 的原理。但其實看源碼的過程中慢慢就會發現,Handler 的內容可不止這點, 像同步屏障、 Handler 的 native 層的阻塞喚醒機制等等這些知識以前就沒有理解清楚。因此寫下這篇文章,從頭開始重塑對 Handler 的印象。

覺得文章太長的可以找我拿了完整的PDF自行研究

參考:

帶你手把手重讀 Handler 源碼,聊聊那些你所不知道一二三
(更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。)
可以點擊關於我聯繫我獲取完整PDF
(VX:mm14525201314)

Handler 採用的是一種生產者-消費者模型,Handler 就是生產者,通過它可以生產需要執行的任務。而 Looper 則是消費者,不斷從 MessageQueue中取出 Message 對這些消息進行消費,下面我們看一下其具體的實現。

發送消息

post & sendMessage

首先我們都知道,Handler 對外主要有兩種方式來實現在其所在 Looper 所在線程執行指定 Runnable——post 及 sendMessage,它們都有對應的 delay 方法 。而不論是 post 還是 sendMessage,都會調用到 sendMessageDelayed方法。比如下面是 post 方法的實現:

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

可以看到它其實調用的仍然是 sendMessageDelayed 方法,只是通過 getPostMessage 方法將這個 Runnable 包裝成了一個 Message 對象。

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

這個包裝出來的 Message 將 callback 設置爲了對應的 Runnable。

而所有的 sendMessage 和 post 方法,實際上最後都通過 sendMessageDelayed 方法調用到了 sendMessageAtTime 方法:

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

sendMessageAtTime 中,它首先通過 mQueue 拿到了對應的 MessageQueue 對象,然後調用了 enqueueMessage 方法將 Message 發送至 MessageQueue 中。

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

最後實際上是調用到了 MessageQueueenqueueMessage 方法將這個消息傳入了 MessageQueue。它將 Message 的 target 設置爲了當前的 Handler,同時要注意看到,這裏在 enqueueMessage 之前先判斷了一下 mAsynchronous 是否爲 true,若爲 true 則將該 Message 的 Asynchronous 置爲 true。

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

那這個 mAsynchronous 是什麼時候被賦值的呢?點進去看可以發現它的賦值是在 Handler 的構造函數中進行的。也就是說創建的 Handler 時若將 async 置爲 true 則該 Handler 發出的 Message 都會被設爲 Async,也就是『異步消息』。

  • public Handler(Callback callback, boolean async)
  • public Handler(Looper looper, Callback callback, boolean async)

關於異步消息和同步消息是什麼,我們放在後面討論。

Handler 有很多種構造函數,但其他的構造函數最後仍然會調用到上述的兩種構造函數,其 async 默認會被設置爲 false。

讓我們看看上述的兩種構造函數:

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

可以看到這個構造函數主要是對 mLoopermQueuemCallbackmAsynchronous 進行賦值,其中 mLooper 是通過 Looper.myLooper 方法獲取到的,另一種構造函數除了 Looper 是通過外部傳入以外和這個構造函數的實現差不多。同時我們還能看出,mQueue 這個 MessageQueueLooper 對象內部的一個成員變量。

消息入隊

enqueueMessage

我們接着看看 Handler 發送了消息後 MessageQueueenqueueMessage 方法:

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

可以看到, MessageQueue 實際上裏面維護了一個 Message 構成的鏈表,每次插入數據都會按時間順序進行插入,也就是說 MessageQueue 中的 Message 都是按照時間排好序的,這樣的話就使得循環取出 Message 的時候只需要一個個地從前往後拿即可,這樣 Message 都可以按時間先後順序被消費。

最後在需要喚醒的情況下會調用 nativeWake 這個 native 方法用於進行喚醒,這些和喚醒機制有關的代碼我們後面再進行討論,先暫時放在一邊。

消息循環

那麼我們看看 Looper.myLooper 方法是如何獲取到 Looper 對象的呢?

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

可以看出來,這個 Looper 對象是通過 sThreadLocal.get 方法獲取到的,也就是說這個 Looper 是一個線程獨有的變量,每個線程具有一個不同的 Looper。

那麼這個 Looper 對象是何時創建又何時放入這個 ThreadLocal 中的呢?

我們通過跟蹤可以發現它實際上是通過 Looper.prepare 方法放入 ThreadLocal 中的:

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.prepare 方法,爲何還能通過主線程的 Handler 發送 Message 到主線程呢?

其實這個 Looper.prepare 方法在主線程創建時就已經被創建並調用了 prepare 方法進行設置,具體我們可以看到 ActivityThread 類的 main 函數:

public static void main(String[] args) {
    // ...
    Looper.prepareMainLooper();
    // ...
    Looper.loop();
    // ...
}

這個 main 函數其實就是我們進程的入口,可以看出來它首先調用了 Looper.prepareMainLooper 創建了主線程的 Looper 並傳入 ThreadLocal,自此我們就可以在主線程發送消息了。爲什麼要這樣設計呢?因爲其實我們的 View 繪製事件等都是通過主線程的 Handler 來進行調度的。

我們接着看到 Looper.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;
    // 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;
        }
        // ...
        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            // ...
        }
        // ...
        msg.recycleUnchecked();
    }
}

這裏其實是一個死循環,它的主要作用是遍歷 MessageQueue,獲取到 LooperMessageQueue 後,不斷通過 MessageQueue 的 next 方法獲取到消息列表中的下一個 Message,之後調用了 Message 的 target 的 dispatchMessage 方法對 Message 進行消費,最後對 Message 進行了回收。

通過上面的代碼可以看出,Looper 主要的作用是遍歷 MessageQueue,每找到一個 Message 都會調用其 target 的dispatchMessage 對該消息進行消費,這裏的 target 也就是我們之前發出該 Message 的 Handler。

消息遍歷

我們接着看到消息的遍歷過程,它不斷地從 MessageQueue 中調用 next 方法拿到消息,並對其進行消費,那我們具體看看 next 的過程:

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1;
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 1
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            // 2
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                // 3
                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 {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
            if (mQuitting) {
                dispose();
                return null;
            }
            // 4
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        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);
                }
            }
        }
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

上面的代碼比較長,我們一步步來進行分析

首先在 next 方法內,它在不斷地進行着循環,在 1 處它先調用了一次 nativePollOnce 這個 native 方法,它與 Handler 的阻塞喚醒機制有關,我們後面再進行介紹。

之後,在 2 處,它進行了一個非常特殊的處理。這裏判斷當前的消息是否是 target 爲 null 的消息,若 target 爲 null,則它會不斷地向下取 Message,直到遇到一個異步的消息。到這裏可能會有讀者覺得很奇怪了,明明在 enqueueMessage 中避免了 Message 的 target 爲 null,爲什麼這裏還會存在 target 爲 null 的消息呢?其實這與 Handler 的同步屏障機制有關,我們稍後介紹

之後便在註釋 3 處判斷判斷當前消息是否到了應該發送的時間,若到了應該發送的時間,就會將該消息取出並返回,否則僅僅是將 nextPollTimeoutMillis 置爲了剩餘的時間(這裏爲了防止 int 越界做了防越界處理)

之後在註釋 4 處,第一次循環的前提下,若 MessageQueue 爲空或者消息未來纔會執行,則會嘗試去執行一些 idleHandler,並在執行後將 pendingIdleHandlerCount 置爲 0 避免下次再次執行。

若這一次拿到的消息不是現在該執行的,那麼會再次調用到 nativePollOnce,並且此次的 nextPollTimeoutMillis 不再爲 0 了,這與我們後面會提到的阻塞喚醒機制有關。

消息的處理

消息的處理是通過 Handler 的 dispatchMessage 實現的:

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

它優先調用了 Message 的 callback,若沒有 callback 則會調用 Handler 中 Callback 的 handleMessage 方法,若其仍沒定義則最終會調用到 Handler 自身所實現的 handleMessage 方法。

因此我們在使用的時候可以根據自己的需求來重寫上面三者其中一個。

同步屏障機制

Handler 中存在着一種叫做同步屏障的機制,它可以實現異步消息優先執行的功能,讓我們看看它是如何實現的。

加入同步屏障

在 Handler 中還存在了一種特殊的消息,它的 target 爲 null,並不會被消費,僅僅是作爲一個標識處於 MessageQueue 中。它就是 SyncBarrier (同步屏障)這種特殊的消息。我們可以通過 MessageQueue::postSyncBarrier 方法將其加入消息隊列。

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    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;
    }
}

可以看到,這裏並沒有什麼特殊的,只是將一個 target 爲 null 的消息加入了消息隊列中,但我們在前面的 enqueueMessage 方法中也看到了,普通的 enqueue 操作是沒有辦法在消息隊列中放入這樣一個 target 爲 null 的消息的。因此這種同步屏障只能通過這個方法發出。

移除同步屏障

我們可以通過 removeSyncBarrier 方法來移除消息屏障。

public void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        // 找到 target 爲 null 且 token 相同的消息
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();
        // If the loop is quitting then it is already awake.
        // We can assume mPtr != 0 when mQuitting is false.
        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

這裏主要是將同步屏障從 MessageQueue 中移除,一般執行完了異步消息後就會通過該方法將同步屏障移除。

最後若需要喚醒,調用了 nativeWake 方法進行喚醒。

同步屏障的作用

而看了前面 MessageQueue::next 的代碼我們知道,當 MessageQueue 中遇到了一個同步屏障,則它會不斷地忽略後面的同步消息直到遇到一個異步的消息,這樣設計的目的其實是爲了使得當隊列中遇到同步屏障時,則會使得異步的消息優先執行,這樣就可以使得一些消息優先執行。比如 View 的繪製過程中的 TraversalRunnable 消息就是異步消息,在放入隊列之前先放入了一個消息屏障,從而使得界面繪製的消息會比其他消息優先執行,避免了因爲 MessageQueue 中消息太多導致繪製消息被阻塞導致畫面卡頓,當繪製完成後,就會將消息屏障移除。

阻塞喚醒機制

從前面可以看出來 Handler 中其實還存在着一種阻塞喚醒機制,我們都知道不斷地進行循環是非常消耗資源的,有時我們 MessageQueue 中的消息都不是當下就需要執行的,而是要過一段時間,此時如果 Looper 仍然不斷進行循環肯定是一種對於資源的浪費。因此 Handler 設計了這樣一種阻塞喚醒機制使得在當下沒有需要執行的消息時,就將 Looper 的 loop 過程阻塞,直到下一個任務的執行時間到達或者一些特殊情況下再將其喚醒,從而避免了上述的資源浪費。

epoll

這個阻塞喚醒機制是基於 Linux 的 I/O 多路複用機制 epoll 實現的,它可以同時監控多個文件描述符,當某個文件描述符就緒時,會通知對應程序進行讀/寫操作。
epoll 主要有三個方法,分別是 epoll_createepoll_ctlepoll_wait

epoll_create
int epoll_create(int size)

其功能主要是創建一個 epoll 句柄並返回,傳入的 size 代表監聽的描述符個數(僅僅是初次分配的 fd 個數)

epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

其功能是對 epoll 事件進行註冊,會對該 fd 執行指定的 op 操作,參數含義如下:

  • epfd:epoll 的句柄值(也就是 epoll_create 的返回值)
  • op:對 fd 執行的操作

    • EPOLL_CTL_ADD:註冊 fd 到 epfd
    • EPOLL_CTL_DEL:從 epfd 中刪除 fd
    • EPOLL_CTL_MOD:修改已註冊的 fd 的監聽事件
  • fd:需要監聽的文件描述符
  • epoll_event:需要監聽的事件

epoll_event 是一個結構體,裏面的 events 代表了對應文件操作符的操作,而 data 代表了用戶可用的數據。
其中 events 可取下面幾個值:

  • EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
  • EPOLLOUT:表示對應的文件描述符可以寫;
  • EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外部數據來);
  • EPOLLERR:表示對應的文件描述符發生錯誤;
  • EPOLLHUP:表示對應的文件描述符被掛斷;
  • EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
  • EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏
    epoll_wait
    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

    其功能是等待事件的上報,參數含義如下:

  • epfd:epoll 的句柄值
  • events:從內核中得到的事件集合
  • maxevents:events 數量,不能超過 create 時的 size
  • timeout:超時時間

當調用了該方法後,會進入阻塞狀態,等待 epfd 上的 IO 事件,若 epfd 監聽的某個文件描述符發生前面指定的 event 時,就會進行回調,從而使得 epoll 被喚醒並返回需要處理的事件個數。若超過了設定的超時時間,同樣也會被喚醒並返回 0 避免一直阻塞。

而 Handler 的阻塞喚醒機制就是基於上面的 epoll 的阻塞特性,我們來看看它的具體實現。

native 初始化

在 Java 中的 MessageQueue 創建時會調用到 nativeInit 方法,在 native 層會創建 NativeMessageQueue 並返回其地址,之後都是通過這個地址來與該 NativeMessageQueue 進行通信(也就是 MessageQueue 中的 mPtr,類似 MMKV 的做法),而在 NativeMessageQueue 創建時又會創建 Native 層下的 Looper,我們看到 Native 下的 Looper 的構造函數:

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd = eventfd(0, EFD_NONBLOCK); //構造喚醒事件的fd
    AutoMutex _l(mLock);
    rebuildEpollLocked(); 
}

可以看到,它調用了 rebuildEpollLocked 方法對 epoll 進行初始化,讓我們看看其實現

void Looper::rebuildEpollLocked() {
    if (mEpollFd >= 0) {
        close(mEpollFd); 
    }
    mEpollFd = epoll_create(EPOLL_SIZE_HINT); 
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event));
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);

    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);
        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
    }
}

可以看到,這裏首先關閉了舊的 epoll 描述符,之後又調用了 epoll_create 創建了新的 epoll 描述符,然後進行了一些初始化後,將 mWakeEventFdmRequests 中的 fd 都註冊到了 epoll 的描述符中,註冊的事件都是 EPOLLIN

這就意味着當這些文件描述符其中一個發生了 IO 時,就會通知 epoll_wait 使其喚醒,那麼我們猜測 Handler 的阻塞就是通過 epoll_wait 實現的。

同時可以發現,Native 層也是存在 MessageQueueLooper 的,也就是說 ative 層實際上也是有一套消息機制的,這些我們到後面再進行介紹。

native 阻塞實現

我們看看阻塞,它的實現就在我們之前看到的 MessageQueue::next 中,當發現要返回的消息將來纔會執行,則會計算出當下距離其將要執行的時間還差多少毫秒,並調用 nativePollOnce 方法將返回的過程阻塞到指定的時間。

nativePollOnce 很顯然是一個 native 方法,它最後調用到了 Looper 這個 native 層類的 pollOnce 方法。

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
                if (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }
        if (result != 0) {
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }
        result = pollInner(timeoutMillis);
    }
}

前面主要是一些對 Native 層消息機制的處理,我們先暫時不關心,這裏最後調用到了 pollInner 方法:

int Looper::pollInner(int timeoutMillis) {
    // ...
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;
    mPolling = true; 
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    // 1
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // ...
    return result;
}

可以發現,這裏在 1 處調用了 epoll_wait方法,並傳入了我們之前在 natviePollOnce 方法傳入的當前時間距下個任務執行時間的差值。這就是我們的阻塞功能的核心實現了,調用該方法後,會一直阻塞,直到到達我們設定的時間或之前我們在 epollfd 中註冊的幾個 fd 發生了 IO。其實到了這裏我們就可以猜到,nativeWake 方法就是通過對註冊的 mWakeEventFd 進行操作從而實現的喚醒。

後面主要是一些對 Native 層消息機制的處理,這篇文章暫時不關注,它的邏輯和 Java 層是基本一致的。

native 喚醒

nativeWake 方法最後通過 NativeMessageQueue 的 wake 方法調用到了 Native 下 Looper 的 wake 方法:

void Looper::wake() {
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

這裏其實就是調用了 write 方法,對 mWakeEventFd 中寫入了 1,從而使得監聽該fdpollOnce 方法被喚醒,從而使得 Java 中的 next 方法繼續執行。

那我們再回去看看,在什麼情況下,Java 層會調用 natvieWake 方法進行喚醒呢?

MessageQueue 類中調用 nativeWake 方法主要有下列幾個時機:

  • 調用 MessageQueue 的 quit 方法進行退出時,會進行喚醒
  • 消息入隊時,若插入的消息在鏈表最前端(最早將執行)或者有同步屏障時插入的是最前端的異步消息(最早被執行的異步消息)
  • 移除同步屏障時,若消息列表爲空或者同步屏障後面不是異步消息時

可以發現,主要是在可能不再需要阻塞的情況下進行喚醒。(比如加入了一個更早的任務,那繼續阻塞顯然會影響這個任務的執行)

總結

Android 的消息機制在 Java 層及 Native 層均是由 HandlerLooperMessageQueue 三者構成
帶你手把手重讀 Handler 源碼,聊聊那些你所不知道一二三

  • Handler:事件的發送及處理者,在構造方法中可以設置其 async,默認爲 "默認爲 true。若 async 爲 false 則該 Handler 發送的 Message 均爲異步消息,有同步屏障的情況下會被優先處理"
  • Looper:一個用於遍歷 MessageQueue 的類,每個線程有一個獨有的 Looper,它會在所處的線程開啓一個死循環,不斷從 MessageQueue 中拿出消息,並將其發送給 target 進行處理
  • MessageQueue:用於存儲 Message,內部維護了 Message 的鏈表,每次拿取 Message 時,若該 Message 離真正執行還需要一段時間,會通過 nativePollOnce 進入阻塞狀態,避免資源的浪費。若存在消息屏障,則會忽略同步消息優先拿取異步消息,從而實現異步消息的優先消費。

    相關問題

    下面還有一些與 Handler 相關的常見問題,可以結合前面的內容得到答案。

    問題 1

    Looper 是在主線程創建,同時其 loop 方法也是在主線程執行,爲什麼這樣一個死循環卻不會阻塞主線程呢?

我們看到 ActivityThread 中,它實際上是有一個 handleMessage 方法,其實 ActivityThread 就是一個 Handler,我們在使用的過程中的很多事件(如 Activity、Service 的各種生命週期)都在這裏的各種 Case 中,也就是說我們平時說的主線程其實就是依靠這個 Looper 的 loop 方法來處理各種消息,從而實現如 Activity 的聲明週期的回調等等的處理,從而回調給我們使用者。

public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case XXX:
                // ...
            }
            Object obj = msg.obj;
            if (obj instanceof SomeArgs) {
                ((SomeArgs) obj).recycle();
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }
    }

因此不能說主線程不會阻塞,因爲主線程本身就是阻塞的,其中所有事件都由主線程進行處理,從而使得我們能在這個循環的過程中作出自己的各種處理(如 View 的繪製等)。

而這個問題的意思應該是爲何這樣一個死循環不會使得界面卡頓,這有兩個原因:

  • 界面的繪製本身就是這個循環內的一個事件
  • 界面的繪製是通過了同步屏障保護下發送的異步消息,會被主線程優先處理,因此使得界面繪製擁有了最高的優先級,不會因爲 Handler 中事件太多而造成卡頓。
    問題 2

    Handler 的內存泄漏是怎麼回事?如何產生的呢?

首先,造成 Handler 的內存泄漏往往是因爲如下的這種代碼:

public class XXXActivity extends BaseActivity {
    // ...
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // 一些處理
        }
    };
    // ...
}

那這樣爲什麼會造成內存泄漏呢?
我們都知道,匿名內部類會持有外部類的引用,也就是說這裏的 Handler 會持有其外部類 XXXActivity 的引用。而我們可以回憶一下 sendMessage 的過程中,它會將 Message 的 target 設置爲 Handler,也就是說明這個 Message 持有了 mHandler 的引用。那麼我們假設通過 mHandler 發送了一個 2 分鐘後的延時消息,在兩分鐘還沒到的時候,我們關閉了界面。按道理來說此時 Activity 可以被 GC 回收,但由於此時 Message 還處於 MessageQueue 中,MessageQueue 這個對象持有了 Message 的引用,Message 又持有了我們的 Handler 的引用,同時由於 Handler 又持有了其外部類 XXXActivity 的引用。這就導致此時 XXXActivity 仍然是可達的,因此導致 XXXActivity 無法被 GC 回收,這就造成了內存泄漏。

因此我們使用 Handler 最好將其定義爲 static 的,避免其持有外部類的引用導致類似的內存泄漏問題。如果此時還需要用到 XXXActivity 的一些信息,可以通過 WeakReference 來使其持有 Activity 的弱引用,從而可以訪問其中的某些信息,又避免了內存泄漏問題。

參考:

帶你手把手重讀 Handler 源碼,聊聊那些你所不知道一二三
(更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。)
可以點擊關於我聯繫我獲取完整PDF
(VX:mm14525201314)
帶你手把手重讀 Handler 源碼,聊聊那些你所不知道一二三

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