Handler怎麼進行執行緒通訊?Handler原理解讀

南方吳彥祖_藍斯發表於2021-10-12

這道題想考察什麼?

  1. 是否熟悉Handler的基本用法
  2. 是否熟悉Handler訊息機制的執行流程
  3. 是否明白Handler進行執行緒通訊的原理

考察的知識點

  1. 利用Handler進行執行緒切換的基本流程
  2. Handler訊息機制涉及到的類以及之間的關係
  3. Handler是怎樣做到執行緒通訊的

考生應該如何回答

  1. 先說一下Handler機制的執行流程,以及涉及到的類之間的關係

    Handler訊息機制主要涉及到四個類:Handler、Looper、MessageQueue、Message.

    Handler: 字面意為處理器,負責訊息的分發與處理,內部持有一個looper物件和一個MessageQueue物件(MessageQueue物件透過looper物件獲得)

    Looper: 字面意為迴圈器,跑在特定的執行緒裡面,持有一個MessageQueue物件,主要透過loop()方法迴圈地從MessageQueue中來拿到當前執行緒需要處理的訊息並處理。我們通常意義上的切換執行緒,歸根結底就是切換到了這個方法中。為什麼需要loop()迴圈?通常我們開啟一個新的執行緒,當程式碼執行完執行緒也就結束了,如果想要該執行緒做到不斷接收並處理外部發來的訊息,就需要開啟looper。

    MessageQueue: 訊息佇列,也就是存放Message的地方,維護了一個透過時間優先順序排列的單連結串列,Handler發出的訊息會先存進此佇列再被取出執行。

    Message: 訊息,可攜帶引數及標識,並持有傳送該訊息的Handler物件。post(Runnable)方式最後也會被改為Message物件,並將Runnable賦值給Message的callback,處理訊息時呼叫。

    一次完整的事件傳送與處理流程為:Handler呼叫sendMessage/post方法,呼叫其持有的MessageQueue物件的enqueueMessage方法將訊息新增到訊息佇列,looper透過loop()方法會不斷的從該MessageQueue中取出Message,並呼叫Message持有的target(Handler)物件的dispatchMessage方法,進而執行到Handler的handlerMessage或者Runnable的run方法或者額外的callback進行處理。
  2. 說一下Handler怎麼做到執行緒通訊,其原理是什麼

    我們在新建一個Handler的時候,在其構造方法中會給其持有的mLooper物件賦值,Handler透過mLooper實現與執行緒的繫結。

    mLooper物件可以在構造方法中傳入,或者在構造方法中透過Looper.myLooper()方法獲取。

    //Handler.javapublic Handler(@Nullable Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }//Looper.javastatic 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) { //當前執行緒中已經有looper物件,丟擲異常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    可以看到looper物件是以ThreadLocal的形式存放的,也就是每個執行緒中都有自己單獨的looper物件。

    執行緒中的looper物件在呼叫Looper.prepare()方法的時候進行初始化並賦值,並且透過判空的方式來保證一個執行緒中只有一個looper物件。

    需要說明的一下是,主執行緒中的looper是在應用程式程式建立的時候,在ActivityThread的main()方法中進行建立並開啟迴圈的,不需要我們手動建立,而子執行緒中的looper是需要我們自己呼叫prepare()/loop()方法來初始化並開啟迴圈。

    //ActivityThread.javapublic static final void main(String[] args) {    //主執行緒, 程式啟動時自動呼叫
        Looper.prepareMainLooper()    //由這裡的loop()方法處理的訊息執行在主執行緒
        Looper.loop();
    }//XXActivity.javanew Thread() {    @Override
        public void run() {        super.run();        //子執行緒, 需手動呼叫
            Looper.prepare();        //由這裡的loop()方法處理的訊息執行在子執行緒
            Looper.loop();
        }
    }.start();

    由此我們知道了Looper.loop()在哪個執行緒呼叫,其訊息迴圈就執行在哪個執行緒中。

    而Looper.loop()中幹了什麼事?就是獲取到當前執行緒的looper物件(怎麼獲取的?前面說了,ThreadLocal中獲取),再拿到其內部MessageQueue物件,從其中不斷地取出訊息並執行,也就是我們執行緒程式碼實際執行的地方。既然loop()中執行的是MessageQueue中的訊息,我們不難想到,若想讓程式碼執行在這個某個執行緒中,肯定是要跟這個執行緒的MessageQueue發生關係的。

    //Handler.javapublic boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue; //這裡的mQueue即mLooper物件中的MessageQueue
        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(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();    if (mAsynchronous) {
            msg.setAsynchronous(true);
        }    return queue.enqueueMessage(msg, uptimeMillis); //將Message物件新增到MessageQueue佇列中}//Looper.java (此處僅貼出關鍵程式碼)public static void loop() {    final Looper me = myLooper();    final MessageQueue queue = me.mQueue;    for (;;) {
            Message msg = queue.next();
            msg.target.dispatchMessage(msg);        //dispatchMessage 會呼叫到handler的handleMessage方法或者Runnable的run方法
            //或者額外傳入的Callback的handleMessage方法.
        }
    }

    可以看到,當(比如子執行緒中)呼叫Handler.sendMessage時,會將訊息傳入該Handler所繫結的mLooper的MessageQueue中,這一步是傳送訊息的過程。也就是說,訊息會從一個執行緒被髮送到另一個執行緒的訊息佇列。如果建立Handler時傳入的是主執行緒的looper物件,那麼主執行緒中的Looper.loop()函式就會從這個MessageQueue中取到子執行緒發來的這個訊息並執行。怎麼執行呢?就是呼叫message.target.dispatchMessage() -> handleMessage()方法,而這裡的target就是傳送該訊息的handler。而由於Looper.loop()是在主執行緒,所以這條訊息也就執行在了主執行緒中。

    所以Handler完成執行緒通訊通俗點說原理就是:在A執行緒中獲得繫結了B執行緒looper的handler的引用,用此handler傳送的訊息會進入B執行緒的訊息佇列,最終會跑在B執行緒的Looper.loop()方法中,這樣就完成了從A執行緒到B執行緒的通訊。

    這裡要區分一下執行緒物件和執行緒的概念:執行緒物件就是Thread物件,是虛擬機器中實實在在存在的,文章中所說的哪個執行緒的mLooper物件,MessageQueue物件都是指這個Thread物件所持有的物件;而執行緒是系統排程的單位,是一段程式碼的執行過程,透過Thread類的start()方法來真實啟動的。所以一個執行緒中呼叫Looper.loop()方法,從本執行緒物件的MessageQueue中拿資料,而這個MessageQueue,其他執行緒也可以透過Handler往裡面傳送訊息(這裡需要加鎖來保證執行緒安全),這樣就完成了執行緒的通訊。執行緒通訊本質上是執行緒間物件或者說記憶體的共享。

    下面以一段程式碼來更清楚地演示一下從子執行緒向主執行緒傳送訊息以及從主執行緒向子執行緒傳送訊息的過程。

    public class MyActivity extends AppCompatActivity {    //與主執行緒繫結的Handler, 此處主動傳入looper物件, 與呼叫預設構造方法效果是一樣的
        Handler mMainHandler = new MainThreadHandler(Looper.myLooper());    //與子執行緒繫結的Handler, 需要在子執行緒中建立, 定義為全域性變數以便在主執行緒中引用
        SubThreadHandler mSubHandler;    @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        new Thread() {            @Override
                public void run() {                super.run();
                    mMainHandler.sendEmptyMessage(0);      //子執行緒傳送訊息給主執行緒
                    Looper.prepare();                      //子執行緒中需要手動初始化looper
                    mSubHandler = new SubThreadHandler();  //初始化子執行緒Handler
                    Looper.loop();                         //開啟迴圈
                }
            }.start();        //主執行緒傳送訊息給子執行緒, 這裡延後1秒鐘以保證mSubHandler完成初始化
            mMainHandler.postDelayed(() -> mSubHandler.sendEmptyMessage(1), 1000);
        }    private static class MainThreadHandler extends Handler {        public MainThreadHandler(Looper looper) {            super(looper);
            }        @Override
            public void handleMessage(@NonNull Message msg) {            super.handleMessage(msg);            //do work in main thread
                Log.d("HandlerTest", "MainThreadHandler: " + msg.what + ", current thread: " + Thread.currentThread().getName());
            }
        }    private static class SubThreadHandler extends Handler {        @Override
            public void handleMessage(@NonNull Message msg) {            super.handleMessage(msg);            //do work in sub thread
                Log.d("HandlerTest", "SubThreadHandler: " + msg.what + ", current thread: " + Thread.currentThread().getName());
            }
        }
    }

    執行結果:

    10928-10928/com.xx.newtest D/HandlerTest: MainThreadHandler: 0, current thread: main
    10928-11722/com.xx.newtest D/HandlerTest: SubThreadHandler: 1, current thread: Thread-4

    首先得到的是子執行緒傳送來的訊息,執行在主執行緒;接著得到主執行緒傳送來的訊息,執行在子執行緒。

更多Android技術分享可以關注@我,也可以加入QQ群號:Android進階學習群:345659112,一起學習交流。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983917/viewspace-2795567/,如需轉載,請註明出處,否則將追究法律責任。

相關文章