面試問關於Handler的這些問題你知道嗎?

轉載請以鏈接形式標明出處:
本文出自:103style的博客

目錄

  • Handler 相關的問題 文末參考文章中找到一些以及自己編的一些
  • Handler 相關問題的解答
  • Handler 及相關源碼的介紹 base on android-28

Handler 相關的問題

  • 在線程中可以直接調用 Handler 無參的構造方法嗎?在主線程和子線程中有沒有區別?

  • Handler 機制中涉及到哪些類,各自的功能是什麼?

  • Handler 是可以調用哪些方法發送 Message 到 MessageQueue中?

  • MessageQueue 保存 Message 的數據結構是什麼樣的?

  • MessageQueue 添加 Message 的方式是什麼樣的,頭插法、尾插法 還是 其它?

  • Looper 是怎麼從 MessageQueue 獲取 Message 的?

  • Looper.loop() 爲什麼不會阻塞APP?

  • 可以監測到 MessageQueue 中無數據的情況嗎?可以的話,通過什麼方式?

  • 在 Looper 中處理多個 Handler 的 Message 時,怎麼知道 Message 是要交給哪個 Handler 來處理的?

  • 線程 和 Looper 的對應關係是,一對一、一對多、多對一 還是 其它?Looper 與 MessageQueue 呢?

  • Handler 發送 Message 到 MessageQueue 中,我們可以通過那些方式來監聽輪到 Message 執行了呢?這幾種監聽都加了的話,是否都能收到回調?

  • Handler 容易造成內存泄漏的原因?

  • 在子線程中如何獲取當前線程的 Looper?

  • 如果在任意線程獲取主線程的 Looper?

  • 如何判斷當前線程是不是主線程?


Handler 相關問題的解答

以下代碼中省略了無關代碼

Q :在線程中可以直接調用 Handler 無參的構造方法嗎?在主線程和子線程中有沒有區別?
A:在主線程中可以;在子線程中會拋出RuntimeException, 需要先調用 Looper.prepare()。主線程在啓動的時候已經在調用過Looper.prepare()

public Handler() {
    this(null, false);
}
public Handler(Callback callback, boolean async) {
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException("...");
    }
}

Q :Handler 機制中涉及到哪些類,各自的功能是什麼?
A:Handler、Looper、MessageQueue、Message
Handler:將 Message 對象發送到 MessageQueue 中去。
LooperMessage 對象從 MessageQueue 中取出來,然後交給 Handler 去處理。
MessageQueue:負責 Message 的保存和取出。
Message:用於保存相關信息的消息。


Q :Handler 是可以調用哪些方法發送 Message 到 MessageQueue中?
A:如下

public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
public final boolean postDelayed(Runnable r, long delayMillis)
public final boolean postDelayed(Runnable r, Object token, long delayMillis)
public final boolean postAtFrontOfQueue(Runnable r)
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)
public final boolean sendMessageAtFrontOfQueue(Message msg)

Q:MessageQueue 保存 Message 的數據結構是什麼樣的?
A:由 Message 組成的單鏈表。

public final class Message implements Parcelable {
    ...
    Message next;
    ...
}

Q :MessageQueue 添加 Message 的方式是什麼樣的,頭插法、尾插法 還是 其它?
A: 根據參數 when 排序,如 when <= 0,則添加到最前面,否則按 when 值進行升序排列,插入到對應的位置,如果值(when > 0)相等,則按添加順序排列。

enqueueMessage(Message msg, long when){
    Message p = mMessages;
    if (p == null || when == 0 || when < p.when) {
        msg.next = p;
        mMessages = msg;
    } else {
        Message prev;
        for (;;) {
            prev = p;
            p = p.next;
            if (p == null || when < p.when) {
                break;
            }
        }
        msg.next = p; 
        prev.next = msg;
    }
}

Q :Looper 是怎麼從 MessageQueue 獲取 Message 的?
A:通過 for 循環,調用 MessageQueue.next()MessageQueue 中獲取可用的 Message

public static void loop() {
    for (;;) {
        Message msg = queue.next();
    }
}

Q:Looper.loop() 爲什麼不會阻塞APP?
A:因爲在沒有可用消息的時候會休眠,然後 當 MessageQueue 有可用消息之後(新增的 when<=0 的消息或者到達指定when時)會通過 epoll機制 喚醒。可參考文末參考文章 我所理解的Handler 中關於Java端與Native端建立連接 的介紹。

public static void loop() {
    for (;;) {
        Message msg = queue.next();
        if (msg == null) {
            return;
        }
    }
}

Q:可以監測到 MessageQueue 中無數據的情況嗎?可以的話,通過什麼方式?
A:在 api 23+ 可以通過執行 handler.getLooper().getQueue().addIdleHandler()來添加監聽,queueIdle()return false 表示只監聽一次無數據的情況,return true 則表示監聽每次無數據的情況,

handler.getLooper().getQueue().addIdleHandler(
        new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                return false;
            }
        });

Q:在 Looper 中處理多個 Handler 的 Message 時,怎麼知道 Message 是要交給哪個 Handler 來處理的?
A:因爲 Message 中的 target 變量即爲對應的 Handler.

public static void loop() {
    for (;;) {
        Message msg = queue.next(); 
        try {
            msg.target.dispatchMessage(msg);
        } finally {
        }
    }
}

Q:線程 和 Looper 的對應關係是,一對一、一對多、多對一 還是 其它?Looper 與 MessageQueue 呢?
A:一個線程只能有一個 Looper,當設置之後再調用prepare()會拋出RuntimeException; 但是一個 Looper 可以設置給多個線程,所以是 多對一 的對應關係。LooperMessageQueue 則是一對一關係,MessageQueueLooper 成員變量。

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

Q :Handler 發送 Message 到 MessageQueue 中,我們可以通過那些方式來監聽輪到 Message 執行了呢?這幾種監聽都加了的話,是否都能收到回調?
A:可以通過三種方式:1通過Message.obtain(handler, callback)Message 設置 callback2通過在創建 Handler 的時候設置 Callback3可以重寫 HandlerhandleMessage(msg) 方法。

當三個方式都設置了,也只會有一個回調,優先級按上述的123排列,優先Message 自己的 callback, 然後再是HandlerCallback,最後纔是重寫 HandlerhandleMessage(msg) 方法。

public static void loop() {
    for (;;) {
        Message msg = queue.next(); 
        try {
            msg.target.dispatchMessage(msg);
        } finally {
        }
    }
}
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

Q:Handler 容易造成內存泄漏的原因?
AMessage.target 存有 Handler 的引用,以知道自身由哪一個Handler 來處理。因此,當 Handler 爲非靜態內部類、或持有關鍵對象的其它表現形式時(如Activity 常表現爲 Context ),就引用了其它外部對象。當 Message 得不到處理時,被 Handler 持有的外部對象會一直處於內存泄漏狀態。


Q :在子線程中如何獲取當前線程的 Looper?
A:調用靜態方式 Looper.myLooper(),在子線程中沒有調用 Looper.prepare()時,返回null.

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

Q :如果在任意線程獲取主線程的 Looper?
A:調用靜態方式 Looper.getMainLooper().

public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

Q :如何判斷當前線程是不是主線程?
A:調用 Looper.getMainLooper().isCurrentThread() 或者 Looper.getMainLooper() == Looper.myLooper()


Handler 及相關源碼的介紹

Handler的構造函數

public Handler() {
    this(null, false);
}
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);
}
public Handler(Callback callback, boolean async) {
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException("...");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

Looper的創建

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

MessageQueue

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit(); // jni調用c層 以實現 epoll機制
}

Message

long when;
Bundle data;
Handler target;
Runnable callback;
Message next;

Looper.loop() 循環遍歷消息隊列

public static void loop() {
    final Looper me = myLooper();
    //...
    final MessageQueue queue = me.mQueue;
    for (;;) {
        //獲取可以執行的消息
        Message msg = queue.next();
        if (msg == null) {
            //睡眠 等待喚醒
            return;
        }
        try {
            msg.target.dispatchMessage(msg);
        //...
        msg.recycleUnchecked();
    }
}

MessageQueue.enqueueMessage(…) 保存消息到隊列中

boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //隊列會空 或 when=0 或 小於首個消息的when  添加到最前面
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            Message prev;
            //找到when對應的位置
            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.next() 尋找隊列可用消息

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        //獲取NativeMessageQueue地址失敗,無法正常使用epoll機制
        return null;
    }
    //用來保存註冊到消息隊列中的空閒消息處理器(IdleHandler)的個數
    int pendingIdleHandlerCount = -1;
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //...
        //檢查當前線程的消息隊列中是否有新的消息需要處理,嘗試進入休眠
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //獲取有效的消息
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }

            if (msg != null) {
                if (now < msg.when) {
                    //沒達到消息需要被處理的時間
                    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;
                    msg.markInUse();
                    return msg;
                }
            } else {
                //沒有更多消息,休眠時間無限
                nextPollTimeoutMillis = -1;
            }
            //...
            //獲取空閒處理回調
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                //進入休眠 等待喚醒
                mBlocked = true;
                continue;
            }
            //...
        }
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            //執行空閒處理
            //....
        }

        //重置變量
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

Handler.dispatchMessage()

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

參考文章

你真的懂Handler嗎?Handler問答
我所理解的Handler


如果覺得不錯的話,請幫忙點個讚唄。

以上


掃描下面的二維碼,關注我的公衆號 Android1024, 點關注,不迷路。
Android1024

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