? ? ? ? Handler在android開發(fā)中占有舉足輕重的位置胆数,相信大家都熟悉其用法及基本使用。
Handler是什么互墓?
? ? ? ? Handler是安卓提供的一種消息機(jī)制必尼。通常用于接受子線程發(fā)送的數(shù)據(jù),并用此數(shù)據(jù)配合主線程更新UI。
為什么要用Handler?
? ? ? ? 舉個例子篡撵,我們點擊一個按鈕去服務(wù)器請求數(shù)據(jù)判莉。如果直接在主線程(UI線程)做請求操作,界面會出現(xiàn)假死現(xiàn)象育谬, 如果長時間還沒有完成的話券盅,會收到Android系統(tǒng)的一個錯誤提示? "應(yīng)用程序無響應(yīng)(ANR)"。為什么呢膛檀?因為在Android里, App的響應(yīng)能力是由Activity Manager和Window Manager系統(tǒng)服務(wù)來監(jiān)控的. 通常在如下三種情況下會彈出ANR對話框:
1:KeyDispatchTimeout(谷歌default 5s锰镀,MTK平臺上是8s) --主要類型? 按鍵或觸摸事件在特定時間內(nèi)無響應(yīng)
2:BroadcastTimeout(10s)? BroadcastReceiver在特定時間內(nèi)無法處理完成
3:ServiceTimeout(20s) --小概率類型? Service在特定的時間內(nèi)無法處理完成。
? ? ? ? 既然這樣咖刃,那我們就把這些耗時操作放在子線程中執(zhí)行好了泳炉。 可問題來了,需要把數(shù)據(jù)填充到相關(guān)控件中展示嚎杨。但Android中更新UI只能在主線程中更新花鹅,子線程中操作是危險的。那怎么走出這個困境枫浙,用Handler刨肃!
1.試想一下:如果在一個Activity中,有多個線程去更新UI自脯,并且都沒有加鎖機(jī)制之景,那么會產(chǎn)生什么樣的問題?——更新界面混亂膏潮。
2.是不是在Android中子線程真的不能更新UI锻狗?這也不一定,之所以子線程不能更新UI界面,是因為Android在線程的方法里面采用checkThread進(jìn)行判斷是否是主線程轻纪,而這個方法是在ViewRootImpl中的油额,這個類是在onResume里面才生成的。因此刻帚,如果這個時候子線程在onCreate方法里面生成更新UI潦嘶,而且沒有做耗時多的操作,還是可更新UI的崇众。你可以驗證一下(打開注釋和注釋那行代碼比較下):
protected void onCreate(@Nullable Bundle savedInstanceState) {
? ? ? ? ......
? ? ? new Thread( new Runnable() {? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Override? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //步驟A try{ Thread.sleep(200)}catch(...){...};
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? tv.setText("子線程中訪問");
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? }).start()掂僵;
? ? ? ? ......
}
怎么使用Handler?
? ? ? ? 我們模擬子線程網(wǎng)絡(luò)請求,完成后更新UI操作顷歌。
step1:
step2:
注:代碼中Message為Handler接收與處理的消息對象。?
看看運(yùn)行后打印結(jié)果:
根據(jù)上面打印日志可以看出handlerMessage運(yùn)行在main線程眯漩,故可以在這里更新ui芹扭。而且發(fā)送的what成功正確接收到為1000.
當(dāng)然Handler還有很多其他用法比如:
1.post(Runnable)? ,postDelayed(Runnable ,long);?
2. sendMessage(Message),sendMessageDelayed(Message,long)
3.sendEmptyMessage(int),sendEmptyMessageDelayed(int,long)
....
這里就不一一列舉了赦抖。我們在開發(fā)中常用的延時操作往往借助上面 handler.xxxDelayed方法來輕松實現(xiàn)舱卡。? ? 其實使用上述不管哪種方法都調(diào)用了sendMessageDelayed函數(shù),所以實質(zhì)都是一致的队萤。我們隨便拿一個看看其內(nèi)部實現(xiàn)(如:post(Runnable) ):
上面我們在主線程創(chuàng)建并使用了Handler對象轮锥,可以正常在子線程發(fā)送消息并成功接收。那么就有好事者說了“我要在子線程創(chuàng)建并使用Handler可不可以浮禾,沒別的意思交胚,就是任性想玩玩”。好吧盈电,閑話少說蝴簇,那就我們就來試試...
子線程創(chuàng)建并使用Handler
實際上寫出這個還沒運(yùn)行,我掐指一算就覺得這樣會有問題(就不告訴你我提前已經(jīng)偷偷了解了匆帚,哈哈)熬词。
看紅框部位的報錯信息,知道是線程里創(chuàng)建Handler沒調(diào)用Looper.prepare()方法.這是什么鬼?我們定位到創(chuàng)建Handler時使用的構(gòu)造函數(shù)處:
也就是說Looper.myLooper()得到的對象為空吸重,就拋出了"Can't create handler inside thread that has not called Looper.prepare()"這個異常互拾。進(jìn)入Looper.myLooper()一探究竟:
sThreadLocal.get()返回一個空對象。那sThreadLocal又是什么?在Looper類源碼中可以看到:
sThreadLocal是一個ThreadLocal 靜態(tài)變量嚎幸。那ThreadLocal是什么有什么作用颜矿?
ThreadLocal是什么?
? ? ? ? ThreadLocal 是線程的局部變量嫉晶, 是每一個線程所單獨持有的骑疆。當(dāng)使用ThreadLocal維護(hù)變量的時候 為每一個使用該變量的線程提供一個獨立的變量副本田篇,即每個線程內(nèi)部都會有一個該變量,這樣同時多個線程訪問該變量并不會彼此相互影響箍铭,因此他們使用的都是自己從內(nèi)存中拷貝過來的變量的副本泊柬, 這樣就不存在線程安全問題。
什么意思?做個實例來體驗一下以便于理解诈火。在此之前先介紹下ThreadLocal常用方法--->
public T get() { } // 獲取ThreadLocal在當(dāng)前線程中保存的變量副本? ? ? ? ? ? ? ?
public void set(T value) { } //set()設(shè)置當(dāng)前線程中變量的副本? ? ? ? ? ? ? ? ? ? ? ? ? ?
public void remove() { } //移除當(dāng)前線程中變量的副本? ? ? ? ? ? ? ?
protected T initialValue() { } //一般是用來在使用時進(jìn)行重寫的
通過上面例子應(yīng)該可以檢驗“為每一個使用該變量的線程提供一個獨立的變量副本”的含義了吧兽赁。。冷守。
如果想要了解ThreadLocal內(nèi)部原理移步:Java多線程編程-(8)-多圖深入分析ThreadLocal原理
現(xiàn)在我們清楚了ThreadLocal的作用刀崖,繼續(xù)回到上面對Looper的分析。因sThreadLocal.get()返回null拍摇,導(dǎo)致異常發(fā)生蒲跨。根據(jù)錯誤日志,需要先調(diào)用 Looper.prepare()方法才行。那我們可以推測授翻,prepare()方法里面應(yīng)該做了sThreadLocal.set(looper)操作。
Looper<--->MessageQueue
那么我們具體看看Looper.prepare()了什么?
果不其然孙咪,同我們上面推測的一致堪唐,sThreadLocal調(diào)用set方法保存了一個looper變量,同時可以知道對于每一個線程只能有一個Looper翎蹈。接下來看看Looper(boolean) 構(gòu)造函數(shù):
也就是說我們創(chuàng)建一個Looper對象淮菠,MessageQueue (按字面意思理解:消息隊列)就被創(chuàng)建了。也可以發(fā)現(xiàn)荤堪,默認(rèn)情況下合陵,這個MessageQueue的quiteAllow=true。
我們可以發(fā)現(xiàn):在一個線程中如果存在Looper則 Looper和與之關(guān)聯(lián)的MessageQueue都是唯一的澄阳。那MessageQueue是什么?它是不是與Message有些啥曖昧關(guān)系呢拥知?看著像是Messa-geQueue包養(yǎng)了一隊列的Message。(逃...)
MessageQueue 與 Message
前面我們用到并簡單介紹了下Message碎赢,現(xiàn)在再對它做一下詳細(xì)的分析...
前面四個what,arg1,arg2,obj屬性在用Handler經(jīng)常會用到低剔,應(yīng)該很熟悉。另外還有幾點值得注意:
Message 實現(xiàn)Parcelable 接口肮塞,也即實現(xiàn)了序列化襟齿,說明Message可用于進(jìn)程間通信。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
有一個Handler 對象 target(跟我們使用的Handler有沒有關(guān)枕赵?)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
有個callback的Runnable 對象(是不是想到Handler.post(runnable) 這個runnable)? ? ? ? ? ? ? ? ? ?
推薦使用obtain()猜欺,該方法可以從消息池中獲取Message實例,不推薦直接調(diào)用構(gòu)造方法拷窜。
好吧現(xiàn)在Message內(nèi)部的一些重要特征我們都已打探清楚开皿,那MessageQueue到底是什么呢?
nativeInit()方法實現(xiàn)為android_os_MessageQueue_nativeInit();這是一個底部方法在此不做細(xì)究涧黄。有興趣的自行深入分析。如果只想要了解MessageQueue的作用我們換種方式直接看看其注釋副瀑,便會一目了然:
也就是說MessageQueue確實是用來存放消息(Message對象)的容器(可按字面意思理解為隊列)弓熏。
其實MessageQueue數(shù)據(jù)結(jié)構(gòu),實質(zhì)是一個單向鏈表糠睡,Message對象有個next字段保存列表中的下一個挽鞠,MessageQueue中的mMessages保存鏈表的第一個元素。
既然是“隊列”那它就有其常規(guī)操作:
入隊 :? boolean enqueueMessage(Message msg, long when) {...}
出隊:? Message next() {...}
這里先不詳細(xì)介紹等我們下面按流程分析到在說狈孔。
到此為止我們大概搞清楚:
1.如果希望Handler正常工作,在當(dāng)前線程中要有一個Looper對象
2.在初始化Looper對象時會創(chuàng)建一個與之關(guān)聯(lián)的MessageQueue;
3.MessageQueue存放并管理Message
4.Message是Handler接收與處理的消息對象
上面一步步分析信认,都可謂是為了破解“在子線程創(chuàng)建Handler”所引發(fā)的這一"血案"。至于怎么解決“子線程創(chuàng)建Handler”報出error均抽,相信通過以上講解嫁赏,很容易就大手一揮寫出以下代碼:
然后運(yùn)行一下,當(dāng)然沒有報任何異常.但我們發(fā)現(xiàn)也沒有任何日志打印出來油挥。也就是說我們發(fā)送的消息(sendMessage)沒有接收到潦蝇。那是什么原因?現(xiàn)在該怎么辦?此刻就有一個疑問浮現(xiàn)在腦海---“為什么主線程創(chuàng)建Handler可以正常工作?是不是它做了其他的操作?”
初遇Looper.loop()...
? ? ? ? 我們找到android應(yīng)用程序的入口ActivityThread中的main方法。ActivityThread就是應(yīng)用程序的主線程深寥,打開它的main方法可以看到:
Looper.prepareMainLooper() ?進(jìn)去看看
確實攘乒,Looper.prepareMainLooper() 調(diào)用了prepare()方法(注意這里調(diào)用prepare時傳遞的參數(shù)值為false,和我們之前創(chuàng)建普通Looper時是不同的惋鹅。因為這是主線程则酝,不會被允許被外部代碼終止),所以現(xiàn)在知道為什么在主線程直接創(chuàng)建Handler而不拋異常了吧。然后,后面還有個Looper.loop()?是不是就是少這步操作我們子線程Handler沒有正常工作,那加上試一下:
真的可以正常工作闰集,打印出log了!
那不禁要問沽讹,為什么加了個Looper.loop()就可以正常接收消息了呢?我們這里暫時不深究loop()內(nèi)部到底什么原理,可以暫按得到的效果和其字面含義理解為一個獲取消息的循環(huán)(輪詢)武鲁。至此Handler消息已經(jīng)可以正確接收爽雄,那我們不妨先看看消息是怎么發(fā)送的,因為有入才有出洞坑,我們就先從“入”這個源頭扒起盲链。
Handler發(fā)送消息實現(xiàn)原理
發(fā)送消息我們會調(diào)用handler.post(),handler.sendMessage()等方法,前文也已經(jīng)分析到所有這些方法實質(zhì)都調(diào)用的是sendMessageDelay():
繼續(xù)看sendMessageAtTime:
在前文Handler的構(gòu)造函數(shù)中我們知道m(xù)Queue=mLooper.mQueue迟杂,也即是Looper中關(guān)聯(lián)的MessageQueue對象刽沾。執(zhí)行至equeueMessage:
特別注意:msg.target = this 這句代碼,該message的target賦值為當(dāng)前的handler對象,這里Message就和當(dāng)前Handler關(guān)聯(lián)起來了排拷,記住msg.target很重要,后面我們會用到侧漓。
我們可以看到最后執(zhí)行的是MessageQueue的enqueueMessage方法,前面在介紹MessageQueue的時候我們就知道了這個enqueueMessage方法是用于"入隊"操作的监氢〔颊幔看看源碼也就清楚:
參數(shù)msg是由我們傳進(jìn)去Message對象藤违,when時是執(zhí)行時間,細(xì)心的朋友會發(fā)現(xiàn)mMessages這個對象,我們可以把它理解為是待執(zhí)行的message隊列纵揍,該隊列是按照when的時間排序的且第一個消息是最先執(zhí)行顿乒。
代碼第4行中有三個條件:如果mMessages對象為空,或when為0也就是立刻執(zhí)行泽谨,或者新消息的when時間比mMessages隊列的when時間還要早璧榄,符合以上任一條件就把新的msg插到mMessages的前面 并把next指向它,也就是msg會插進(jìn)隊列的最前面吧雹,等待loop的輪詢骨杂。
如果上面的條件都不符合就進(jìn)入else中,我們可以看到17行是有個for的死循環(huán)遍歷已有的message對象雄卷,其中第20行有個if語句when < p.when when是新消息的執(zhí)行時間搓蚪,p.when的是隊列中message消息的執(zhí)行時間,如果找到比新的message還要晚執(zhí)行的消息丁鹉,就執(zhí)行
msg.next = p;?
prev.next = msg;
也就是把插到該消息的前面妒潭,優(yōu)先執(zhí)行新的消息。
現(xiàn)在我們搞清楚了sendMessage最終就是將消息(Message)放入MessageQueue里面揣钦,由其存放并管理杜耙,那接下來我們要明白的一點就是怎么取出消息了。那就回到了Looper.loop()的解釋了拂盯。
消息接收:再續(xù)Looper.loop()
片段1好理解,我們重點分析片段2 记靡;
這里要給大家說一下谈竿,Linux的一個進(jìn)程間通信機(jī)制:管道(pipe)。
原理:在內(nèi)存中有一個特殊的文件摸吠,這個文件有兩個句柄(引用)空凸,一個是讀取句柄,一個是寫入句柄
主線程Looper從消息隊列讀取消息寸痢,當(dāng)讀完所有消息時呀洲,進(jìn)入睡眠,主線程阻塞啼止。子線程往消息隊列發(fā)送消息道逗,并且往管道文件寫數(shù)據(jù),主線程即被喚醒献烦,從管道文件讀取數(shù)據(jù)滓窍,主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢巩那,再次睡眠吏夯。所以不會過多消耗性能此蜈。
looper里面是一個for死循環(huán),看畫紅線的第一行代碼,是從MessageQueue中提取Message,注釋可能造成阻塞噪生。我們進(jìn)到MessageQueue的next里面看看:
簡單分析一下裆赵,if(now < msg.when)? 若當(dāng)前時間還沒到msg指定時間,設(shè)置一個timeout以到時用于喚醒跺嗽;else msg執(zhí)行出隊操作战授。另外可以發(fā)現(xiàn)當(dāng)msg=null,當(dāng)也即消息隊列為空抛蚁,nextPollTimeoutMillis置為 -1 陈醒,next就會阻塞。
回到loop()方法片段2瞧甩,我們得到了Message對象钉跷,繼續(xù)向下走到第二處紅線位置,也就到了分發(fā)處理Message的時候了肚逸。
? ? ? ? msg.target.dispatchMessage(msg);
記不記得我們分析發(fā)送消息時提到的一個"特別注意"事項(msg.target = this 這句代碼,該message的target賦值為當(dāng)前的handler對象),在這里果然用到了爷辙。也就是說Handler發(fā)送的每個Message對象都存在有該Handler的句柄(target),所以這里實質(zhì)就是調(diào)用了handler的dispatchMessage方法,那我們進(jìn)入Handler.dispatch方法:
現(xiàn)在該明白了為什么我們在使用Handler的時候復(fù)寫其handlerMessage方法朦促,可以在內(nèi)部接收并處理消息了吧膝晾。是不是有種柳暗花明的感覺。
到此為止务冕,我們已經(jīng)把Handler的整個工作原理擼得差不多了血当,大致梳理一下。
當(dāng)我們使用Handler時禀忆,在當(dāng)前線程必須有且僅有一個Looper對象臊旭,Looper里面維護(hù)一個MessageQueue。所以需要提前調(diào)用Looper.prepare()方法將loop對象設(shè)置到內(nèi)部靜態(tài)ThreadLocal中(以保證線程安全)箩退。(主線程已設(shè)置了)
我們用Handler主要是發(fā)送和接收消息离熏。
對于發(fā)送消息:通過post,send等方法(實質(zhì)都是send)發(fā)送消息Message(runnable最后也轉(zhuǎn)化為message)實現(xiàn)。最終都是調(diào)用MessageQueue的enqueueMessage方法將Message插入MessageQueue(上面所說Looper中所維護(hù)的)這一單向鏈表中進(jìn)行統(tǒng)一管理戴涝。
接收消息:我們必須開啟Looper.loop()來輪詢滋戳,通過調(diào)用MessageQueue中的next()方法移除并獲取之前發(fā)送的Message對象(MessageQueue為空時,next阻塞)。獲得的Message對象最終交由發(fā)送該消息的Handler對象(message.target)的dispatchMessage方法處理啥刻。而dispatchMessage方法最后會走到handlerMessage()方法去奸鸯。所以我們在創(chuàng)建Handler時才能夠在其handlerMessage()或callback.
handlerMessage方法中獲取發(fā)送的消息并做出系列操作。
那么至此不經(jīng)要問可帽,主線程中的Looper.loop()一直無限循環(huán)為什么不會造成ANR府喳?
主線程-loop()-anr
首先,我們想一下蘑拯,主線程要沒有Looper.loop()會怎么樣?
顯而易見钝满,如果應(yīng)用入口main方法中沒有l(wèi)ooper進(jìn)行循環(huán)脉让,那么主線程一運(yùn)行完畢就會退出柬帕。那我們打開一個app就過段時間直接關(guān)閉了。也就是說:ActivityThread的main方法主要就是做消息循環(huán),一旦退出消息循環(huán)寿冕,那么你的應(yīng)用也就退出了唐础。
我們知道了looper.loop()是必須的彤枢,再討論這個死循環(huán)為啥不會造成ANR異常惨篱?
? ? ? 因為Android 的是由事件驅(qū)動的,looper.loop() 會不斷地接收/處理事件收厨,每一個點擊觸摸或者說Activity的生命周期都是運(yùn)行在 Looper.loop() 的控制之下晋柱,如果loop停止,那應(yīng)用也就停止了诵叁。所以只能是某一個消息或者說對消息的處理時間超過系統(tǒng)規(guī)定時長(阻塞)才會導(dǎo)致ANR,而不是looper.loop()本身雁竞。
end...
因個人水平有限,有紕漏或錯誤處還望大家批評指正,謝謝拧额!
參考: