天天看點

Handler怎麼進行線程通信?Android Handler原了解讀

這道題想考察什麼?

  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.java
    public 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.java
    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) { //目前線程中已經有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.java
    public static final void main(String[] args) {
        //主線程, 程序啟動時自動調用
        Looper.prepareMainLooper()
        //由這裡的loop()方法處理的消息運作在主線程
        Looper.loop();
    }
    
    //XXActivity.java
    new Thread() {
        @Override
        public void run() {
            super.run();
            //子線程, 需手動調用
            Looper.prepare();
            //由這裡的loop()方法處理的消息運作在子線程
            Looper.loop();
        }
    }.start();
               

    由此我們知道了Looper.loop()在哪個線程調用,其消息循環就執行在哪個線程中。

    而Looper.loop()中幹了什麼事?就是擷取到目前線程的looper對象(怎麼擷取的?前面說了,ThreadLocal中擷取),再拿到其内部MessageQueue對象,從其中不斷地取出消息并執行,也就是我們線程代碼實際運作的地方。既然loop()中執行的是MessageQueue中的消息,我們不難想到,若想讓代碼執行在這個某個線程中,肯定是要跟這個線程的MessageQueue發生關系的。

    //Handler.java
    public 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());
            }
        }
    }