看完就懂Handler源碼

由Handler提出的問題圖


一廊遍、提出問題

面試時常被問到的問題:

簡述 Android 消息機制

Android 中 Handler璃谨,Looper蚀瘸,MessageQueue芽狗,Message 有什么關(guān)系绢掰?

這倆問題其實是一個問題,其實只要搞清楚了 Handler童擎,Looper滴劲,MessageQueue,Message 的作用和聯(lián)系顾复,就理解了 Android 的 Handler 消息機制班挖。那么再具體一點:

為什么在主線程可以直接使用 Handler?

Looper 對象是如何綁定 MessageQueue 的芯砸?

MessageQueue 里的消息從哪里來萧芙?Handler是如何往MessageQueue中插入消息的?

Message 是如何綁定 Handler 的假丧?

Handler 如何綁定 MessageQueue双揪?

關(guān)于 handler,在任何地方 new handler 都是什么線程下包帚?

Looper 循環(huán)拿到消息后怎么處理渔期?

二、解決問題

從主消息的消息機制開始分析

1.1 主線程Looper的創(chuàng)建和循環(huán)

Android應用程序的入口是main函數(shù),主線程Looper的創(chuàng)建也是在這里完成的.

ActivityThread ---> main()函數(shù)

代碼圖

Looper.prepareMainLooper():用來創(chuàng)建主線程的Looper對象

1.1.1 創(chuàng)建主線程Looper

looper ---> prepareMainLooper()

?looper ---> prepareMainLooper()

prepareMainLooper()方法:主要是使用prepare(false)創(chuàng)建當前線程的Looper對象,再使用myLooper()方法來獲取當前線程的Looper對象.

// step1 Looper() ---> prepare()


Looper() ---> prepare()

prepare()方法中用ThreadLocal來保存主線程的Looper對象.

ThreadLocal:用來存儲數(shù)據(jù)的類,存放屬于當前線程的變量

ThreadLocal提供了get/set方法分別用來獲取和保存變量. ? 比如主線程通過prepare()方法來創(chuàng)建Looper對象,并使用sThreadLocal.set(new Looper(quitAllowed))來保存主線程的Looper對象.那么主線程調(diào)用myLooper()[實際上是調(diào)用了sThreadLocal.get方法].通過ThreadLocal來獲取主線程的Looper對象.

如果在子線程調(diào)用這些方法就是通過ThreadLocal保存和獲取屬于子線程的Looper對象

// step2: Looper ---> myLooper()


Looper ---> myLooper()

myLooper()方法 == ThreadLocal.get()方法 獲取當前線程ThreadLocal保存的Looper對象

附加問題

問題一:為什么在主線程可以直接使用Handler渴邦?

主線程已經(jīng)創(chuàng)建了Looper對象并開啟了消息循環(huán)

問題二:Looper對象是如何綁定MessageQueue的/Looper對象創(chuàng)建MessageQueue的過程

Looper有一個成員變量Queue,它就是Looper對象默認保存的MessageQueue.

上述代碼中Looper自帶一個構(gòu)造器,新建Looper對象時會直接創(chuàng)建MessageQueue并且賦值給Queue.

1.1.2 開始循環(huán)處理消息

回到最開始的main()函數(shù),在創(chuàng)建了Looper對象以后就調(diào)用了Looper.loop()來循環(huán)處理消息


Looper.loop()

Looper ---> loop()


Looper ---> loop()

step1:myLooper():獲取當前線程的對象[ ThreadLocal在哪個線程使用就獲取哪個線程的Lopper對象]

step2:me.mQueue: Looper對象創(chuàng)建時新建的MessageQueue變量

step3: 一個for循環(huán),首先通過 queue.next() 來提取下一條消息

step4:msg.target.dispatchMessage(msg) 這個方法最終會調(diào)用 Handler的handlerMessage(msg)方法

附加問題

msg.target是何時被賦值的/Messge是如何綁定Handler的? (那我們來看看dispatchMessage()這個方法)


dispatchMessage()

可以看到最后執(zhí)行了handlerMessage()方法 這是一個空方法也就是需要我們覆寫并實現(xiàn)的

dispatchMessage附加問題

消息分發(fā)的優(yōu)先級

????·Message的回調(diào)方法:message.callback.run()優(yōu)先級最高;

????·Handler的回調(diào)方法:mCallback.handleMessage(msg)優(yōu)先級次于上方;

????·Handler的回調(diào)方法:handleMessage()優(yōu)先級最低;

Looper循環(huán)通過Handler發(fā)送消息一個整體的流程.接下來分析Handler在消息機制中的主要作用以及和Looper疯趟、Message的關(guān)系.


1.2 Handler的創(chuàng)建和作用

上面說到loop()方法不斷從消息隊列MessageQueue中取出消息(queue.next()方法),如果沒有消息則阻塞,反之交給Message綁定的Handler處理

需要解決的問題

????· MessageQueue里的消息從哪里來的?

????· Handler是如何往MessageQueue中插入消息的?

? ? · " msg.target "是何時被賦值的?/Message是如何綁定Handler的?


1.2.1 Handler發(fā)送消息

Handler ---> sendMessage(Message msg)


Handler ---> sendMessage(Message msg)

可以看到sendMessage(Message msg) 方法最終會調(diào)用enqueueMessage()方法,這個方法主要有兩個作用

? ? ? ? ? ? 1> 賦值Message對象的target

? ? ? ? ? ? ? ? ? ? msg.target = this 把發(fā)送消息的Handler賦值給msg的target.

? ? ? ? ? ? 2> 消息隊列插入消息

? ? ? ? ? ? ? ? ? ? queue.enqueueMessage(msg,uptimeMillis) queue是MessageQueue的一個實例

????????????????????queue.enqueueMessage(msg,uptimeMillis) 是執(zhí)行 MessageQueue的enqueueMessage方法來插入消息


1.2.2 Handler的創(chuàng)建

下面是Handler無參構(gòu)造器和主要構(gòu)造器,另外幾個重載的構(gòu)造器是有些通過傳遞不同的參數(shù)調(diào)用包含兩個參數(shù)的構(gòu)造器

兩個參數(shù)構(gòu)造器的第一個參數(shù)為callback回調(diào),第二個函數(shù)用來標記消息是否異步


handler構(gòu)造代碼圖

step1: 調(diào)用myLooper()方法,該方法是使用sThreadLocal對象獲取當前線程的Looper對象

回顧圖

如果獲取的Looper對象為NULL,說明沒有執(zhí)行Looper.prepare()為當前線程保存Looper變量,就會拋出RuntimeException異常.

這里又說明了Handler必須再有Looper的線程中使用.報錯先放一邊,沒有Looper就無法綁定MessageQueue對象也就無法進行更多有關(guān)操作

step2: mQueue = mLooper.mQueue; 說明了 Handler的MessageQueue對象是由當線程Looper的MessageQueue賦值.

由于Handler和Looper可以看作使用的是一個HandlerMessageQueue對象,所以Handler和Looper共享消息隊列MessageQueue.

? ? ? ? ? ? ·Handler發(fā)送消息(用mQueue往消息隊列插入消息)

? ? ? ? ? ? ·Looper可以方便的循環(huán)使用mQueue查詢消息.如果查詢到消息,就可以用Message對象綁定的Handlertarget去處理消息,反之則阻塞.

補充問題

關(guān)于handler,在任何地方new handler都是什么線程下?(這個問題要分是否傳遞Looper對象來看)

? ? ? ? ·傳遞Looper對象創(chuàng)建Handler: Handler handler = new Handler(looper);?

傳遞Looper對象創(chuàng)建Handler

? ? ·不傳遞 Looper 創(chuàng)建 Handler:Handler handler = new Handler() 上文就是 Handler 無參創(chuàng)建的源碼,可以看到是通過 Looper.myLooper() 來獲取 Looper 對象谋梭,也就是說對于不傳遞 Looper 對象的情況下信峻,在哪個線程創(chuàng)建 Handler 默認獲取的就是該線程的 Looper 對象,那么 Handler 的一系列操作都是在該線程進行的瓮床。


可以看出來傳遞Looper對象Handler就直接使用了.所以對于傳遞Looper對象創(chuàng)建Handler的情況下,傳遞的Looper是哪個線程的,Handler綁定的就是該線程

到這里Looper和Handler就有應該大概的流程了,我們看一下應該簡單的子線程Handler的使用例子

子線程Handler的使用例子

step1:調(diào)用Looper.prepare() 為當前線程創(chuàng)建Looper對象,同時也就創(chuàng)建了MessageQueue對象[創(chuàng)建Looper對象的同時,Looper會自帶MessageQueue對象]盹舞。之后將該線程的Looper對象保存在ThreadLocal中.注意這里的一切操作都在子線程中完成,如果不調(diào)用Looper.prepare方法就會導致Handler報錯.


step2:Handler handler = new Handler() 創(chuàng)建Handler對象,覆寫handlerMessage處理消息,等待該Handler發(fā)送的消息處理時會調(diào)用該方法.


step3:handler.sendEmpteMessage(1) 使用handler發(fā)送消息,這里只是單例. 發(fā)送的過程中會將自己賦值給msg.target,然后再將消息插入到Looper綁定的MessageQueue()中;


step4:Looper.loop() 首先獲取當前線程的Looper,根據(jù)Looper對象可以拿到Looper保存在MessageQueue中的對象mQueue.有了MessageQueue對象就可以for循環(huán)它保存的消息Message對象,如果沒有消息就返回null導致阻塞. 然后用Message中保存的Handler:msg.target來處理消息,最終調(diào)用handlerMessage覆寫的方法來處理消息


step5:Looper.myLooper.quit() 邏輯處理完畢后,需要調(diào)用quit方法來終止消息循環(huán),否則這個子線程就會一直處于等待狀態(tài),而如果子線程處于等待狀態(tài)退出Looper以后,這個線程就會被立刻終止产镐。[因此建議不需要使用Handler的時候終止Looper]


三、總結(jié)和其他小知識點

2.1 Handler矾策、Looper磷账、MessageQueue、Message

? ? ·Handler用來發(fā)送消息,創(chuàng)建時先獲取默認或傳遞來的Looper對象,并持有Looper對象包含的MessageQueue對象,發(fā)送消息的時候該MessageQueue對象來插入消息并把自己封裝到具體的Message中;

? ? ·Looper用來為某個線程作消息循環(huán),.Looper持有一個MessageQueue對象mQueue,這樣就可以通過循環(huán)來獲取MessageQueue所維護的Message.如果獲取MessageQueue沒有消息時,便阻塞Loop的queue.next()中的nativePollOnce()方法,隨后喚起主線程繼續(xù)工作,之后便用Message封裝的handler對象進行處理.

? ? ·MessageQueue是一個消息隊列.他不直接添加消息,而是通過Looper關(guān)聯(lián)的Handler對象來添加消息.

? ? ·Message包含了要傳遞的數(shù)據(jù)和信息


2.2.1 Android中為什么主線程不會因為Looper.loop()里的死循環(huán)卡死?

(簡單做法就是可執(zhí)行代碼是一直能執(zhí)行下去的,死循環(huán)便能保證不會被退出) 通過創(chuàng)建新線程的方式

為這個死循環(huán)準備了一個新的線程(在進入死循環(huán)之前便創(chuàng)建了新的binder[Binder詳細講解]線程,在代碼ActivityThread.main()中)

通過binder創(chuàng)建新線程

thread.attach(false):創(chuàng)建一個Binder線程[具體是指ApplicationThread,Binder的服務器,用于接收系統(tǒng)服務AMS發(fā)送來的事件]該binder線程通過Handler將Message發(fā)送給主線程


主線程的死循環(huán)一直運行是不是特別消耗CPU資源呢?

其實不是的,這里涉及到Linux pipeLinux pipe詳細教學/epoll機制Epoll機制詳細教學(Linux pipe與Epoll的區(qū)別).簡單的說在主線程的MessageQueue沒有消息時,便阻塞loop的quque.next().所以說大多數(shù)時候都處于休眠狀態(tài),并不會消耗大量CPU資源.


Activity的生命周期是怎么實現(xiàn)在死循環(huán)體外能夠執(zhí)行起來的?

函數(shù)有一部分獲取sMainThreadHandler的代碼

類H繼承了Handler,在主線程創(chuàng)建時就創(chuàng)建了Handler用于處理Binder線程發(fā)送來的消息

Activity的生命周期都是依靠主線程的Looper.loop贾虽,當收到不同Message時則采用相應措施:

在H.handleMessage(msg)方法中逃糟,根據(jù)接收到不同的msg,執(zhí)行相應的生命周期蓬豁。

比如收到msg=H.LAUNCH_ACTIVITY绰咽,則調(diào)用ActivityThread.handleLaunchActivity()方法,最終會通過反射機制地粪,創(chuàng)建Activity實例取募,然后再執(zhí)行Activity.onCreate()等方法;

再比如收到msg=H.PAUSE_ACTIVITY蟆技,則調(diào)用ActivityThread.handlePauseActivity()方法玩敏,最終會執(zhí)行Activity.onPause()等方法。 上述過程质礼,我只挑核心邏輯講旺聚,真正該過程遠比這復雜。


3.3 Handler使用造成內(nèi)存泄漏

? ? ? ?? · 有延時消息,要在Activity銷毀的時候移除Messages

? ? ? ?? · 匿名內(nèi)部類導致的泄漏改為匿名靜態(tài)內(nèi)部類,并且對上下文或者Activity使用弱引用



4.1Handler原理

handler的構(gòu)造方法內(nèi)部通過Looper.myLooper創(chuàng)建一個Looper,然后通過Looper去創(chuàng)建一個消息隊列

這個時候的handler,looper與messagequeue就捆綁到一起了這個時候SendEmptyMessage()與sendEmptyMessageDelayed()最終都會到enqueueMessage()方法當中.

這個方法是將發(fā)送的消息添加到消息隊列中.在這個方法中會把當前的handler對象綁定到message當中的一個target變量中.這樣就完成了handler向消息隊列存放消息的過程.

工作原理流程圖

4.2 Handler的應用場景

因為Android中,主線程不建議做耗時操作,子線程不建議更新UI.

但Android開發(fā)其實就是搭建好頁面,將服務器的數(shù)據(jù)展示到頁面上.所以網(wǎng)絡請求會使用的很頻繁

而網(wǎng)絡請求屬于耗時操作,需要放到子線程中完成.

但一般情況下也不會通過子線程更新UI.需要將請求成功的數(shù)據(jù)發(fā)送到主線程進行UI更新

所以一般使用handler

4.3 ThreadLocal

1>定義:ThreadLocal是線程內(nèi)部的數(shù)據(jù)存儲類


4.4 HandlerThread

1>定義:是一個Android已經(jīng)封裝好的輕量級異步類

2>作用: 實現(xiàn)多線程/異步通信眶蕉、消息傳遞

3>優(yōu)點:方便實現(xiàn)異步通信


4.5 內(nèi)部原理=Thread類+Handler機制




加油加油加油


補充:

Looper:輪詢消息,獲取到消息后發(fā)送給Handler

? ? a.取消息

? ? b.消息給到Handler

? p?rpare() 初始化Looper,只有一個

? Looper() 構(gòu)造 初始化了消息隊列

? loop() 輪詢方法(取消息 將消息給到handler)

Handler

? ? a.發(fā)消息

? ? b.處理消息

創(chuàng)建Handler保證當前線程持有Looper對象

sendMessage() 這個方法最終會走enqueueMessage()方法 在這個方法中會把當前的handler對象綁定到message當中的一個target變量中.這樣就完成了handler向消息隊列存放消息的過程.

MessageQueue

? ? 存儲消息(排隊)

Message????????

? ? 數(shù)據(jù)的載體

ThreadLocal

? ? 一個存儲線程對象的容器


口述

首先我們Handler內(nèi)部構(gòu)造通過Looper.myLooper創(chuàng)建了一個Looper對象,Looper對象自己攜帶構(gòu)造器創(chuàng)建了MessageQueue對象,這樣Handler砰粹、Looper、MessageQueue綁定到了一起 Looper:它是用來輪詢消息,獲取到消息交給Handler Handler呢負責發(fā)送消息 處理消息 MessageQueue:是一個存儲消息的隊列.我們先走到Looper里面的p?rpare()方法這個方法只能調(diào)用一次,它是用來創(chuàng)建Looper對象的,在這個方法里面有一個ThreadLocal對象,它是用來存儲Looper對象的.用這個方法來保證一個線程只能有一個Looper實例.然后我們看一下Looper的構(gòu)造,里面初始化了MessageQueue對象并且保存了當前線程.消息是如何傳進Handler里面的 看一下Loop.loop() 首先里面先調(diào)用了一個myLooper() 它把我們剛開始通過p?rpare()方法創(chuàng)建的Looper對象取了出來 如果為Looper為空則拋出異常 Looper不為空的話將messageQueue對象獲取 進行死循環(huán)遍歷 通過queue.next()方法取出來消息 如果消息為null則返回如果消息不為空的話調(diào)用msg.target.dispatchMessage(msg)方法這個方法就是將消息傳遞給handler的[target就是handler的一個對象] 在dispatchMessage()這個方法 通過他處理消息 這個方法里面可以看到 當mag的callBack對象是null的時候它會走一個handleMessage(Message msg) 是一個空方法需要我們覆寫? 我們Handler通過sendMessage()這個方法發(fā)送消息 隨后走到SendEmptyMessage()與sendEmptyMessageDelayed() 這兩個方法最終調(diào)用 enqueueMessage()這個方法 它會通過 msg.target =this; 把發(fā)送消息的Handler賦值給msg的target. 這樣就完成了handler向消息隊列存放消息的過程

為什么主線程不需要調(diào)用Looper.prepare()造挽? 因為系統(tǒng)已經(jīng)幫我們創(chuàng)建過了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碱璃,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子饭入,更是在濱河造成了極大的恐慌嵌器,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件圣拄,死亡現(xiàn)場離奇詭異嘴秸,居然都是意外死亡,警方通過查閱死者的電腦和手機庇谆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凭疮,“玉大人饭耳,你說我怎么就攤上這事≈唇猓” “怎么了寞肖?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵纲酗,是天一觀的道長。 經(jīng)常有香客問我新蟆,道長觅赊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任琼稻,我火速辦了婚禮吮螺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帕翻。我一直安慰自己鸠补,他們只是感情好,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布嘀掸。 她就那樣靜靜地躺著紫岩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪睬塌。 梳的紋絲不亂的頭發(fā)上泉蝌,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天,我揣著相機與錄音揩晴,去河邊找鬼勋陪。 笑死,一個胖子當著我的面吹牛文狱,可吹牛的內(nèi)容都是我干的粥鞋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瞄崇,長吁一口氣:“原來是場噩夢啊……” “哼呻粹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起苏研,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤等浊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后摹蘑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筹燕,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年衅鹿,在試婚紗的時候發(fā)現(xiàn)自己被綠了撒踪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡大渤,死狀恐怖制妄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情泵三,我是刑警寧澤耕捞,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布衔掸,位于F島的核電站,受9級特大地震影響俺抽,放射性物質(zhì)發(fā)生泄漏敞映。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一磷斧、第九天 我趴在偏房一處隱蔽的房頂上張望振愿。 院中可真熱鬧,春花似錦瞳抓、人聲如沸埃疫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽栓霜。三九已至,卻和暖如春横蜒,著一層夾襖步出監(jiān)牢的瞬間胳蛮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工丛晌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留仅炊,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓澎蛛,卻偏偏與公主長得像抚垄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谋逻,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 一呆馁、提出問題 面試時常被問到的問題: 簡述 Android 消息機制 Android 中 Handler,Loop...
    Marker_Sky閱讀 2,262評論 0 18
  • 為了更好的理解 Looper 的工作原理毁兆,我們需要對 ThreadLocal 進行了解浙滤,如果對 ThreadLoc...
    墨染書閱讀 1,475評論 0 3
  • 注:本來只是想整理成Handler部分的面試題及答案拿來背誦的,哪知一看源碼就沒停下來气堕,變成了大篇文章...本文根...
    Hoooopa閱讀 714評論 2 1
  • 前言 在Android開發(fā)的多線程應用場景中纺腊,Handler機制十分常用 今天,我將手把手帶你深入分析Handle...
    BrotherChen閱讀 472評論 0 0
  • 本人上周攜老公前往種草已久的人氣法國餐廳-TRB茎芭,享受閑暇時光揖膜。以下是對于每道菜的點評: 餐前面包是少不了的,從口...
    田睿音閱讀 618評論 1 1