天天看點

關于 Handler 的靈魂三問

轉載請注明出處:juejin.im/post/5c9896…

寫在前面

之前在郭神的訂閱号,看到了這一篇關于

Handler

的投稿文章

《或許你可以從這個角度去了解Handler》

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

Message

的對象擷取方式,到

Handler

sendMessage

方法解析,再到

enqueueMessage

方法解析。

enqueueMessage

中沒有看到回調方法

handleMessage

,結果是

dispatchMessage

方法調用了

handleMessage

再看

Handler

Looper

類的關系,又發現

loop

方法中調用了

dispatchMessage

,最後分析了

loop

方法,得出結論,

MessageQueue

消息隊列最後是在這個方法執行的。

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

但由于個人了解能力有限,部分地方還存在疑惑,于是就自己動手豐衣足食,結合面試中可能會問到的一些問題來做了學習。

于是有了此文。

這是重點

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()

在循環者

Looper

loop

方法中被調用

擷取到消息後,調用

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

,它并不是一個線程類。

ActivityThread

Android

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

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.

ContentProvider

publish

在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…

轉載于:https://juejin.im/post/5c9896ca6fb9a070f30b0e18