Android多線程之Handler、Looper與MessageQueue源碼解析

本文的目的是來分析下 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

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