Handler(下)那些事兒

繼上一篇 Handler(上)那些事兒之後,我們已經瞭解了ThreadLocal、MessageQueue、Looper三者之間的聯繫,帶着上一遍的疑問,我們來了解一下Handler源碼部分。

Handler

Handler 構造方法

在這裏插入圖片描述
上訴構造方法來自與api28,不過沒什麼大區別.相比大家在開發中用到做多的應該是無參構造,我們先來看一看源碼

  public Handler() {
        this(null, false);
    }
    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 " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

我們可以看到無參構造方法內部其實調用了另一個構造方法.主要就是給mLooper mQueue mCallback mAsynchronous 賦值,分別表示當前線程綁定的Looper,當前線程綁定的Looper內部的MessageQueue消息隊列,回調,是否異步.(異步本章節不作講解)
乘勝追擊,我們再看來來其他的構造方法:

    public Handler(Callback callback) {
        this(callback, false);
    }

    public Handler(Looper looper) {
        this(looper, null, false);
    }

    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }

    public Handler(boolean async) {
        this(null, async);
    }


我們可以看到內部其實最後都是調用Handler(CallBack,async)/Handler(Looper,CallBack,async)這兩個構造方法,該方法主要就是進行mLooper mQueue mCallback mAsynchronous的賦值.
總結就是

  • 若mLooper爲空則默認過去當前線程綁定的Looper,不爲空則用傳入的Looper
  • mQueue則是通過Looper內部的MessageQueue獲取的
  • mCallback默認爲null
  • mAsynchronous默認爲false

所以這就解釋了平時在子線程和主線程通信的時候,對於Handler的使用我們就只是通過無參構造方法創建,因爲內部會獲取當前線程也就是主線程的Looper,所以可以通過Handler去改變UI.

sendMessage/postxxx

通過sendMessage/postxxx方法可以去分發消息,但是Handler給我們提供了很多sendMessage和postxxx方法,有的作用是延時作用,有的時候傳一個empty類型的Message,在此都不會一一分析,大家可以自行看看源碼,但是所有的方法最後都會去調用sendMessageAtTime我們來看看.

   public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
  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);
    }

便於理解保留sendMessageAtTime調用前的一個方法,便於理解.

  • sendMessageAtTime(message,uptimeMillis),uptimeMillis表示具體執行的時間
  • 所以可以看到sendMessageDelayed方法最終調用了sendMessageAtTime,傳入的第二參數表示時間就是當前時間+加上延時的時間(若有延時則加,無則不加)
  • sendMessageAtTime內部又調用了enqueueMessage,將message添加到隊列中,通過調用MessageQueue#enqueueMessage方法.

那麼後面的工作就交給了Looper#loop方法,去循環的調用MessageMessage#next方法.

在講解Looper#loop的時候,提到將MessageMessage#next返回的Message,通過message.target#dispatchMessage方法回調該信息,那麼我們來看看dispatchMessage方法.

dispatchMessage

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

其實dispatchMessage主要決定消息回調的先後順序,

  • 若msg.callback不爲空則回調handleCallBack
  • 若mCallBack不爲空則回到mCallBack.handleMessage
  • 最後都不會掉則回調Handler的handleMessage

msg.callback其實就是在構建Message的時候傳入的Runnable
mCallBack其實就是Handler構造方法中傳入的Callback

所以通過dispatchMessage,我們可以知道去獲取Message的途徑.

使用實例


public class MainActivity extends AppCompatActivity {

    Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread() {
            @Override
            public void run() {
                super.run();
                Looper.prepare();
                handler = new Handler(Looper.myLooper(), callback);
                Looper.loop();
            }
        }.start();
        findViewById(R.id.btn_add).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = Message.obtain();
                message.obj = "hello";
                handler.sendMessage(message);
            }
        });
        System.out.println("main " + Thread.currentThread().getName());

    }

    Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            System.out.println("THread " + msg.obj.toString() + Thread.currentThread().getName());
            return false;
        }
    };
}

上訴代碼就是一種子線程Handler的使用. 那麼有人會問爲什麼平時主線程Handler用不到Looper#prepare和Looper#loop? 因爲Activity在創建的時候就幫我做了.

總結

這麼一來整一個Handler的消息機制就都瞭解了,最後咱們還是要總結歸納整個流程.

  • 首先需要通過Looper#prepare去初始化該線程的MessageQuene
  • 通過創建Handler並獲取當前線程的Looper,從而獲取Looper的MessageQuene
  • 通過sendMessage,將所要分發的Message添加到MessageQueue中.
  • 由於Looper#loop會去不斷的循環調用MessageQueue#next方法去獲取可以分發的Message
  • 最後通過Message.target#dispatchMessage去回調Message.

爲了便於理解整個過程,我將Handler過程視爲一個傳送帶的工作流程.
在這裏插入圖片描述
Handler就是工人,負責貨物(Message)的組裝,將貨物(Message)拖放到傳送帶(MessageQuene)中,傳送帶(MessageQuene)被動力(Looper)驅使着去傳送貨物(Message),最終傳送到終點再有工人(Handler)去組裝.

進階部分

儲存Message是通過什麼方式存儲的.

  • 存儲結構是以單鏈表的形式存儲
  • 存儲是依據執行Message的最終實際時間的先後去存儲的.

MessageQueue線程喚醒/休眠

我們先來看看MessageQueue的next方法

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            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 && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
              (1)   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;
                }

                if (mQuitting) {
                    dispose();
                    return null;
                }
                (2)   if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
             if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
        ...
        }

注:
mBlocked:表示線程是否休眠,該變量會在enqueueMessage中用到。
mMessages:表示當前執行的Message。

我們來看看(1)處代碼

  1. 判斷當前msg.when也就是執行message時間和now(當前時間)進行對比
  2. 若now>msg.when,則返回當前msg,並且mBlocked設置爲false,因爲當前時間大於可執行時間,所以不需要休眠等待,故線程當前狀態爲未休眠。
  3. 若now<msg.when則會記錄下nextPollTimeoutMillis(距離執行msg的時間差),進行線程休眠。

那麼記錄下nextPollTimeoutMillis(時間差)有什麼用呢? 大家知道looper.loop內部是有一個死循環去獲取msg的,並且msg單鏈的排列又是按照時間排列,所以只有執行完當前msg再回輪到下一個,那麼已知下一個msg執行時間和當前時間的差距,系統過讓線程休眠,休眠的時間則爲執行的時間差,nativePollOnce方法就是讓線程休眠nextPollTimeoutMillis的時間,這樣就避免的資源的浪費.


我們可以看到(2)處代碼

  1. 若當前沒有可執行的message或者是執行message的時間未到,沒有可執行的任務所以需要將mBlocked賦爲true。

大家想一想這一種情況,若當前時間爲12點,下一個執行的msgA爲12.30,因爲有時間差所以線程休眠,那麼此時我添加一個msgB他的執行時間爲12.15,可是線程還休眠着無法執行msgB,所以需要喚醒線程,將msgB插入到msgA前,然後修改休眠時間進行休眠.那麼喚醒這一步驟在enqueueMessage.
那麼我們來看看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) {1if (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;2if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;3} 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.4if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

我們來看看3處重要代碼。

  1. 若當前msg任務停止,則會拋出異常再回收資源。
  2. 我們可以看到needWake 和mMessages賦予了p,主要就是判斷當前的mMessages是否爲null,null就表示這沒有可執行的Message;當前需要添加的Message的執行時間早於mMessages,需要插入到隊列中,然後將當前線程狀態賦予needWake。
  3. 這一塊表示異步的msg,異步這一塊不作講解,後面會專門開一篇說異步。
  4. 最後根據當前線程是否休眠,來調用netiveWake去喚醒線程。

可以總結一下

  • 若當前無可執行的msg,則線程會去休眠,此時mBlocked=true
  • 若當前執行的msg的時間未到,則會記錄下時間差nextPollTimeoutMillis,並調用nativePollOnce方法讓線程休眠,此時mBlocked=true.
  • 若在線程休眠的時候,插入新的msg,則會去喚醒線程,重新排列msg的順序,去執行重新排列後下一個該執行的msg.

通過上訴總結,我們可以回答面試中的一種類似的題目.
1.爲什麼sendMessage加了延遲,線程不會堵塞?
2.因爲當前可執行msg時間未到,系統進行休眠,那麼插入新的msg如何操作?
這些類似的問題都可以從休眠堵塞,以及喚醒這一塊解釋即可.

爲什麼在Activity中的Handler直接new,而子線程中會報錯且提示需要looper.prepare?

5240        Looper.prepareMainLooper();
5241        //創建ActivityThread 對象
5242        ActivityThread thread = new ActivityThread();
5243        thread.attach(false);
5244
5245        if (sMainThreadHandler == null) {
5246            sMainThreadHandler = thread.getHandler();
5247        }
5248
5249        if (false) {
5250            Looper.myLooper().setMessageLogging(new
5251                    LogPrinter(Log.DEBUG, "ActivityThread"));
5252        }
5253
5254        Looper.loop();

上述代碼在ActivityThread.java#main方法中,main方法是整個APP初始化的入口,可以看到它已經幫我們做了Looper.prepare 和 Looper.loop,主要是在主線程中,handler直接new即可,不需要再Looper.prepare 和 Looper.loop

在消息機制中,那麼多方法內進行死循環,爲什麼系統不會卡死?

這個問題得要從ActivityThread.java開始說起,整一個APP的初始化在ActivityThread的main方法執行,在一個線程的生命在代碼執行完後結束,那麼如果執行完了這不就意味着這個線程就會消失,那麼在main方法中不就意味着APP結束?所以就需要一個死循環或者是像MessageQueue中的線程休眠喚醒這樣的流程,來保存整個main線程長久的生命週期。從而在MessageQueue的中對線程休眠和喚醒的流程。當然整一個app也不止一個消息機制,會有很多機制,所以會通過創建線程去執行。

下集預告

OkHttp的源碼解讀

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