Android多執行緒之Handler、Looper與MessageQueue原始碼解析

葉應是葉發表於2018-06-21

本文的目的是來分析下 Android 系統中以 Handler、Looper、MessageQueue 組成的非同步訊息處理機制,通過原始碼來了解整個訊息處理流程的走向以及相關三者之間的關係

需要先了解以下幾個預備知識

  • Handler:UI 執行緒或者子執行緒通過 Handler 向 MessageQueue(訊息佇列) 傳送 Message
  • MessageQueue:通過 Handler 傳送的訊息並非是立即執行的,需要存入一個訊息佇列中來依次執行
  • Looper:Looper 不斷從 MessageQueue 中獲取訊息並將之傳遞給訊息處理者(即是訊息傳送者 Handler 本身)進行處理
  • 互斥機制:可能會有多條執行緒(1條 UI 執行緒,n 條子執行緒)向同一個訊息佇列插入訊息,此時就需要進行同步

Handler 傳送訊息的形式主要有以下幾種形式,其最終呼叫的都是 sendMessageAtTime() 方法

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

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

可以看到 sendMessageAtTime() 方法中需要一個已初始化的 MessageQueue 型別的全域性變數 mQueue,否則程式無法繼續走下去

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

mQueue 變數是在建構函式中進行初始化的,且 mQueue 是成員常量,這說明 HandlerMessageQueue 是一一對應的關係,不可更改

如果建構函式沒有傳入 Looper 引數,則會預設使用當前執行緒關聯的 Looper 物件,mQueue 需要依賴於從 Looper 物件中獲取,如果 Looper 物件為 null ,則會直接丟擲異常,且從異常資訊 Can`t create handler inside thread that has not called Looper.prepare() 中可以看到,在向 Handler 傳送訊息前,需要先呼叫 Looper.prepare()

    public Handler(Callback callback, boolean async) {
        ···
        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;
    }

走進 Looper 類中,可以看到,myLooper() 方法是從 sThreadLocal 物件中獲取 Looper 物件的,sThreadLocal 物件又是通過 prepare(boolean) 來進行賦值的,且該方法只允許呼叫一次,一個執行緒只能建立一個 Looper 物件,否則將丟擲異常

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

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

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

此處除了因為prepare(boolean)多次呼叫會丟擲異常導致無法關聯多個 Looper 外,Looper 類的建構函式也是私有的,且在建構函式中還初始化了一個執行緒常量 mThread,這都說明了 Looper 只能關聯到一個執行緒,且關聯之後不能改變

    final Thread mThread;    

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

那麼 Looper.prepare(boolean) 方法又是在哪裡呼叫的呢?查詢該方法的所有引用,可以發現在 Looper 類中有如下方法,從名字來看,可以猜測該方法是由主執行緒來呼叫的,查詢其引用

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

最後定位到 ActivityThread 類的 main() 方法

看到 main() 函式的方法簽名,可以知道該方法就是一個應用的起始點,即當應用啟動時, 系統就自動為我們在主執行緒做好了 Handler 的初始化操作, 因此在主執行緒裡我們可以直接使用 Handler

如果是在子執行緒中建立 Handler ,則需要我們手動來呼叫 Looper.prepare() 方法

    public static void main(String[] args) {
        ···
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

回到最開始,既然 Looper 物件已經由系統來為我們初始化好了,那我們就可以從中得到 mQueue物件

    public Handler(Callback callback, boolean async) {
        ···
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can`t create handler inside thread that has not called Looper.prepare()");
        }
        //獲取 MessageQueue 物件
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

mQueue 又是在 Looper 類的建構函式中初始化的,且 mQueueLooper 類的成員常量,這說明 Looper 與 MessageQueue 是一一對應的關係

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

sendMessageAtTime() 方法中在處理 Message 時,最終呼叫的是 enqueueMessage() 方法

當中,需要注意 msg.target = this 這句程式碼,target 物件指向了傳送訊息的主體,即 Handler 物件本身,即由 Handler 物件發給 MessageQueue 的訊息最後還是要交由 Handler 物件本身來處理

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

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //target 物件指向的也是傳送訊息的主體,即 Handler 物件
        //即由 Handler 物件發給 MessageQueue 的訊息最後還是要交由 Handler 物件本身來處理
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

因為存在多個執行緒往同一個 Loop 執行緒的 MessageQueue 中插入訊息的可能,所以 enqueueMessage() 內部需要進行同步。可以看出 MessageQueue 內部是以連結串列的結構來儲存 Message 的(Message.next),根據 Message 的延時時間的長短來將決定其在訊息佇列中的位置

mMessages 代表的是訊息佇列中的第一條訊息,如果 mMessages 為空,說明訊息佇列是空的,或者 mMessages 的觸發時間要比新訊息晚,則將新訊息插入訊息佇列的頭部;如果 mMessages 不為空,則尋找訊息列隊中第一條觸發時間比新訊息晚的非空訊息,並將新訊息插到該訊息前面

到此,一個按照處理時間進行排序的訊息佇列就完成了,後邊要做的就是從訊息佇列中依次取出訊息進行處理了

boolean enqueueMessage(Message msg, long when) {
        //Message 必須有處理者
        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 並回撥給 Handler 的

在 MessageQueue 中訊息的讀取其實是通過內部的 next() 方法進行的,next() 方法是一個無限迴圈的方法,如果訊息佇列中沒有訊息,則該方法會一直阻塞,當有新訊息來的時候 next() 方法會返回這條訊息並將其從單連結串列中刪除

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

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                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);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            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);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

next() 方法又是通過 Looper 類的 loop() 方法來迴圈呼叫的,而 loop() 方法也是一個無限迴圈,唯一跳出迴圈的條件就是 queue.next() 方法返回為null ,細心的讀者可能已經發現了,loop() 就是在 ActivityThreadmain()函式中呼叫的

因為 next() 方法是一個阻塞操作,所以當沒有訊息也會導致 loop() 方法一隻阻塞著,而當 MessageQueue 一中有了新的訊息,Looper 就會及時地處理這條訊息並呼叫 Message.target.dispatchMessage(Message) 方法將訊息傳回給 Handler 進行處理

/**
     * 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;

        // 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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

            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 物件處理訊息的方法

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

如果 msg.callback 不為 null ,則呼叫 callback 物件的 run() 方法,該 callback 實際上就是一個 Runnable 物件,對應的是 Handler 物件的 post() 方法

    private static void handleCallback(Message message) {
        message.callback.run();
    }
    public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

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

如果 mCallback 不為 null ,則通過該回撥介面來處理訊息,如果在初始化 Handler 物件時沒有通過建構函式傳入 Callback 回撥介面,則交由 handleMessage(Message) 方法來處理訊息,我們一般也是通過重寫 Handler 的 handleMessage(Message) 方法來處理訊息

最後來總結下以上的內容

一、在建立 Handler 例項時要麼為建構函式提供一個 Looper 例項,要麼預設使用當前執行緒關聯的 Looper 物件,如果當前執行緒沒有關聯的 Looper 物件,則會導致丟擲異常

二、Looper 與 Thread ,Looper 與 MessageQueue 都是一一對應的關係,在關聯後無法更改,但 Handler 與 Looper 可以是多對一的關係

三、Handler 能用於更新 UI 有個前提條件:Handler 與主執行緒關聯在了一起。在主執行緒中初始化的 Handler 會預設與主執行緒繫結在一起,所以此後在處理 Message 時,handleMessage(Message msg) 方法的所線上程就是主執行緒,因此 Handler 能用於更新 UI

四、可以建立關聯到另一個執行緒 Looper 的 Handler,只要本執行緒能夠拿到另外一個執行緒的 Looper 例項

        new Thread("Thread_1") {
            @Override
            public void run() {
                Looper.prepare();
                final Looper looper = Looper.myLooper();
                new Thread("Thread_2") {
                    @Override
                    public void run() {
                        Handler handler = new Handler(looper);
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                //輸出結果是:Thread_1
                                Log.e(TAG, Thread.currentThread().getName());
                            }
                        });
                    }
                }.start();
                Looper.loop();
            }
        }.start();

更多的原始碼解讀請看這裡:Java_Android_Learn


相關文章