Android 之 “只是想來談談 Handler 機制”

傻小孩b發表於2019-02-28

《Android之“只是想來談談Handler機制”》
轉載請註明來自 傻小孩bgold.xitu.io/user/57e089…喜歡的可以關注我,不定期總結文章!您的支援是我的動力哈!

前言

在談Handler的時候,應該會涉及到三個角色:Handler自身LooperMessageQueue。這裡我將一一做分析記錄:

Looper

Looper類程式碼並不長,所以相對消化起來並不是特別的難。
Looper是一種迴圈機制,而且保證每個執行緒只能存在一個Looper機制。
講到Looper,如果要從頭開始認識這個機制,當然要參考應用的主執行緒main函式入口,ActivityThread。程式碼:

public static void main(String[] args) {
       ...
        Looper.prepareMainLooper(); 
   ...
     Looper.loop();
 }複製程式碼

再往裡面看 Looper.prepareMainLooper();

 public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

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));
    }複製程式碼

我們暫且不說這個prepareMainLooper有什麼作用,從程式碼功能,我們可以看出來,程式做了兩件事情:
1、sThreadLocal例項set了一個Looper的例項。
2、通過sThreadLocal.get() != null判斷達到不允許該執行緒中重新申請一個looper例項。
其中,ThreadLocal有點像HashMap的機制,key作為一個唯一標識值,且唯一對應一個value。所以這裡為當前執行緒例項了一個獨立標識的Looper例項。

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

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                //...
            }

            msg.recycleUnchecked();
        }
    }

----------------------------------------------------------------------

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
 }複製程式碼

在上面的方法loop中,最有亮點當然就是for (;;)死迴圈了!(不然怎麼叫迴圈機制呢哈)。從程式程式碼中,我們可以發現looper機制,執行迴圈機制的開始入口,便是loop方法!但是,我們在執行之前,應該要知道是哪個執行緒的Looper例項執行了?這裡有個關鍵方法myLooper()。看到方法體的sThreadLocal.get()後,讀者會不會瞬間剛剛在哪裡判斷的時候也呼叫了。

對!就是Looper機制中的prepare方法,初始化了當前Looper的例項物件,最後通過呼叫loop()啟動了迴圈機制!大概我寫下順序

* 一般執行緒 :
 Looper.prepare() --> Looper.loop()

* 主執行緒:
Looper.prepareMainLooper(); ->Looper.prepare() && 初始化sMainLooper(也就是getMainLooper獲取的主執行緒looper例項,這個動作是synchronized執行緒同步的) --> Looper.loop()複製程式碼

###MessageQueue

MessageQueue是一個訊息佇列機制,遵循先進先出的規則,簡單講述這裡由插入與刪除兩個動作,其中enqueueMessag()作為訊息插入、next()則是取出資料的時候程式會將其取出的資料移除訊息佇列,其中next是一個阻塞試的死迴圈機制,只要有訊息插入,將進行next取出。

這裡MessageQueue不做更多的說明,首先看到Looper機制是如何關聯MessageQueue列的,首先定位在Looper初始化例項的構造方法與loop啟動機制的方法

  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }複製程式碼
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;
            }
          ...
        }
    }複製程式碼

從程式碼結構中,可以發現訊息佇列MessageQueue,在構造方法中例項化。而且loop方法死迴圈執行中,是不斷監測佇列中next是否有訊息來臨,當然前面也說明了,next是阻塞的,因此等到有訊息message插入的時候,才會通過next獲取到message例項,然後在進行下一動作。

Handler

前面的鋪墊就是為了今天的主角,首先分析Handler之前,我們都知道日常我們的handler用法,例如如下:

   private static Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1000){
                //..doing
            }
        }
    };
    handler.sendEmptyMessage(1000);複製程式碼

這裡我們直接通過構造方法到send方法再到接收原始碼進行分析

首先鎖定到構造方法

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }複製程式碼

鎖定到重要的兩句話mLooper = Looper.myLooper();mQueue = mLooper.mQueue;。這裡通過上面分析主執行緒的Looper機制,應該很熟悉了,構造器主要做的其中兩個重要的事情:
1、獲得當前執行緒(一般主執行緒)Looper機制例項
2、通過looper例項獲得與Looper例項繫結關係的訊息佇列例項mQueue

其次我們再看下傳送訊息,這裡我直接跳轉到最終處理訊息的邏輯:

sendEmptyMessage ->sendEmptyMessageDelayed ->sendMessageDelayed ->sendMessageAtTime->enqueueMessage複製程式碼

傳送個訊息也不容易啊~

   public boolean sendMessageAtTime(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);
    }複製程式碼
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }複製程式碼

不知道讀者注意到了沒有,最終Handler傳送Meesage的時候,其實最終還是呼叫了與當前執行緒的Looper例項所繫結的訊息佇列中的enqueueMessage,即只是一個插入message到訊息佇列的過渡者!當然,其中通過msg.target = this;,message物件繫結了呼叫目標是當前Handler物件。這裡我建議讀者可以手動自己看下原始碼,不難理解。

前面簡述MessageQueue的時候有提及到,MessageQueue只有兩個操作,即單連結串列的插入以及刪除。由於next()是阻塞式的死迴圈,只要訊息佇列有訊息,則馬上將message取出並溢位。

最後聯絡到真正處理轉發的Looper例項,前面也說loop方法死迴圈執行中,在不斷監測佇列中next是否有訊息來臨。如果Handler此時傳送Message到訊息佇列中,則繫結關係中的Looper例項中的loop方法將會獲得此時的message,經過訊息佇列終於來到當前執行緒的looper機制中,此時loop方法通過msg.target.dispatchMessage(msg)返回給Hander物件,這裡我再貼下loop方法

  public static void loop() {
           ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {

                return;
            }

           // 這裡的target,前面我們可以在Handler看到每個Message都會通過target繫結傳送Handler物件
          // 最終訊息交給Handler中dispatchMessage方法處理
            msg.target.dispatchMessage(msg);

            msg.recycleUnchecked();
        }
    }複製程式碼
// 最終處理再轉發給 handleCallback 或 handleMessage
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }複製程式碼

上面筆者直接通過註釋講述了最後的處理。當然這裡我們也可以觀察下這裡面的mCallback,這裡是我們初始化Handler的時候有構造方法可以將mCallback進行初始化,這裡我們可以觀察到在執行Handler的handleMessage前還經過mCallback.handleMessage(msg)的判斷,說明mCallback在這裡起到一個攔截的作用,如果例項者回撥返回true,就不會將訊息回撥給handleMessage。

筆者是花了三個多小時過了下原始碼和記錄了分析的筆記,當別人提起底層的原始碼機制,這塊真的有點薄弱。平常應用層開發多了,花在原始碼層分析的時間也少了,所以一有時間還是得多讀讀書,多讀讀程式碼,提升下思維和能力~共勉哈!最後我引用下別人的一張圖做最後的總結,感謝閱讀!

Android 之 “只是想來談談 Handler 機制”
Android Handler.png

傻小孩b mark共勉,寫給在成長路上奮鬥的你

相關文章