每日一道面試題(第9期)---談談Handler機制和原理

吐槽:想起大約一週前的一個夜晚,我正在好好地學習。忽然之間。。。胸口一陣疼痛,而後就是一直疼,而且隨着深呼吸會更加重痛。我。。。。作錯了什麼???難道嫉妒我這麼愛學習就要猝死了???怎麼能這樣!!!那天晚上,我忍着痛入眠,次日醒來,疼痛依然伴隨吾身。。。去醫院。。。一頓拍片檢查操做以後,醫生:「嗯,你肺裏進空氣了。」我(滿臉問號):「???」,我肺裏沒空氣我怎麼活的?醫生(一臉平靜):「肺泡破了吧,空氣進入胸腔了,壓縮了肺部」。。。。好吧,是氣胸。得,想我一不運動,二不高又不瘦,我會得這種病也是**奇了怪了。而後就是。。天天吸氧、拍片複查。。重複重複。。。直到如今,我仍是不能理解,,,爲何我會得這種病???java


零零碎碎的東西老是記不長久,僅僅學習別人的文章也只是他人咀嚼後留下的殘渣。無心中發現了這個每日一道面試題 ,想了想若是隻是簡單地去思考,那麼不只會收效甚微,甚至難一點的題目本身可能都懶得去想,堅持不下來。因此不如把每一次的思考、理解以及別人的看法記錄下來。不只加深本身的理解,更要激勵本身堅持下去。git

Handler 機制

Handler 想必接觸 Android 的都已經很熟悉了,咱們一般用用於子線程與主線程的通訊,並在主線程中處理相關消息,好比更改 UI 等。Handler 的消息機制爲咱們處理線程中的通訊確實方便了許多。github

基本使用

最簡單的使用就是在主線程中建立 Handler 對象並重寫 handlerMessage() 方法處理消息,在子線程中經過 handler 對象的 sendMessage 方法發送消息。面試

Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                // TODO write your message processing code
                ...
            }
        };

        new Thread(){
            @Override
            public void run() {
                super.run();
                //TODO handling events
                ...
                handler.sendMessage(Message.obtain());
            }
        };
複製代碼

爲了簡單示例,Handler 才這樣寫的。這種建立方法會形成內存泄漏,具體願意以及解決方案在每日一道面試題(第 1 期)---自定義 handler 如何有效保證內存泄漏問題 都已經說明,這裏再也不贅述。app

而關於 Handler 的原理,咱們可能會多多少少的瞭解到與 Looper、Message、MessageQueue 都是離不開的,那麼具體是怎麼配合呢?下面我根據消息機制的過程,結合源碼一步一步的解析。異步

Handler 原理

建立 Handler 對象

在咱們使用時,首先就是建立 Handler,那咱們看一下 Handler 的構造函數都幹了些什麼吧。async

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());
            }
        }
        //獲取本地 TLS 存儲區的 Looper 對象引用
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //使用 Looper 中的 MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}
    
public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}
複製代碼

好吧,不少構造函數,不過有用的就是這兩個,其餘都是調用這兩個而已。從兩個對比能夠看出,若是你沒有傳入 Looper 對象,那麼就會經過 Looper.myLooper() 獲取。傳入的話,就是用自定義的,而且 MessageQueue 一直是使用 Looper 中的 MessageQueue,因此這裏出現了第兩個重要結論:ide

  • Handler 對象的建立必定須要一個 Looper 對象
  • Looper 中必定有 MessageQueue

而後還有兩個參數 callBack 和 async,callBack 是 Handler 內部的一個接口,內部只有一個 handleMessage() 方法,做用咱們下面講到再說。而後就是 async,這個就好理解了,就是決定是異步處理消息,仍是同步處理消息,默認爲同步 false;函數

public interface Callback {
    public boolean handleMessage(Message msg);
}
複製代碼

Looper 對象的建立

上一個步驟中咱們知道,Handler 是直接從 Looper.myLooper() 中得到對象的,咱們具體探討一下。oop

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

很簡單,是從一個集合中拿到的,這個 sThreadLocal,是 ThreadLocal 類的對象,表明着本地存儲區(Thread Local Storage 簡稱 TLS),線程中的惟一 Looper 對象就存儲在這裏,每一個線程都有一個本地存儲區域,而且是私有的,線程之間不能相互訪問。咱們尋找一下是在哪一個地方插入的

public static void prepare() {
    prepare(true);
}
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));
}
複製代碼

找到啦,由兩個方法的權限訪問修飾符可知,咱們只能調用第一個,也就是說,第二個方法的參數永遠是 true。再來看第二個方法,首先就是一個異常捕獲,有的人可能已經很熟悉了,那就是一個線程中只能有一個 Looper。後面就是沒有的話就 new 一個 Looper 對象,構造函數中都幹了什麼呢?

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

也很簡單,初始化了 mQueue 也就是 MessageQueue,還有就是當前所在的線程 mThread。從權限訪問修飾符能夠看出,這是一個私有的構造方法,因此說,咱們建立 Looper 方法只有 prepare() 方法啦。

MessageQueue 對象的建立

上述 Looper 對象的建立中,new 了一個 MessageQueue 方法,咱們看看都幹了什麼。

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}
複製代碼

也很簡單,初始化了兩個變量 mQuitAllowed 與 mPtr,第一個表明着消息隊列是否能夠退出,上面也說了,咱們麼的辦法,只能是 true。第二個 mPtr,涉及到 Native 層的代碼,在 native 層也作了一個初始化,具體深刻了解可到此處Android 消息機制 2-Handler(Native 層)

發送消息

建立完 Handler,下一步就是發送 Message 了,去看看源碼

public final boolean sendMessage(Message msg){};
public final boolean sendEmptyMessage(int what){};
public final boolean sendEmptyMessageDelayed(int what, long delayMillis){};
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis){};
public final boolean sendMessageDelayed(Message msg, long 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);
}
複製代碼

又是好多發送消息的,不過他們最後都調用了 sendMessageAtTime 方法。第一個參數是要發送的消息,而第二個是一個絕對時間,也就是發送消息的時間。在作了一些判斷以後,調用了 enqueueMessage 方法。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //消息得到發送該消息的 Handler 對象引用
    msg.target = this;
    //是否同步屬性
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼

在又作了一些 Message 的屬性初始化後,調用了 queue 的 enqueueMessage 方法,而這個 queue 就是在 Looper 對象中得到的 MessageQueue 對象。下面是 MessageQueue 中的 enqueueMessage 方法

boolean enqueueMessage(Message msg, long when) {
    // 每個 Message 必須有一個 target
    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) {
        if (mQuitting) {  //正在退出時,回收 msg,加入到消息池
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;//mMessages 爲當前消息隊列的頭結點
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //p 爲 null(表明 MessageQueue 沒有消息) 或者 msg 的觸發時間是隊列中最先的, 則進入該該分支
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; 
        } else {
            //將消息按時間順序插入到 MessageQueue。通常地,不須要喚醒事件隊列,除非
            //消息隊頭存在 barrier,而且同時 Message 是隊列中最先的異步消息。
            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;
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
複製代碼

總結就是,MessageQueue 按照消息的觸發時間插入隊列,隊頭是最先要觸發的消息。一個新的消息加入會根據觸發時長從隊頭開始遍歷。

消息輪詢

好了,消息已經發送給 MessageQueue 了,那麼誰來管理這個 MessageQueue,將其中的消息正確的、準確的分發呢?那就是 Looper,準確的說是 Looper 中的 loop() 方法,這也是咱們在子線程中建立 Handler 先要以前要 Looper.perpare(),以後要 Looper.loop() 的緣由。

public static void loop() {
        final Looper me = myLooper();//獲取 TSL 存儲區的 Looper 對象
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//獲取相對應的消息隊列
            
            ................
            
        for (;;) {//消息主循環,除非線程退出,否則會一直循環,沒有消息時會阻塞
            //獲取下一個消息,沒有消息時會阻塞,消息隊列退出後會返回 null,則該循環也退出
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }

            // 默認爲 null,可經過 setMessageLogging() 方法來指定輸出,用於 debug 功能
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            ................
            
            try {
                //獲取消息後根據 msg 的 target 即所屬 Hnadler 分發消息
                //target 即 Handler 在上面發送消息代碼解釋中有說明
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            .................

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

            ................

            //分發完此消息,就回收此 Message 對象,留以複用
            msg.recycleUnchecked();
        }
}
複製代碼

一些代碼解釋已經很清楚了,在在這裏面主要是一個死循環輪詢消息。由 MessageQueue 的 next() 方法取出消息,再由 Message 所屬的 Handler 對象 dispatchMessage() 方法分發消息。首先咱們看 next() 方法。

獲取消息

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {//當消息循環已經退出,則直接返回
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;//阻塞時長
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //阻塞函數,參數 nextPollTimeoutMillis 表示等待時長,或者消息隊列被喚醒,都會返回
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //當消息中的 Handler 爲空時,在 MessageQueue 中尋找下一個異步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    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 {
                        // 得到消息
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        //改變 message 狀態
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    //沒有消息,設置阻塞時長
                    nextPollTimeoutMillis = -1;
                }

                // 若是正在退出,返回空
                if (mQuitting) {
                    dispose();
                    return null;
                }

                .............
        }
}
複製代碼

nativePollOnce 是一個阻塞函數,參數 nextPollTimeoutMillis 則表明阻塞時長,當值爲-1 時,則會一直阻塞下去。 因此說,next 函數在 MessageQ 有消息時,會獲取消息並返回,在沒有消息時,則會一直阻塞。

分發消息

獲取完消息,就要到分發消息了,也就是消息輪詢總結中的 dispatchMessage 函數,在 Handler 類中

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

這裏的調用流程就是

  • 首先判斷 Message 的 callback 是否存在,若是存在,就執行 handleCallback 函數,這個函數就一個簡單的 message.callback.run() 方法調用。Message 中的 callback 其實是一個 Runnable,因此就會執行自定義的 run 函數。這個的做用是在 Handler 的 post 消息上,其實 post 消息與 send 消息並無太大的不用,只是經過 getPostMessage 方法將 Message 封裝了一下,其實就是將自定義的 Runnable 傳給 Message 的 callback,這樣在分發消息的時候就是直接執行自定義的 Runnable 中的 run 函數。
public final boolean post(Runnable r){
    return  sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
複製代碼
  • 而後就是 Handler 類中有一個 Callback 接口,接口中只有一個 handleMessage() 函數。若是成員變量 mCallBack 存在,就會首先執行此接口中的函數。實際使用中咱們能夠實現此接口,並重寫方法。而後經過 Handler 的構造方法傳入。
public interface Callback {
    public boolean handleMessage(Message msg);
}
複製代碼
  • 最後纔是咱們熟悉的重寫 Handler 類中的 handleMessage 方法,這個方法若是在上面那種處理事後返回的是 true,那麼就根本到不了這個函數。因此說上面兩種處理方式均可以攔截消息。其中第一種是必定會攔截消息,第二種則由返回值肯定。
public void handleMessage(Message msg) {
}
複製代碼

總結

轉了一圈,從 Handler 到 Looper 又到 MessageQ 最後又回到 Handler,整個消息機制也就差很少這樣啦,固然 Message 在其中就是一個實體啦,能夠協上數據一塊兒傳遞消息。我也是根據這個順序,一步一步的慢慢了解每一個步驟的。

最後,附上一張圖吧,會更清晰一點

相關文章
相關標籤/搜索