Android中消息機制分析

本文中的源碼基於Android 29;

一、概述

對於Android開發者而言,我們處理異步消息用的最多的也是輕車熟路的一種方式,就是使用Handler進行消息的分發和處理。但是我們在一個頁面(Activity 或者 Fragment)中可以直接使用Handler進行消息的分發和處理。實例如下:

private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            //處理消息
            return false;
        }
    });

我們一般在handleMessage()方法裏進行消息的處理,同時在需要發送消息的地方使用mHandler.sendEmptyMessage(0)發送消息即可。callback回調是怎麼收到消息的呢?同時Handler又是怎麼發送消息的呢?我們帶着問題看一下源碼。

Android開發者們都知道:當APP啓動時,會默認產生一個主線程(也就是UI線程),這個線程會關聯一個消息隊列,然後所有的操作都會被封裝成消息後在主線程中處理。那麼到底Android中消息機制是什麼樣子的呢?

對於Android程序而言,運行程序也是需要通過Java中的程序入口開始執行。而在ActivityThread中的就存在這麼一個main方法,作爲程序的入口。我們看一下源碼:

public static void main(String[] args) {
       //省略部分代碼…
        Process.setArgV0("<pre-initialized>");

	//創建主線程Looper
        Looper.prepareMainLooper();

	//省略部分代碼…
	
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

	//創建主線程對應Handler
        if (sMainThreadHandler == null) {//UI線程的Handler
            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");
    }

在應用程序中,當執行ActivityThread中的main方法之後,程序就運行起來了。如上面main()方法中的代碼所示,首先是通過 Looper.prepareMainLooper();創建主線程的Looper對象,然後通過sMainThreadHandler = thread.getHandler()創建了Handler,也就是ActivityThread中的類H,該類繼承於Handler,我們在這裏不在分析。最後通過Looper.loop()方法開始了消息的輪詢。業務邏輯就這麼簡單。

二、消息輪詢器:Looper

我們通過第一部分的代碼發現對於Android消息機制而言,Looper扮演者非常重要的角色。那麼我們就像瞭解一下Looper有哪些特性吧。

首先我們先了解一下Looper的成員變量吧,mQueue是消息隊列(MessageQueue),主要用來存儲消息。

mThread:是指當前的線程,嚴格的是與Looper關聯的線程。sThreadLocal:主要用來獲取或者設置Looper對象。主要就說這三個變量吧。

下面我們說一下它的構造器:

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

在構造器中,初始化了消息隊列和當前線程的信息,仔細的同學可能注意到了,這裏構造器的參數quitAllowed是什麼意思呢?我們通過查看MessageQueue的源碼我們可以發現,主要用於限制能否安全清除所有消息。若爲主線程不允許使用quit方法。

接下來我們瞭解一下它的主要方法。在第一部分我們看到主線程中,首先使用了Looper.prepareMainLooper()方法創建了主線程的Looper對象。下面我們分析一下源碼:

/**
     * 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();
        }
    }

我們看到在這裏使用了prepare(false)方法傳入了false,我們看一下源碼:

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

通過以上代碼我們通過sThreadLocal獲取當前線程的Looper對象,若已經存在了則會拋出異常“Only one Looper may be created per thread”,也就是說一條線程對應一個Looper對象。若沒有調用過該方法,則會通過sThreadLocal保存一個Looper對象。我們回過頭來,prepareMainLooper()方法,通過設置quitAllowed = false,來創建一個不允許終止的Looper對象,也就是主線程是不允許調用quit方法的,這樣我們在主線程創建的Handler就可以一直進行消息輪詢了~。sMainLooper 就是主線程對應的Looper對象。

同時還有一個方法prepare(),該方法主要用於子線程創建Looper,使用Handler();同時建議在子線程中調用了prepare()方法之後需要調用loop()方法,最後以quit()方法結束本次輪詢;

最後,我們看一下消息輪詢的邏輯吧,也就是loop()方法。源碼如下:

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        //獲取Looper對象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //獲取looper對象對應的消息隊列
        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;
            }

            //省略部分代碼...

            //進行消息的分發處理
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            //省略部分代碼...

            msg.recycleUnchecked();
        }
    }

該方法是Handler 消息機制的核心方法,首先該方法會獲取當前線程對應的looper對象MessageQueue的對象,然後就會進入一個死循環。在該死循環中首先是通過消息隊列獲取下一條消息,也就是源碼中的“queue.next()”方法,該方法的源碼在這裏就不在多餘解釋了,它的作用我們不看源碼也能猜得出來,哈哈~,但是如果消息隊列中沒有消息,就會發生阻塞~

我們繼續向下閱讀,我們看到msg.target.dispatchMessage(msg);這就是消息分發的調用方法,我們知道msg就是Message的對象,那麼target又是啥啊?哈哈,看過Message源碼的同學一定都知道它裏面有一個成員變量target,原來是Handler,現在一切都清楚了吧!我們的Handler就是這麼收到消息的

三、紐帶:Handler

我們分析完了Looper的源碼,作爲消息的輪詢器Looper扮演着很重要的角色,也可以說是是整個消息機制的動力核心,是Looper驅動着消息的傳遞。而Handler扮演着紐帶的角色,它承載着Looper ,連接着MessageQueue,進行消息的輸入輸出,我就叫它爲紐帶吧。

在第一部分我們通過使用Callback將消息回調回頁面進行處理,我們先看一下Callback吧。

/**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     */
    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);
    }

Callback就是Handler暴露給外部的數據接口,用於接收分發的消息,我們就可以在handleMessage()方法裏進行處理了。在第二部分中我們分析Looper的loop()方法發現通過“msg.target.dispatchMessage(msg)”方法回調回Handler,我們看一下dispatchMessage()的源碼

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

在該方法裏首先是判斷msg.callback是否爲空,我們通過源碼知道callback實際上就是一個Runnable對象,若非空的話,就會執行handleCallback(msg)方法,該方法就是讓msg.callback這個線程運行起來。若msg.callback爲空的話,然後看Handler的成員變量mCallback是否爲空不爲空的話就會回調Callback的handleMessage()方法;若mCallback爲空的話就會調用Handler的handleMessage()方法。現在清楚了吧,想要處理消息要麼通過Callback回調,要麼實現Handler並重寫handleMessage()方法。這就是爲啥Handler有一個沒有方法體的handleMessage()方法。

 我們再看一下消息的添加的邏輯吧。在第一部分我們就是使用mHandler.sendEmptyMessage(0)將消息添加進消息隊列的,這類的方法有以下幾個:

這類方法都會在一定的時間的情況下,將消息推送在消息隊列的底部。然後會在與該Handler關聯的線程裏的handleMessage方法下處理該消息。

還有種情況就是我們有時候會使用mHandler.post(r),這類方法一般是開了一條子線程去運行,這又是怎麼回事呢?首先這類方法有以下幾種:

我們看到該類型的方法其參數都包含Runnable參數,當我們調用這類方法時,所添加Runnable就會被添加進相應的消息隊列中,該線程最終會在Handler的dispatchMessage()方法中執行,我們知道對於Message的callback就是一條線程,當callback非空時就會運行;

總體上上述方法:

  • post()和postDelayed()方法就會通過調用sendMessageDelayed()方法將線程添加進隊列,同時在延遲一定時間運行;
  • 同時postAtTime()方法是調用sendMessageAtTime()方法將該線程添加進消息隊列,然後在指定的時間運行;

我們繼續查看源碼發現post(),postDelayed()和postAtTime()方法最終都是通過sendMessageAtTime()方法將消息添加進消息隊列。我們看一下源碼:

 /**
     * Enqueue a message into the message queue after all pending messages
     * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
     * Time spent in deep sleep will add an additional delay to execution.
     * You will receive it in {@link #handleMessage}, in the thread attached
     * to this handler.
     * 
     * @param uptimeMillis The absolute time at which the message should be
     *         delivered, using the
     *         {@link android.os.SystemClock#uptimeMillis} time-base.
     *         
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the message will be processed -- if
     *         the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    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);
    }

該方法最終通過enqueueMessage()方法將消息添加進入消息隊列,隨後就會通過Looper將消息分發下去。

postAtFrontOfQueue()方法是通過sendMessageAtFrontQueue()方法將消息添加的消息隊列,並保證下一個就會執行。然而最終也是通過enqueueMessage()方法完成的將消息添加進消息隊列的操作。關於消息添加進入消息隊列的邏輯在這裏不在分析了,感興趣的同學可以看一下MessageQueue的enqueueMessage()方法。

同時在我們的開發中多數情況下頁面是會銷燬的,這就需要銷燬一些消息,有一個方法就能解決這個問題:

removeCallbacksAndMessages()該方法提供了一種解決方法,當Object爲null是,消息隊列中的消息會被標記回收。具體可以去MessageQueue的removeCallbacksAndMessages()方法中分析。

現在我們理解了爲什麼在子線程中需要這麼使用了吧。

class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }

四、小問題

給大家留個小問題,爲什麼在Activity中就不需要創建Looper了?與君共勉~

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