關於 Handler 的靈魂三問

轉載請註明出處:juejin.im/post/5c9896…面試

寫在前面

以前在郭神的訂閱號,看到了這一篇關於Handler 的投稿文章bash

《或許你能夠從這個角度去理解Handler》異步

介紹得很詳細,分析源碼的流程也很清晰。ide

Message 的對象獲取方式,到 HandlersendMessage 方法解析,再到 enqueueMessage 方法解析。函數

enqueueMessage 中沒有看到回調方法 handleMessage,結果是 dispatchMessage 方法調用了 handleMessageoop

再看 HandlerLooper 類的關係,又發現 loop 方法中調用了 dispatchMessage,最後分析了 loop 方法,得出結論,MessageQueue 消息隊列最後是在這個方法執行的。post

這位做者大佬也是事無鉅細,在重要的方法和代碼處都加了註釋,使得讀者理解起來更輕鬆,值得學習。學習

但因爲我的理解能力有限,部分地方還存在疑惑,因而就本身動手豐衣足食,結合面試中可能會問到的一些問題來作了學習。ui

因而有了此文。spa

鎮

這是重點

1.Handler 的原理?

面試官最愛問的一個問題

首先咱們瞭解一下
Handler—— 處理者
Message—— 消息
MessageQueue—— 消息隊列
Looper—— 循環者

因而,咱們大概能夠這樣描述:
消息處理者 Handler 從子線程中發送消息 Meesage 到 消息隊列 MessageQueue 中,消息隊列 MessageQueue 會對消息進行排序,循環者 Looper 循環地從消息隊列 MessageQueue 中取出消息,回調給主線程中的消息處理者 Handler, 在 handleMessage 方法處理結果。完成一次消息異步發送的流程。

而後,走一遍流程:

  • 回調方法,在此處理你要作的事情
public Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        //TODO..
    }
}
複製代碼
  • 發送消息
handler.sendMessage(m);

handler.post(r);
複製代碼
  • 發送消息最終調用的是這個方法
queue.enqueueMessage(msg, uptimeMillis);
複製代碼
  • 消息隊列 MessageQueue 中入隊方法 enqueueMessage(),出隊方法 next()
boolean enqueueMessage(Message msg, long when) {...

Message next() {...
複製代碼

next() 在循環者 Looperloop 方法中被調用
獲取到消息後,調用 dispatchMessage(msg) 來分發消息

public static void loop() {...

msg.target.dispatchMessage(msg);
...
複製代碼

最終回到了 Handler 類中的 dispatchMessage 方法,若是 Message 是一個純粹的消息體,那將會調用 handleMessage 方法。

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

到此,走完了此次流程。
我想你大概能知道,如何組織本身的語言回答面試官了。

但,可能多數面試官不止於此。

繼續問道...

2.Handler 爲何能夠 post Runnable?

這個問題,不太難理解,Ctrl+鼠標左鍵,看過源碼的都能知道。咱們跟着源碼走一遍

handler.post(new Runnable() {...
複製代碼

這裏實際調用了 sendMessageDelayed(Message msg, long delayMillis)Runnable 轉成了 Message 對象,如何轉換的呢

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}
複製代碼

能夠看到 Message 中的 callback 屬性是一個 Runnable 對象

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
複製代碼

3.Loop 死循環爲何不會致使主線程 ANR?

這個問題自己多是有問題的...

  • 首先咱們要知道loop方法中爲何要用死循環?

先說 ActivityThread ,雖然類名以Thread 結尾,但它並無繼承 Thread,它並非一個線程類。

ActivityThreadAndroid應用程序的入口,也就是任何一個進程的主線程入口。

public static void main(String[] args) {
    ...
    
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
複製代碼

這就是一個Java 應用,main 函數是入口,當執行到 loop 方法就開始死循環,若是死循環結束,那將會報錯。

能夠看到就是這樣設計的。目的就是爲了咱們點擊應用的圖標時不會一閃而過,而是正確地打開頁面,執行生命週期,響應各類輸入事件。

咱們的代碼就是在主線程 loop 函數中的死循環中被執行的,因此這和 ANR 是兩個不一樣的東西。

至於 Looper 會被阻塞,這個更深刻一些,不在這裏討論。

  • 什麼狀況下會發生ANR?

1.輸入事件(按鍵和觸摸事件)5s內沒被處理。
2.BroadcastReceiver的事件(onRecieve方法)在規定時間內沒處理完(前臺廣播爲10s,後臺廣播爲60s)。
3.service 前臺20s後臺200s未完成啓動。
4.ContentProviderpublish在10s內沒進行完。

因此它不包括 Looper.loop() 的死循環。

寫在後面

一直想總結一下本身對 Handler 的認識,以加強記憶,幫助學習和理解。

直到今天才完成。

關於 Handler,它實現了線程之間的通訊,而線程只是CPU調度的最小單位。瞭解了 Handler 的原理只是看到冰山一角。要想了解更多的通訊方式,還有很長的路要走。

Linux已經擁有的進程間通訊IPC手段包括(Internet Process Connection): 管道(Pipe)、信號(Signal)和跟蹤(Trace)、插口(Socket)、報文隊列(Message)、共享內存(Share Memory)和信號量(Semaphore)。而 Binder 是主要的IPC方式。

記錄在此,僅爲學習!
感謝您的閱讀!歡迎指正!


參考:

1.yanzhenjie.blog.csdn.net/article/det…

2.www.zhihu.com/question/34…

3.www.jianshu.com/p/fa962a5fd…

相關文章
相關標籤/搜索