我們一起分析 Android Handler 的源碼攻柠。
Handler 現(xiàn)在幾乎是 Android 面試的必問知識點(diǎn)了立由,大多數(shù) Android 工程師都在項(xiàng)目中使用過 Handler。主要場景是子線程完成耗時(shí)操作的過程中洼裤,通過 Handler 向主線程發(fā)送消息 Message邻辉,用來刷新 UI 界面。這節(jié)課我們來了解 Handler 的發(fā)送消息和處理消息的源碼實(shí)現(xiàn)。
分析源碼的時(shí)候最好是找到一個(gè)合適的切入點(diǎn)值骇,Handler 源碼的一個(gè)切入點(diǎn)就是它的默認(rèn)構(gòu)造器莹菱。
從 new Handler() 開始
在無參構(gòu)造器里調(diào)用了重載的構(gòu)造方法并分別傳入 null 和 false。并且在構(gòu)造方法中給兩個(gè)全局變量賦值:mLooper 和 mQueue吱瘩。
這兩者都是通過 Looper 來獲取道伟,具體代碼如下:
可以看出,myLooper 通過一個(gè)線程本地變量中的存根使碾,然后 mQueue 是 Looper 中的一個(gè)全局變量蜜徽,類型是 MessageQueue 類型。
接下來的分析重點(diǎn)就是這個(gè) Looper 是什么票摇?以及何時(shí)被初始化拘鞋?
Looper 介紹
不知你有沒有思考過一個(gè)問題,啟動(dòng)一個(gè) Java 程序的入口函數(shù)是 main 方法矢门,但是當(dāng) main 函數(shù)執(zhí)行完畢之后此程序停止運(yùn)行掐禁,也就是進(jìn)程會自動(dòng)終止。但是當(dāng)我們打開一個(gè) Activity 之后颅和,只要我們不按下返回鍵 Activity 會一直顯示在屏幕上傅事,也就是 Activity 所在進(jìn)程會一直處于運(yùn)行狀態(tài)。實(shí)際上 Looper 內(nèi)部維護(hù)一個(gè)無限循環(huán)峡扩,保證 App 進(jìn)程持續(xù)進(jìn)行蹭越。
Looper初始化
Activity 啟動(dòng)過程時(shí),ActivityThread 的 main 方法是一個(gè)新的 App 進(jìn)程的入口教届,其具體實(shí)現(xiàn)如下:
解釋說明:
圖中 1 處就是初始化當(dāng)前進(jìn)程的 Looper 對象响鹃;
圖中 2 處調(diào)用 Looper 的 loop 方法開啟無限循環(huán)。
prepareMainLooper 方法如下:
這里我沒有省略任何一行代碼案训,因?yàn)榇颂幍拇a很精簡但是每一行又都有意義买置。
圖中 1 處在 prepareMainLooper 中調(diào)用 prepare 方法創(chuàng)建 Looper 對象,仔細(xì)查看發(fā)現(xiàn)其實(shí)就是 new 出一個(gè) Looper强霎。核心之處在于將 new 出的 Looper 設(shè)置到了線程本地變量 sThreadLocal 中忿项。也就是說創(chuàng)建的 Looper 與當(dāng)前線程發(fā)生了綁定。
Looper 的構(gòu)造方法如下:
可以看出城舞,在構(gòu)造方法中初始化了消息隊(duì)列 MessageQueue 對象轩触。
prepare 方法執(zhí)行完之后,會在圖中 3 處調(diào)用 myLooper() 方法家夺,從 sThreadLocal 中取出 Looper 對象并賦值給 sMainLooper 變量脱柱。
注意:
圖中 2 處在創(chuàng)建 Looper 對象之前,會判斷 sThreaLocal 中是否已經(jīng)綁定過 Looper 對象拉馋,如果是則拋出異常榨为。這行代碼的目的是確保在一個(gè)線程中 Looper.prepare() 方法只能被調(diào)用 1 次惨好。比如以下代碼:
執(zhí)行上述代碼程序會秒崩,打印日志如下:
注意:
不是說調(diào)用 2 次 prepare 才會拋異常嗎随闺?為什么 MainActivity 中只調(diào)用了 1 遍就導(dǎo)致程序崩潰日川? 這是因?yàn)樵?MainActivity 所在進(jìn)程被創(chuàng)建時(shí),Looper 的 prepare 方法已經(jīng)在 main 方法中調(diào)用了 1 遍板壮。這會直接導(dǎo)致一個(gè)非常重要的結(jié)果:
prepare 方法在一個(gè)線程中只能被調(diào)用 1 次逗鸣;
Looper 的構(gòu)造方法在一個(gè)線程中只能被調(diào)用 1 次合住;
最終導(dǎo)致 MessageQueue 在一個(gè)線程中只會被初始化 1 次绰精。
也就是說 UI 線程中只會存在 1 個(gè) MessageQueue 對象,后續(xù)我們通過 Handler 發(fā)送的消息都會被發(fā)送到這個(gè) MessageQueue 中透葛。
Looper 負(fù)責(zé)做什么事情
用一句話總結(jié) Looper 做的事情就是:不斷從 MessageQueue 中取出 Message笨使,然后處理 Message 中指定的任務(wù)升熊。
在 ActivityThread 的 main 方法中毯侦,除了調(diào)用 Looper.prepareMainLooper 初始化 Looper 對象之外粱快,還調(diào)用了 Looper.loop 方法開啟無限循環(huán)撩嚼,Looper 的主要功能就是在這個(gè)循環(huán)中完成的晒来。
很顯然印屁,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 對象,然后調(diào)用其 dispatchMessage 方法處理 Message 自身浩蓉。那這個(gè) target 是誰呢派继?查看 Message.java 源碼可以看出 target 就是 Handler 對象,如下所示:
Handler 的 dispatchMessage 方法如下:
可以看出捻艳,在 dispatchMessage 方法中會調(diào)用一個(gè)空方法 handleMessage驾窟,而這個(gè)方法也正是我們創(chuàng)建 Handler 時(shí)需要覆蓋的方法。那么 Handler 是何時(shí)將其設(shè)置為一個(gè) Message 的 target 的呢认轨?
Handler 的 sendMessage 方法
Handler 有幾個(gè)重載的 sendMessage 方法绅络,但是基本都大同小異。我用最普通的 sendMessage 方法來分析嘁字,代碼具體如下:
可以看出昨稼,經(jīng)過幾層調(diào)用之后,sendMessage 最終會調(diào)用 enqueueMessage 方法將 Message 插入到消息隊(duì)列 MessageQueue 中拳锚。而這個(gè)消息隊(duì)列就是我們剛才分析的在 ActivityThread 的 main 方法中通過 Looper 創(chuàng)建的 MessageQueue假栓。
Handler 的 enqueueMessage 方法
可以看出:
在圖中 1 處 enqueueMessage 方法中,將 Handler 自身設(shè)置為 Message的target 對象霍掺。因此后續(xù) Message 會調(diào)用此 Handler 的 dispatchMessage 來處理匾荆;
圖中 2 處會判斷如果 Message 中的 target 沒有被設(shè)置拌蜘,則直接拋出異常;
圖中 3 處會按照 Message 的時(shí)間 when 來有序得插入 MessageQueue 中牙丽,可以看出 MessageQueue 實(shí)際上是一個(gè)有序隊(duì)列简卧,只不過是按照 Message 的執(zhí)行時(shí)間來排序。
至此 Handler 的發(fā)送消息和消息處理流程已經(jīng)介紹完畢烤芦,接下來看幾個(gè)面試中經(jīng)常被問到的與 Handler 相關(guān)的題目举娩。
Handler 的 post(Runnable) 與 sendMessage 有什么區(qū)別
看一下 post(Runnable) 的源碼實(shí)現(xiàn)如下:
實(shí)際上 post(Runnable) 會將 Runnable 賦值到 Message 的 callback 變量中,那么這個(gè) Runnable 是在什么地方被執(zhí)行的呢构罗?Looper 從 MessageQueue 中取出 Message 之后铜涉,會調(diào)用 dispatchMessage 方法進(jìn)行處理,再看下其實(shí)現(xiàn):
可以看出遂唧,dispatchMessage 分兩種情況:
如果 Message 的 Callback 不為 null芙代,一般為通過 post(Runnabl) 方式,會直接執(zhí)行 Runnable 的 run 方法盖彭。因此這里的 Runnable 實(shí)際上就是一個(gè)回調(diào)接口纹烹,跟線程 Thread 沒有任何關(guān)系。
如果 Message 的 Callback 為 null召边,這種一般為 sendMessage 的方式铺呵,則會調(diào)用 Handler 的 hanlerMessage 方法進(jìn)行處理。
Looper.loop() 為什么不會阻塞主線程
剛才我們了解了隧熙,Looper 中的 loop 方法實(shí)際上是一個(gè)死循環(huán)片挂。但是我們的 UI 線程卻并沒有被阻塞,反而還能夠進(jìn)行各種手勢操作贱鼻,這是為什么呢宴卖?在 MessageQueue 的 next 方法中,有如下一段代碼:
nativePollOnce 方法是一個(gè) native 方法邻悬,當(dāng)調(diào)用此 native 方法時(shí)症昏,主線程會釋放 CPU 資源進(jìn)入休眠狀態(tài),直到下條消息到達(dá)或者有事務(wù)發(fā)生父丰,通過往 pipe 管道寫端寫入數(shù)據(jù)來喚醒主線程工作肝谭,這里采用的 epoll 機(jī)制。關(guān)于 nativePollOnce 的詳細(xì)分析可以參考:nativePollOnce函數(shù)分析蛾扇。
Handler 的 sendMessageDelayed 或者 postDelayed 是如何實(shí)現(xiàn)的
之前我已經(jīng)介紹過攘烛,在向 MessageQueue 隊(duì)列中插入 Message 時(shí),會根據(jù) Message 的執(zhí)行時(shí)間排序镀首。而消息的延時(shí)處理的核心實(shí)現(xiàn)是在獲取 Message 的階段坟漱,接下來看下 MessageQueue 的 next 方法。
圖中藍(lán)框處表示從 MessageQueue 中取出一個(gè) Message更哄,但是當(dāng)前的系統(tǒng)時(shí)間小于 Message.when芋齿,因此會計(jì)算一個(gè) timeout腥寇,目的是實(shí)現(xiàn)在 timeout 時(shí)間段后再將 UI 線程喚醒,因此后續(xù)處理 Message 的代碼只會在 timeout 時(shí)間之后才會被 CPU 執(zhí)行觅捆。
注意:在上述代碼中也能看出赦役,如果當(dāng)前系統(tǒng)時(shí)間大于或等于 Message.when,那么會返回 Message 給 Looper.loop()栅炒。但是這個(gè)邏輯只能保證在 when 之前消息不被處理掂摔,不能夠保證一定在 when 時(shí)被處理。
總結(jié)
應(yīng)用啟動(dòng)是從 ActivityThread 的 main 開始的赢赊,先是執(zhí)行了 Looper.prepare()乙漓,該方法先是 new 了一個(gè) Looper 對象,在私有的構(gòu)造方法中又創(chuàng)建了 MessageQueue 作為此 Looper 對象的成員變量域携,Looper 對象通過 ThreadLocal 綁定 MainThread 中簇秒;
當(dāng)我們創(chuàng)建 Handler 子類對象時(shí)鱼喉,在構(gòu)造方法中通過 ThreadLocal 獲取綁定的 Looper 對象秀鞭,并獲取此 Looper 對象的成員變量 MessageQueue 作為該 Handler 對象的成員變量;
在子線程中調(diào)用上一步創(chuàng)建的 Handler 子類對象的 sendMesage(msg) 方法時(shí)扛禽,在該方法中將 msg 的 target 屬性設(shè)置為自己本身锋边,同時(shí)調(diào)用成員變量 MessageQueue 對象的 enqueueMessag() 方法將 msg 放入 MessageQueue 中;
主線程創(chuàng)建好之后编曼,會執(zhí)行 Looper.loop() 方法豆巨,該方法中獲取與線程綁定的 Looper 對象,繼而獲取該 Looper 對象的成員變量 MessageQueue 對象掐场,并開啟一個(gè)會阻塞(不占用資源)的死循環(huán)往扔,只要 MessageQueue 中有 msg,就會獲取該 msg熊户,并執(zhí)行 msg.target.dispatchMessage(msg) 方法(msg.target 即上一步引用的 handler 對象)萍膛,此方法中調(diào)用了我們第二步創(chuàng)建 handler 子類對象時(shí)覆寫的 handleMessage() 方法。