Android 消息機制

一 概述    

Android消息機制主要指Handler的運行機制,主要包括上層的Handler接口以及下層的MessageQueue和Looper。

  • Handler :  消息處理。通常我們需要繼承並實現handleMessage方法或者設置一個Callback。
  • MessageQueue: 消息隊列。用於存儲消息。
  • Looper: 消息循環。無限循環從消息隊列中查找消息。

三者的關係如圖:

二 MessageQueue工作原理

MessageQueue負責存儲和讀取消息,雖然MessageQueue名字叫消息隊列,但是實際內部是用一個單鏈表結構來維護消息列表的。

消息隊列存儲的消息指的是Message的實例。在分析MessageQueue工作原理前先了解一下Message的一些重要參數。

  • msg.target  代表發送這個Message的Handler實例
  • msg.when  代表執行這個Message的時間
  • msg.isInUse()和msg.markInUse() 判斷當前Message是否正在使用和設置使用狀態
  • msg.next 下一個需要執行的Message對象
  • msg.callback 一個Runnable對象,通過Handler.post方法設置。

MessageQueue插入操作對應的方法是enqueueMessage方法

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

插入方法接收的參數是需要插入的消息以及執行消息的時間,主要工作流程如下

  1. 如果msg.target爲空或者msg已經在使用中,則拋出異常。(無法重複插入同一msg)
  2. 如果Looper已經退出(調用過Looper.quit()方法),則拋出異常。
  3. mMessage表示單鏈表頭部,鏈表頭部爲空,或者執行時間早於頭部消息的執行時間,則直接插入頭部。
  4. 如果不滿足3,則開啓循環,從頭部開始遍歷,直到當前msg的執行時間晚於待插入msg的執行時間,插入鏈表。否則插入隊尾。
  5. 如果執行消息的線程處於Block等待狀態,則喚醒線程。

插入流程分析結束,上述過程也證明了消息隊列本質不是隊列,而是單鏈表,通過插入排序,根據執行時間正序排列。

接着分析消息出隊流程,精簡後代碼如下

    Message next() {
        
        int nextPollTimeoutMillis = 0;
        for (;;) {
          
            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) {
                    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;
                }
            }

            mBlocked = true;
        }
    }

這裏調用了一個很重要的方法nativePollOnce,這是一個native方法,第二個參數爲超時等待時間。如果爲-1,則表示無限等待,直到被喚醒爲止。如果值爲0,則無須等待立即返回。

循環體大致流程如下:

  1. 調用nativePollOnce,首次調用參數爲0,不等待立即返回。
  2. 進入同步代碼塊,如果隊列裏沒有消息,則賦值 nextPollTimeoutMilli爲-1,重新進入循環,進入無限等待Block狀態。
  3. 如果有消息,對比隊首消息的執行時間和當前時間,如果當前時間晚於隊首執行時間,則消息出隊並返回該消息,否則賦值 nextPollTimeoutMilli爲時間差,重新進入循環,調用nativePollOnce,線程進入等待狀態。

三 Looper工作原理

Looper在Android消息機制裏負責取出消息,然後調用Handler接口處理消息。首先看一下Looper是如何創建的。

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

    public static void prepare() {
        prepare(true);
    }

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

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

Looper的構造方法是私有的,創建了MessageQueue的實例和獲取了當前Thread的引用,向外暴露的創建實例的方法是prepare和prepareMainLooper,創建出來的實例存儲在sThreadLocal中,它是Looper類的靜態變量。意味着每一個線程最多隻有一個Looper和一個MessageQueue。(ThreadLocal原理可以參考https://blog.csdn.net/Justwen26/article/details/103450958

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;

        ......

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ......
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            ......
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            }
            ......
        }
    }

Loop()是一個靜態方法,流程相對簡單

  1. 獲取當前線程Looper對象和MessageQueue對象,並開啓死循環。
  2. 進入循環體,調用MessageQueue.next()方法獲取消息。(可能會阻塞當前線程)
  3. 如果observer不爲空,則調用observer.messageDispatchStarting方法
  4. 調用msg.target.dispatchMessage()方法進行消息分發,這裏的msg.target就是Handler的一個實例。
  5. 如果observer不爲空,則調用observer.messageDispatched方法
  6. 重複2~5

2和5步驟中的observer在消息分發的前後調用,我們可以註冊observer進行消息處理的監聽。著名的卡頓檢測框架BlockCanary就是基於這個observer實現的。

子線程的需要我們手動調用Looper.preapre()創建Looper對象和Looper.loop()開啓循環,而主線程則是由系統在ActivityThread的main()方法中調用Looper.prepareMainLooper()創建。

Handler

Handler在Android消息機制中負責發送消息和處理消息,並對外暴露對應的API供開發者調用。

發送消息

Handler提供了多個API進行發送消息。

    public final boolean sendEmptyMessage(int what){
        return sendEmptyMessageDelayed(what, 0);
    }
	
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
	
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }	
	
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
	
    public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

    public boolean sendMessageAtTime(@NonNull 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);
    }

最終都會調用到sendMessageAtTime方法,將消息插入消息隊列,他們區別在於

  • sendEmptyMessage和sendEmptyMessageDelay 只需要傳入一個what參數,方法內部會創建一個Message實例。
  • sendMessage和sendMessageDelay 需要我們自己去創建Message實例。
  • post和postDelay 傳入一個Runnable對象,方法內部會創建一個Message實例。
  • 最終的sendMessageAtTime方法的第二個參數是當前時間加上延遲時間,在調用不帶delay的方法時,延遲時間爲0。

處理消息

處理消息的代碼比較簡單

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

    private static void handleCallback(Message message) {
        message.callback.run();
    }
  1. 如果msg設置了callback,則調用callback的run方法。(這裏的msg.callback則是通過調用post方法傳遞的Runnabl對象)
  2. 如果Handler設置了callback,則調用callback的handleMessage方法
  3. 如果步驟2callback.handleMessage方法返回false,則繼續調用Handle自己的handleMessage方法。

消息機制工作流程總結

上面分析了Handler,MessageQueue和Looper三者分別的工作流程,下面總結三者是如何協同工作,完成了整個消息工作流程。

  • 準備階段
  1. 調用Looper.preapre() 創建當前線程Looper和MessageQueue對象。
  2. 調用Looper.loop() 開啓消息循環,並調用MessgeQueue.next()獲取消息。因爲MessageQueue裏沒有消息,則調用nativePollOnce(-1),線程進入無限等待狀態。
  • 開始工作
  1. 創建Handler併發送一個消息。
  2. Handler調用enqueueMessage方法向MessageQueue插入消息,如果線程處於等待狀態,則喚醒線程。
  3. 線程喚醒後,next方法繼續執行,取出下一個消息,並返回給Looper,Looper調用Handler.dispatchMessage進行消息分發
  4. 分發完畢,如果MessageQueue裏沒有消息,則線程繼續進入無限等待狀態,否則計算下一個消息的間隔時間,等待對應時間。
  5. 重複2~5
  • 結束工作
  1. 調用Looper.quit()方法,結束Looper循環,此時不允許再使用Handler發送消息。

其他問題

Handler內存泄漏

現象

我們在編寫代碼工程中,如果定義了一個Handler成員變量,AS會提示我們Handler應該定義爲靜態變量否則可能會導致內存泄漏。

原因:

Java中非靜態內部類會隱式持有外部類引用。

我們通常在使用Handler的時候,會以非靜態內部的時候方式去重寫handleMessage方法,導致這個Handle的實例會隱式持有外部類的引用。

當發送一個消息時,將產生如下引用鏈

OutterClass -> Handler -> Message -> MessageQueue -> Looper -> ThreadLocal -> Thread

如果是一個延遲消息,那麼在消息被執行前,整條引用鏈會一直存在,從而導致Handler的外部類無法被釋放,出現內存泄漏。

解決方案:

  • 將Handler設置爲靜態變量,這裏就不會持有外部類的引用了。
  • 在確定不需要外部類實例的時候,主動調用removeCallbacksAndMessages方法移除所有使用該Handler發送的消息。

消息執行的時間

消息隊列是基於單鏈表實現的,且內部是根據執行時間先後排序的。當消息依次執行的時候,當前消息必須等前一個消息執行完畢才能執行,即使當前時間已經晚於當前消息理論的執行時間。所以Handler的sendDelay方法不適合需要精確計時的場景。

Looper死循環

這是一個老生常談的問題, 首先看一下這段代碼

        new Thread() {

            @Override
            public void run() {
                Looper.prepare();
                Looper.loop();
                Log.d("Looper", "Looper starts successfully");
            }
        }.start();

創建了一個線程,開啓消息循環,最後打了一行log。

但是實際上,最後一行log並不會打出,也就是說loop方法的確會阻塞當前線程。

那麼問題來了爲什麼主線程沒有卡死?

  1. 首先loop方法雖然內部是一個死循環,但是並不是時時刻刻都在循環,在沒有消息的時候,會因爲nativePollOnce導致線程休眠,消息被插入後纔會喚醒線程。
  2. Android本質是消息驅動,也就是說任何代碼都是在消息裏執行(包括觸摸事件),當沒有消息處理的時候,線程休眠不會產生任何影響。如果一旦有消息處理,線程就會被喚醒進行消息的處理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章