溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時間:2020-03-01 06:12:39 來源:網(wǎng)絡(luò) 閱讀:214 作者:Android丶VG 欄目:移動開發(fā)

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

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

參考:

帶你手把手重讀 Handler 源碼,聊聊那些你所不知道一二
(更多完整項目下載。未完待續(xù)。源碼。圖文知識后續(xù)上傳github。)
可以點擊關(guān)于我聯(lián)系我獲取完整PDF
(VX:mm14525201314)

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

發(fā)送消息
post & sendMessage

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

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

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

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

這個包裝出來的 Message 將 callback 設(shè)置為了對應(yīng)的 Runnable。

而所有的 sendMessage 和 post 方法,實際上最后都通過 sendMessageDelayed 方法調(diào)用到了 sendMessageAtTime 方法:

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

sendMessageAtTime 中,它首先通過 mQueue 拿到了對應(yīng)的 MessageQueue 對象,然后調(diào)用了 enqueueMessage 方法將 Message 發(fā)送至 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);
}

最后實際上是調(diào)用到了 MessageQueueenqueueMessage 方法將這個消息傳入了 MessageQueue。它將 Message 的 target 設(shè)置為了當(dāng)前的 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 是什么時候被賦值的呢?點進去看可以發(fā)現(xiàn)它的賦值是在 Handler 的構(gòu)造函數(shù)中進行的。也就是說創(chuàng)建的 Handler 時若將 async 置為 true 則該 Handler 發(fā)出的 Message 都會被設(shè)為 Async,也就是『異步消息』。

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

關(guān)于異步消息和同步消息是什么,我們放在后面討論。

Handler 有很多種構(gòu)造函數(shù),但其他的構(gòu)造函數(shù)最后仍然會調(diào)用到上述的兩種構(gòu)造函數(shù),其 async 默認會被設(shè)置為 false。

讓我們看看上述的兩種構(gòu)造函數(shù):

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

可以看到這個構(gòu)造函數(shù)主要是對 mLooper、mQueuemCallback、mAsynchronous 進行賦值,其中 mLooper 是通過 Looper.myLooper 方法獲取到的,另一種構(gòu)造函數(shù)除了 Looper 是通過外部傳入以外和這個構(gòu)造函數(shù)的實現(xiàn)差不多。同時我們還能看出,mQueue 這個 MessageQueueLooper 對象內(nèi)部的一個成員變量。

消息入隊
enqueueMessage

我們接著看看 Handler 發(fā)送了消息后 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 構(gòu)成的鏈表,每次插入數(shù)據(jù)都會按時間順序進行插入,也就是說 MessageQueue 中的 Message 都是按照時間排好序的,這樣的話就使得循環(huán)取出 Message 的時候只需要一個個地從前往后拿即可,這樣 Message 都可以按時間先后順序被消費。

最后在需要喚醒的情況下會調(diào)用 nativeWake 這個 native 方法用于進行喚醒,這些和喚醒機制有關(guān)的代碼我們后面再進行討論,先暫時放在一邊。

消息循環(huán)

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

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

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

那么這個 Looper 對象是何時創(chuàng)建又何時放入這個 ThreadLocal 中的呢?

我們通過跟蹤可以發(fā)現(xiàn)它實際上是通過 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));
}

那按道理來說我們在應(yīng)用程序中并沒有調(diào)用 Looper.prepare 方法,為何還能通過主線程的 Handler 發(fā)送 Message 到主線程呢?

其實這個 Looper.prepare 方法在主線程創(chuàng)建時就已經(jīng)被創(chuàng)建并調(diào)用了 prepare 方法進行設(shè)置,具體我們可以看到 ActivityThread 類的 main 函數(shù):

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

這個 main 函數(shù)其實就是我們進程的入口,可以看出來它首先調(diào)用了 Looper.prepareMainLooper 創(chuàng)建了主線程的 Looper 并傳入 ThreadLocal,自此我們就可以在主線程發(fā)送消息了。為什么要這樣設(shè)計呢?因為其實我們的 View 繪制事件等都是通過主線程的 Handler 來進行調(diào)度的。

我們接著看到 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();
    }
}

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

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

消息遍歷

我們接著看到消息的遍歷過程,它不斷地從 MessageQueue 中調(diào)用 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 方法內(nèi),它在不斷地進行著循環(huán),在 1 處它先調(diào)用了一次 nativePollOnce 這個 native 方法,它與 Handler 的阻塞喚醒機制有關(guān),我們后面再進行介紹。

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

之后便在注釋 3 處判斷判斷當(dāng)前消息是否到了應(yīng)該發(fā)送的時間,若到了應(yīng)該發(fā)送的時間,就會將該消息取出并返回,否則僅僅是將 nextPollTimeoutMillis 置為了剩余的時間(這里為了防止 int 越界做了防越界處理)

之后在注釋 4 處,第一次循環(huán)的前提下,若 MessageQueue 為空或者消息未來才會執(zhí)行,則會嘗試去執(zhí)行一些 idleHandler,并在執(zhí)行后將 pendingIdleHandlerCount 置為 0 避免下次再次執(zhí)行。

若這一次拿到的消息不是現(xiàn)在該執(zhí)行的,那么會再次調(diào)用到 nativePollOnce,并且此次的 nextPollTimeoutMillis 不再為 0 了,這與我們后面會提到的阻塞喚醒機制有關(guān)。

消息的處理

消息的處理是通過 Handler 的 dispatchMessage 實現(xiàn)的:

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

它優(yōu)先調(diào)用了 Message 的 callback,若沒有 callback 則會調(diào)用 Handler 中 Callback 的 handleMessage 方法,若其仍沒定義則最終會調(diào)用到 Handler 自身所實現(xiàn)的 handleMessage 方法。

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

同步屏障機制

Handler 中存在著一種叫做同步屏障的機制,它可以實現(xiàn)異步消息優(yōu)先執(zhí)行的功能,讓我們看看它是如何實現(xiàn)的。

加入同步屏障

在 Handler 中還存在了一種特殊的消息,它的 target 為 null,并不會被消費,僅僅是作為一個標(biāo)識處于 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 的消息的。因此這種同步屏障只能通過這個方法發(fā)出。

移除同步屏障

我們可以通過 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 中移除,一般執(zhí)行完了異步消息后就會通過該方法將同步屏障移除。

最后若需要喚醒,調(diào)用了 nativeWake 方法進行喚醒。

同步屏障的作用

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

阻塞喚醒機制

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

epoll

這個阻塞喚醒機制是基于 Linux 的 I/O 多路復(fù)用機制 epoll 實現(xiàn)的,它可以同時監(jiān)控多個文件描述符,當(dāng)某個文件描述符就緒時,會通知對應(yīng)程序進行讀/寫操作。
epoll 主要有三個方法,分別是 epoll_createepoll_ctl、epoll_wait

epoll_create
int epoll_create(int size)

其功能主要是創(chuàng)建一個 epoll 句柄并返回,傳入的 size 代表監(jiān)聽的描述符個數(shù)(僅僅是初次分配的 fd 個數(shù))

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

其功能是對 epoll 事件進行注冊,會對該 fd 執(zhí)行指定的 op 操作,參數(shù)含義如下:

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

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

epoll_event 是一個結(jié)構(gòu)體,里面的 events 代表了對應(yīng)文件操作符的操作,而 data 代表了用戶可用的數(shù)據(jù)。
其中 events 可取下面幾個值:

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

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

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

當(dāng)調(diào)用了該方法后,會進入阻塞狀態(tài),等待 epfd 上的 IO 事件,若 epfd 監(jiān)聽的某個文件描述符發(fā)生前面指定的 event 時,就會進行回調(diào),從而使得 epoll 被喚醒并返回需要處理的事件個數(shù)。若超過了設(shè)定的超時時間,同樣也會被喚醒并返回 0 避免一直阻塞。

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

native 初始化

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

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); //構(gòu)造喚醒事件的fd
    AutoMutex _l(mLock);
    rebuildEpollLocked(); 
}

可以看到,它調(diào)用了 rebuildEpollLocked 方法對 epoll 進行初始化,讓我們看看其實現(xiàn)

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

可以看到,這里首先關(guān)閉了舊的 epoll 描述符,之后又調(diào)用了 epoll_create 創(chuàng)建了新的 epoll 描述符,然后進行了一些初始化后,將 mWakeEventFdmRequests 中的 fd 都注冊到了 epoll 的描述符中,注冊的事件都是 EPOLLIN

這就意味著當(dāng)這些文件描述符其中一個發(fā)生了 IO 時,就會通知 epoll_wait 使其喚醒,那么我們猜測 Handler 的阻塞就是通過 epoll_wait 實現(xiàn)的。

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

native 阻塞實現(xiàn)

我們看看阻塞,它的實現(xiàn)就在我們之前看到的 MessageQueue::next 中,當(dāng)發(fā)現(xiàn)要返回的消息將來才會執(zhí)行,則會計算出當(dāng)下距離其將要執(zhí)行的時間還差多少毫秒,并調(diào)用 nativePollOnce 方法將返回的過程阻塞到指定的時間。

nativePollOnce 很顯然是一個 native 方法,它最后調(diào)用到了 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 層消息機制的處理,我們先暫時不關(guān)心,這里最后調(diào)用到了 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;
}

可以發(fā)現(xiàn),這里在 1 處調(diào)用了 epoll_wait方法,并傳入了我們之前在 natviePollOnce 方法傳入的當(dāng)前時間距下個任務(wù)執(zhí)行時間的差值。這就是我們的阻塞功能的核心實現(xiàn)了,調(diào)用該方法后,會一直阻塞,直到到達我們設(shè)定的時間或之前我們在 epollfd 中注冊的幾個 fd 發(fā)生了 IO。其實到了這里我們就可以猜到,nativeWake 方法就是通過對注冊的 mWakeEventFd 進行操作從而實現(xiàn)的喚醒。

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

native 喚醒

nativeWake 方法最后通過 NativeMessageQueue 的 wake 方法調(diào)用到了 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);
        }
    }
}

這里其實就是調(diào)用了 write 方法,對 mWakeEventFd 中寫入了 1,從而使得監(jiān)聽該fdpollOnce 方法被喚醒,從而使得 Java 中的 next 方法繼續(xù)執(zhí)行。

那我們再回去看看,在什么情況下,Java 層會調(diào)用 natvieWake 方法進行喚醒呢?

MessageQueue 類中調(diào)用 nativeWake 方法主要有下列幾個時機:

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

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

總結(jié)

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

  • Handler:事件的發(fā)送及處理者,在構(gòu)造方法中可以設(shè)置其 async,默認為 "默認為 true。若 async 為 false 則該 Handler 發(fā)送的 Message 均為異步消息,有同步屏障的情況下會被優(yōu)先處理"
  • Looper:一個用于遍歷 MessageQueue 的類,每個線程有一個獨有的 Looper,它會在所處的線程開啟一個死循環(huán),不斷從 MessageQueue 中拿出消息,并將其發(fā)送給 target 進行處理
  • MessageQueue:用于存儲 Message,內(nèi)部維護了 Message 的鏈表,每次拿取 Message 時,若該 Message 離真正執(zhí)行還需要一段時間,會通過 nativePollOnce 進入阻塞狀態(tài),避免資源的浪費。若存在消息屏障,則會忽略同步消息優(yōu)先拿取異步消息,從而實現(xiàn)異步消息的優(yōu)先消費。
    相關(guān)問題

    下面還有一些與 Handler 相關(guān)的常見問題,可以結(jié)合前面的內(nèi)容得到答案。

    問題 1

    Looper 是在主線程創(chuàng)建,同時其 loop 方法也是在主線程執(zhí)行,為什么這樣一個死循環(huán)卻不會阻塞主線程呢?

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

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

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

而這個問題的意思應(yīng)該是為何這樣一個死循環(huán)不會使得界面卡頓,這有兩個原因:

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

    Handler 的內(nèi)存泄漏是怎么回事?如何產(chǎn)生的呢?

首先,造成 Handler 的內(nèi)存泄漏往往是因為如下的這種代碼:

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

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

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

參考:

帶你手把手重讀 Handler 源碼,聊聊那些你所不知道一二
(更多完整項目下載。未完待續(xù)。源碼。圖文知識后續(xù)上傳github。)
可以點擊關(guān)于我聯(lián)系我獲取完整PDF
(VX:mm14525201314)
帶你手把手重讀 Handler 源碼,聊聊那些你所不知道一二

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI