15分鐘徹底掌握Handler

我們一起分析 Android Handler 的源碼。

Handler 現(xiàn)在幾乎是 Android 面試的必問(wèn)知識(shí)點(diǎn)了,大多數(shù) Android 工程師都在項(xiàng)目中使用過(guò) Handler。主要場(chǎng)景是子線程完成耗時(shí)操作的過(guò)程中,通過(guò) Handler 向主線程發(fā)送消息 Message,用來(lái)刷新 UI 界面。這節(jié)課我們來(lái)了解 Handler 的發(fā)送消息和處理消息的源碼實(shí)現(xiàn)。

分析源碼的時(shí)候最好是找到一個(gè)合適的切入點(diǎn),Handler 源碼的一個(gè)切入點(diǎn)就是它的默認(rèn)構(gòu)造器。

從 new Handler() 開(kāi)始

511591752260_.png

在無(wú)參構(gòu)造器里調(diào)用了重載的構(gòu)造方法并分別傳入 null 和 false。并且在構(gòu)造方法中給兩個(gè)全局變量賦值:mLooper 和 mQueue。

這兩者都是通過(guò) Looper 來(lái)獲取,具體代碼如下:


521591768081_.png

可以看出,myLooper 通過(guò)一個(gè)線程本地變量中的存根,然后 mQueue 是 Looper 中的一個(gè)全局變量,類(lèi)型是 MessageQueue 類(lèi)型。

接下來(lái)的分析重點(diǎn)就是這個(gè) Looper 是什么?以及何時(shí)被初始化?

Looper 介紹

不知你有沒(méi)有思考過(guò)一個(gè)問(wèn)題,啟動(dòng)一個(gè) Java 程序的入口函數(shù)是 main 方法,但是當(dāng) main 函數(shù)執(zhí)行完畢之后此程序停止運(yùn)行,也就是進(jìn)程會(huì)自動(dòng)終止。但是當(dāng)我們打開(kāi)一個(gè) Activity 之后,只要我們不按下返回鍵 Activity 會(huì)一直顯示在屏幕上,也就是 Activity 所在進(jìn)程會(huì)一直處于運(yùn)行狀態(tài)。實(shí)際上 Looper 內(nèi)部維護(hù)一個(gè)無(wú)限循環(huán),保證 App 進(jìn)程持續(xù)進(jìn)行。

Looper初始化

Activity 啟動(dòng)過(guò)程時(shí),ActivityThread 的 main 方法是一個(gè)新的 App 進(jìn)程的入口,其具體實(shí)現(xiàn)如下:

531591768172_.png

解釋說(shuō)明:

圖中 1 處就是初始化當(dāng)前進(jìn)程的 Looper 對(duì)象;
圖中 2 處調(diào)用 Looper 的 loop 方法開(kāi)啟無(wú)限循環(huán)。
prepareMainLooper 方法如下:


541591768413_.png

這里我沒(méi)有省略任何一行代碼,因?yàn)榇颂幍拇a很精簡(jiǎn)但是每一行又都有意義。

圖中 1 處在 prepareMainLooper 中調(diào)用 prepare 方法創(chuàng)建 Looper 對(duì)象,仔細(xì)查看發(fā)現(xiàn)其實(shí)就是 new 出一個(gè) Looper。核心之處在于將 new 出的 Looper 設(shè)置到了線程本地變量 sThreadLocal 中。也就是說(shuō)創(chuàng)建的 Looper 與當(dāng)前線程發(fā)生了綁定。

Looper 的構(gòu)造方法如下:


551591768596_.png

可以看出,在構(gòu)造方法中初始化了消息隊(duì)列 MessageQueue 對(duì)象。

prepare 方法執(zhí)行完之后,會(huì)在圖中 3 處調(diào)用 myLooper() 方法,從 sThreadLocal 中取出 Looper 對(duì)象并賦值給 sMainLooper 變量。


561591768607_.png

注意:

圖中 2 處在創(chuàng)建 Looper 對(duì)象之前,會(huì)判斷 sThreaLocal 中是否已經(jīng)綁定過(guò) Looper 對(duì)象,如果是則拋出異常。這行代碼的目的是確保在一個(gè)線程中 Looper.prepare() 方法只能被調(diào)用 1 次。比如以下代碼:


571591768618_.png

執(zhí)行上述代碼程序會(huì)秒崩,打印日志如下:


581591768629_.png

注意:

不是說(shuō)調(diào)用 2 次 prepare 才會(huì)拋異常嗎?為什么 MainActivity 中只調(diào)用了 1 遍就導(dǎo)致程序崩潰? 這是因?yàn)樵?MainActivity 所在進(jìn)程被創(chuàng)建時(shí),Looper 的 prepare 方法已經(jīng)在 main 方法中調(diào)用了 1 遍。這會(huì)直接導(dǎo)致一個(gè)非常重要的結(jié)果:

prepare 方法在一個(gè)線程中只能被調(diào)用 1 次;
Looper 的構(gòu)造方法在一個(gè)線程中只能被調(diào)用 1 次;
最終導(dǎo)致 MessageQueue 在一個(gè)線程中只會(huì)被初始化 1 次。
也就是說(shuō) UI 線程中只會(huì)存在 1 個(gè) MessageQueue 對(duì)象,后續(xù)我們通過(guò) Handler 發(fā)送的消息都會(huì)被發(fā)送到這個(gè) MessageQueue 中。

Looper 負(fù)責(zé)做什么事情

用一句話總結(jié) Looper 做的事情就是:不斷從 MessageQueue 中取出 Message,然后處理 Message 中指定的任務(wù)。

在 ActivityThread 的 main 方法中,除了調(diào)用 Looper.prepareMainLooper 初始化 Looper 對(duì)象之外,還調(diào)用了 Looper.loop 方法開(kāi)啟無(wú)限循環(huán),Looper 的主要功能就是在這個(gè)循環(huán)中完成的。


591591768875_.png

很顯然,loop 方法中執(zhí)行了一個(gè)死循環(huán),這也是一個(gè) Android App 進(jìn)程能夠持續(xù)運(yùn)行的原因。

圖中 1 處不斷地調(diào)用 MessageQueue 的 next 方法取出 Message。如果 message 不為 null 則調(diào)用圖中 2 處進(jìn)行后續(xù)處理。具體就是從 Message 中取出 target 對(duì)象,然后調(diào)用其 dispatchMessage 方法處理 Message 自身。那這個(gè) target 是誰(shuí)呢?查看 Message.java 源碼可以看出 target 就是 Handler 對(duì)象,如下所示:


601591768883_.png

Handler 的 dispatchMessage 方法如下:


611591768897_.png

可以看出,在 dispatchMessage 方法中會(huì)調(diào)用一個(gè)空方法 handleMessage,而這個(gè)方法也正是我們創(chuàng)建 Handler 時(shí)需要覆蓋的方法。那么 Handler 是何時(shí)將其設(shè)置為一個(gè) Message 的 target 的呢?

Handler 的 sendMessage 方法

Handler 有幾個(gè)重載的 sendMessage 方法,但是基本都大同小異。我用最普通的 sendMessage 方法來(lái)分析,代碼具體如下:

611591768897_.png

可以看出,經(jīng)過(guò)幾層調(diào)用之后,sendMessage 最終會(huì)調(diào)用 enqueueMessage 方法將 Message 插入到消息隊(duì)列 MessageQueue 中。而這個(gè)消息隊(duì)列就是我們剛才分析的在 ActivityThread 的 main 方法中通過(guò) Looper 創(chuàng)建的 MessageQueue。

Handler 的 enqueueMessage 方法

621591768911_.png

可以看出:

在圖中 1 處 enqueueMessage 方法中,將 Handler 自身設(shè)置為 Message的target 對(duì)象。因此后續(xù) Message 會(huì)調(diào)用此 Handler 的 dispatchMessage 來(lái)處理;
圖中 2 處會(huì)判斷如果 Message 中的 target 沒(méi)有被設(shè)置,則直接拋出異常;
圖中 3 處會(huì)按照 Message 的時(shí)間 when 來(lái)有序得插入 MessageQueue 中,可以看出 MessageQueue 實(shí)際上是一個(gè)有序隊(duì)列,只不過(guò)是按照 Message 的執(zhí)行時(shí)間來(lái)排序。
至此 Handler 的發(fā)送消息和消息處理流程已經(jīng)介紹完畢,接下來(lái)看幾個(gè)面試中經(jīng)常被問(wèn)到的與 Handler 相關(guān)的題目。

Handler 的 post(Runnable) 與 sendMessage 有什么區(qū)別

看一下 post(Runnable) 的源碼實(shí)現(xiàn)如下:


631591769178_.png

實(shí)際上 post(Runnable) 會(huì)將 Runnable 賦值到 Message 的 callback 變量中,那么這個(gè) Runnable 是在什么地方被執(zhí)行的呢?Looper 從 MessageQueue 中取出 Message 之后,會(huì)調(diào)用 dispatchMessage 方法進(jìn)行處理,再看下其實(shí)現(xiàn):


641591769188_.png

可以看出,dispatchMessage 分兩種情況:

如果 Message 的 Callback 不為 null,一般為通過(guò) post(Runnabl) 方式,會(huì)直接執(zhí)行 Runnable 的 run 方法。因此這里的 Runnable 實(shí)際上就是一個(gè)回調(diào)接口,跟線程 Thread 沒(méi)有任何關(guān)系。
如果 Message 的 Callback 為 null,這種一般為 sendMessage 的方式,則會(huì)調(diào)用 Handler 的 hanlerMessage 方法進(jìn)行處理。
Looper.loop() 為什么不會(huì)阻塞主線程

剛才我們了解了,Looper 中的 loop 方法實(shí)際上是一個(gè)死循環(huán)。但是我們的 UI 線程卻并沒(méi)有被阻塞,反而還能夠進(jìn)行各種手勢(shì)操作,這是為什么呢?在 MessageQueue 的 next 方法中,有如下一段代碼:

651591769196_.png

nativePollOnce 方法是一個(gè) native 方法,當(dāng)調(diào)用此 native 方法時(shí),主線程會(huì)釋放 CPU 資源進(jìn)入休眠狀態(tài),直到下條消息到達(dá)或者有事務(wù)發(fā)生,通過(guò)往 pipe 管道寫(xiě)端寫(xiě)入數(shù)據(jù)來(lái)喚醒主線程工作,這里采用的 epoll 機(jī)制。關(guān)于 nativePollOnce 的詳細(xì)分析可以參考:nativePollOnce函數(shù)分析。

Handler 的 sendMessageDelayed 或者 postDelayed 是如何實(shí)現(xiàn)的

之前我已經(jīng)介紹過(guò),在向 MessageQueue 隊(duì)列中插入 Message 時(shí),會(huì)根據(jù) Message 的執(zhí)行時(shí)間排序。而消息的延時(shí)處理的核心實(shí)現(xiàn)是在獲取 Message 的階段,接下來(lái)看下 MessageQueue 的 next 方法。


661591769209_.png

圖中藍(lán)框處表示從 MessageQueue 中取出一個(gè) Message,但是當(dāng)前的系統(tǒng)時(shí)間小于 Message.when,因此會(huì)計(jì)算一個(gè) timeout,目的是實(shí)現(xiàn)在 timeout 時(shí)間段后再將 UI 線程喚醒,因此后續(xù)處理 Message 的代碼只會(huì)在 timeout 時(shí)間之后才會(huì)被 CPU 執(zhí)行。

注意:在上述代碼中也能看出,如果當(dāng)前系統(tǒng)時(shí)間大于或等于 Message.when,那么會(huì)返回 Message 給 Looper.loop()。但是這個(gè)邏輯只能保證在 when 之前消息不被處理,不能夠保證一定在 when 時(shí)被處理。

總結(jié)

應(yīng)用啟動(dòng)是從 ActivityThread 的 main 開(kāi)始的,先是執(zhí)行了 Looper.prepare(),該方法先是 new 了一個(gè) Looper 對(duì)象,在私有的構(gòu)造方法中又創(chuàng)建了 MessageQueue 作為此 Looper 對(duì)象的成員變量,Looper 對(duì)象通過(guò) ThreadLocal 綁定 MainThread 中;
當(dāng)我們創(chuàng)建 Handler 子類(lèi)對(duì)象時(shí),在構(gòu)造方法中通過(guò) ThreadLocal 獲取綁定的 Looper 對(duì)象,并獲取此 Looper 對(duì)象的成員變量 MessageQueue 作為該 Handler 對(duì)象的成員變量;
在子線程中調(diào)用上一步創(chuàng)建的 Handler 子類(lèi)對(duì)象的 sendMesage(msg) 方法時(shí),在該方法中將 msg 的 target 屬性設(shè)置為自己本身,同時(shí)調(diào)用成員變量 MessageQueue 對(duì)象的 enqueueMessag() 方法將 msg 放入 MessageQueue 中;
主線程創(chuàng)建好之后,會(huì)執(zhí)行 Looper.loop() 方法,該方法中獲取與線程綁定的 Looper 對(duì)象,繼而獲取該 Looper 對(duì)象的成員變量 MessageQueue 對(duì)象,并開(kāi)啟一個(gè)會(huì)阻塞(不占用資源)的死循環(huán),只要 MessageQueue 中有 msg,就會(huì)獲取該 msg,并執(zhí)行 msg.target.dispatchMessage(msg) 方法(msg.target 即上一步引用的 handler 對(duì)象),此方法中調(diào)用了我們第二步創(chuàng)建 handler 子類(lèi)對(duì)象時(shí)覆寫(xiě)的 handleMessage() 方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容