Android 的消息機制(UI線程的Looper 爲啥不會阻塞?答案在後面)

說道 Android 的消息機制,其實說的就是 Handler 的運行機制以及 Handler 所附帶的 MessageQueue 和 Looper 的工程過程。

一、 Handler 的運行機制

當 Handler 創建的時候,會採用當前線程的 Looper 來構建消息循環系統,如果當前線程沒有 Looper 則會報錯,當然,開始是 UI 線程,所以不用擔心。
當然,當Looper 被創建的時候, MessageQueue 也被創建好了,這樣 Looper 和 MessageQueue 就可以跟 Handler 一起工作了。

這裏,先做一下 Handler 的工作流程:
當我們使用 Handler 的send 或者 post 的時候,它會調用 MessageQueue 的 qnqueueMessage 方法,將這個 消息放到消息隊列中,然後 Looper 發現有新消息到來時,就會處理這個消息了;然後 Handler 的 handleMessage 方法就會被調用。

注意 Looper 是運行在創建 Handler 所在的線程中的,這樣一來,Handler 中的業務邏輯就會被切換到創建 Handler 所在的縣城中去執行了

過程可以用下圖表示(圖片來源):
在這裏插入圖片描述
接下來繼續深入它。

1.2、消息隊列的工作原理

消息隊列在 Android 中指的是 MessageQueue,它其實不是隊列,裏面實現的單鏈表結構,它主要包含兩個操作:插入和讀取。
插入對應 enqueueMessage ,讀取則對應 next,其中 enqueueMessage 表示往隊列中插入一條消息,而 next 則從隊列中取出消息,並將消息從隊列中刪除。

其中,需要注意的是它的 next 方法:

    Message next() {
   		 ...
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
		...
            }
		...

可以發現 next 方法是一個無線循環的方法,如果消息隊列沒有消息,那麼next 方法會一直阻塞在這裏。當有新消息到來時,next 方法會返回這條消息並將它從單鏈表中刪除。

1.3 Looper 的工作原理

Looper 在 Android 的消息機制中扮演者消息隊列的角色,具體來說,你會不停地從 MessageQueue 中查看是否有新的消息,如果有則取出消息,如果沒有,則一直阻塞在那裏。

在 Looper 的構造方法中,會創建一個 MessageQueue ,然後將當前線程保存起來。

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

然後,Handler 的創建是需要 Looper 的,沒有Looper 是會報錯的,那 Looper 如何創建呢?
很簡單,在線程中,通過 Looper.prepare() 爲線程開啓一個 Looper,接着使用 Looper.loop() 來開啓消息循環模式,如下所示:

new Thread(){
    @Override
    public void run() {
        Looper.prepare();
        super.run();
        Looper.loop();
    }
};

當然,這個是普通線程的,如果是 UI 線程,還提供了 prepareMainLooper 方法,這個方法主要是給 ActivityThread 創建Looper 使用,由於主線程比較特殊,也可以使用 Looper.getMainLooper() 來獲取主線程Looper。

比如常用在共用類中:

 public static Handler HANDLER = new Handler(Looper.getMainLooper());

1.3.1 Looper 的退出

Looper 也是可以退出的,有 quit() 和 quitSafely() 兩個方法;quit() 會直接退出,二 quitSalely() 則是已有消息都處理完畢,纔會退出。

如果是自己定義的 Looper ,則建議要執行退出操作,否則這個子線程就會一直處於等待狀態。容易造成內存泄漏,當然主線程就不用我們操心了。

Looper 的重要方法是 loop 方法,只有調用了 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();
		  ...

        boolean slowDeliveryDetected = false;

        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 traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         ... 

          ...

可以看到,只有 queue.next() 爲null 時,纔會退出這個循環;當執行了 quit 或者 quitSafely 來通知 Looper 退出,它就會調用 MessageQueue 來退出,這樣 queue.next() 就會返回null,looper 也就退出了。
否則就會一直阻塞在這裏,一直等到 next 有新的消息,則 msg.target.dispatchMessage(msg); 就會被執行,這樣 Handler 的dispatchMessage 就會被執行了。可以看到 Handler 的 dispatchMessage 方法如下:

    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,Message 的callback 是一個 RUnnable 對象,實際上就是 Hnadler 的post 方法所傳遞的 Runnable 。所以,如果我們使用 了 Handler.post ,則不會走 handleMessage(msg) ;
如果不是,則檢查 mCallback 是否爲null,mCalback 其實就是個接口:

    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

如果不爲空,最終就會調用到我們熟悉的 handleMessage(msg) 方法了。

這樣,Handler 的消息機制就分析完了。

這裏,面試官就會問了,既然 looper 這裏是個無線循環,爲啥不會阻塞UI線程?

爲了回答這個問題,首先,我們先去到主線程的消息隊列

二、主線程的消息隊列

Android 的主線程就是 ActivityThread,主線程的入口爲 main 方法:

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

      .... 

        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

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

可以看到,main() 方法會通過 Looper.prepareMainLooper() 來創建 Looper 和 MessageQueue,然後創建了 ActivityThread線程,最後通過 Looper.loop() 開啓循環。

而它的 Handler 就是 ActivityThread.H ,裏面對應着 Activity 的啓動等消息:
在這裏插入圖片描述

那這還是不能說明UI線程爲啥不會阻塞啊?!!

別急,除了這個 ActivityThread 這個線程,其實還有個 ApplicationThread 線程,它裏面創建了 Binder 方法,當 ApplicationThread 與 AMS 進行進程間通信完成後,就會通過 ApplicationThread 會向 H 發送消息,H 收到消息之後,就會將 ApplicationThread 中的邏輯切換到 ActivityThread 中取執行,即切換到主線程去執行了。
比如 Activity 的啓動最後就通過 ApplicationThread 發送 LUANCHER_ACTIVITY 給 H 去啓動的。

所以,UI線程並不會因爲looper而阻塞,但如果我們主線程中做了一些耗時操作,導致導致 MessageQueue 的消息過多,等待執行,造成 ANR。

自此,我們的 Android 消息機制就分析完了。

參考 Android 藝術開發 第10章

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