Android 消息机制 Handler总结
老久就想著寫一篇 關(guān)于消息機(jī)制的文章來(lái)總結(jié)一下。
Android的消息機(jī)制主要是指Handler 的運(yùn)行機(jī)制。我們?cè)陂_(kāi)發(fā)時(shí)有的時(shí)候需要在子線程進(jìn)行耗時(shí)的I/o 操作,可能是讀取文件或者 訪問(wèn)網(wǎng)絡(luò)等,有時(shí)候耗時(shí)工作完成需要在UI上做響應(yīng)改變,又知道Android開(kāi)發(fā)的規(guī)范限制,不能在子線程中訪問(wèn)更新UI,不然會(huì)出現(xiàn)程序異常,這時(shí)候Hanlder擔(dān)任了這樣的角色,把更新UI的操作挪動(dòng)到主線程中去操作。當(dāng)然Handler并不是僅僅這點(diǎn)作用的。
Handler的運(yùn)行需要底層 MessageQueue 和Looper的支持。
MessageQueue(消息隊(duì)列),采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)的消息列表。不能處理消息。
Looper(消息循環(huán)),專門用來(lái)處理消息,Looper會(huì)以無(wú)限循環(huán)的形式去查找是否有新消息,有就立刻處理,沒(méi)有的話就一直輪詢等待。 Handler創(chuàng)建的時(shí)候會(huì)采用當(dāng)前線程的Looper來(lái)構(gòu)造消息循環(huán)系統(tǒng)。
ThreadLocal(特殊的Looper),首先他不是線程,他的作用就是在每個(gè)線程中存儲(chǔ)數(shù)據(jù)。ThreadLocal可以在不同的線程中互不干擾的存儲(chǔ)并提供數(shù)據(jù),Handler通過(guò)Thread Local 獲取到每個(gè)線程的Looper,無(wú)論是主線程(UI線程(別名:ActivityThread,當(dāng)主線程創(chuàng)建的時(shí)候,就會(huì)初始化Looper)),還是子線程。
為什么不允許子線程訪問(wèn)UI?因?yàn)锳ndroid的線程是不安全的,如果在多線程中并發(fā)訪問(wèn)極有可能導(dǎo)致UI控件狀態(tài)不可控,還有一種狀況。
可不可以加鎖機(jī)制呢? 最好不要用啦,但是會(huì)使訪問(wèn)UI的效率降低很多,鎖機(jī)制會(huì)阻塞一些線程的執(zhí)行,另外實(shí)現(xiàn)起訪問(wèn)UI的邏輯變得很復(fù)雜。所以采用這種Handler單線程來(lái)處理,簡(jiǎn)單,高效。
消息機(jī)制的流程
Handler 創(chuàng)建的時(shí)候,就會(huì)初始化Looper,不然會(huì)報(bào)異常:
java.long.RuntimeException:Can't create handler inside thread that has not called Looper.prepare();Handler創(chuàng)建完之后,這時(shí)候內(nèi)部的Looper和MessageQueue ,三者一起協(xié)同工作啦,
通過(guò)Handler 的send方法發(fā)送一個(gè)message,這個(gè)message會(huì)在Handler內(nèi)部Looper處理,也可以通過(guò)Handler的post的方法將一個(gè)Runnable 投遞到Handler內(nèi)部的Looper中去處理,(post方法的底層實(shí)現(xiàn)還是通過(guò)send方法實(shí)現(xiàn)投遞的)。
Handler的send方法被調(diào)用時(shí),也會(huì)調(diào)用Message Queue 的嗯卻 u 俄 Message方法 將此消息放入消息隊(duì)列中,然后那個(gè)無(wú)限循環(huán)的Looper發(fā)現(xiàn)有隊(duì)列中有消息了,就會(huì)處理這個(gè)消息,消息中的Runnable 或者Handler的handlerMessage 方法被調(diào)用。此時(shí)的Looper是運(yùn)行在Handler所創(chuàng)建的線程中。
Handler
代碼如下:
在子線程中
Message msg = Message.obtain();
msg.obj = 要傳遞的數(shù)據(jù);
//msg.what = 1 ; //給msg打標(biāo)記
handler.sendMessage(msg);
在主線程創(chuàng)建handler對(duì)象 復(fù)寫方法 接收msg 更新UI
NewsAdapter adapter= (NewsAdapter) msg.obj;
lv_news.setAdapter(adapter);
Handler內(nèi)存泄露
解決辦法1:
在Activity的onDestroy中通過(guò)removeCallbacks, removeMessages移除指定消息,或用removeCallbacksAndMessages(null)移除消息隊(duì)列中所有消息.
解決辦法2:
將Handler聲明為靜態(tài)
最好是上述兩種辦法一起用
ThreadLocal的原理
ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)儲(chǔ)存類,通過(guò)它可以在指定線程中儲(chǔ)存數(shù)據(jù)(也只有這個(gè)指定線程能獲取到它儲(chǔ)存的數(shù)據(jù)),也就是說(shuō),它可以在不同線程中互不干擾的儲(chǔ)存數(shù)據(jù)
通過(guò)它可以獲取每個(gè)線程的Looper。Handler創(chuàng)建時(shí)采用當(dāng)前線程的Looper來(lái)構(gòu)造消息循環(huán)系統(tǒng),就是通過(guò)ThreadLocal來(lái)獲取當(dāng)前線程的Looper
使用方法
private ThreadLocal mTL = new ThreadLocal<>();
然后在不同線程中直接用mTL對(duì)象對(duì)T變量進(jìn)行set(value)儲(chǔ)存或get()獲取即可
不同線程訪問(wèn)同一個(gè)ThreadLocal的get方法時(shí),ThreadLocal內(nèi)部會(huì)從各自線程中取出一個(gè)數(shù)組,然后再根據(jù)索引取值,顯然不同線程中的數(shù)組是不一樣的,所以互不干擾。
使用場(chǎng)景
* 當(dāng)某些數(shù)據(jù)是以線程為作用域,并且不同線程具有不同的數(shù)據(jù)副本時(shí),就可以采用ThreadLocal。例如Looper,它作用域就是線程,并且不同線程具有不同的Looper。
* 又例如在復(fù)雜邏輯下的監(jiān)聽(tīng)器,如果需要監(jiān)聽(tīng)器貫穿整個(gè)線程,就可以用ThreadLocal讓監(jiān)聽(tīng)器成為線程的全局對(duì)象,在線程內(nèi)只需通過(guò) get即可拿到監(jiān)聽(tīng)器。如果不用ThreadLocal,就只有兩種方法:通過(guò)參數(shù)在方法間傳遞,該方法在邏輯復(fù)雜時(shí)不可接受;聲明監(jiān)聽(tīng)器為靜態(tài),這種辦法只適用于一個(gè)線程,如果N個(gè)線程執(zhí)行,那就需要N個(gè)靜態(tài)監(jiān)聽(tīng)器對(duì)象,也不好。
原理
ThreadLocal有個(gè)內(nèi)部類:Values,它是專門用來(lái)儲(chǔ)存線程的ThreadLocal數(shù)據(jù)
每個(gè)Thread的成員位置都聲明了一個(gè)Values
ThreadLocal有個(gè)成員變量reference,它是對(duì)ThreadLocal自己的一個(gè)弱引用
Values類內(nèi)部成員位置有個(gè)Object數(shù)組table,用于儲(chǔ)存當(dāng)前線程的數(shù)據(jù)
當(dāng)調(diào)用set時(shí),先拿到當(dāng)前線程對(duì)象Thread,然后取出當(dāng)前線程的Values數(shù)據(jù),如果為空就初始化一下,然后調(diào)用Values的put方法往table里儲(chǔ)存數(shù)據(jù)。put儲(chǔ)存數(shù)據(jù)時(shí)總是會(huì)把數(shù)據(jù)存到reference的后一個(gè)(例:如果把reference存到了table的index位置,那么就把數(shù)據(jù)存到index+1位置)
get時(shí),同樣是先取出當(dāng)前Thread的values對(duì)象,如果為空就返回初始值(初始值由initialValues方法指定)。如果不為空,就查找reference在table中的位置,找到后取出位置+1的數(shù)據(jù)即是我們儲(chǔ)存的數(shù)據(jù)
綜上可知,不同線程操作同一個(gè)ThreadLocal對(duì)象時(shí),實(shí)際上整個(gè)操作過(guò)程都還是在各自線程內(nèi)(都是操作各自線程的values對(duì)象)
Handler的原理
使用Handler時(shí)涉及到四個(gè)對(duì)象:
在子線程中開(kāi)啟Looper后,處理完消息必須調(diào)用quit方法終止消息循環(huán),然后線程就會(huì)終止,不然該子線程會(huì)一直處于等待狀態(tài)。
流程
* Looper構(gòu)造時(shí)會(huì)創(chuàng)建MessageQueue對(duì)象,并將當(dāng)前線程Thread對(duì)象儲(chǔ)存。子線程可以通過(guò)Looper.prepare()創(chuàng)建Looper,通過(guò)Looper.loop()開(kāi)啟消息循環(huán)。* 調(diào)用loop()開(kāi)啟循環(huán)后,Looper會(huì)循環(huán)調(diào)用MessageQueue的next方法來(lái)取出消息。next方法也是個(gè)死循環(huán),沒(méi)有消息時(shí)會(huì)一直阻塞,于是Looper也跟著阻塞在這。當(dāng)調(diào)用了quit或quitSafely標(biāo)記為退出循環(huán)時(shí),next方法會(huì)返回null,這時(shí)Looper才會(huì)終止循環(huán)。* 創(chuàng)建Handler時(shí)會(huì)采用當(dāng)前線程的Looper來(lái)構(gòu)建消息循環(huán)系統(tǒng),然后就可以通過(guò)post/send發(fā)送消息* send和post方法最終都會(huì)調(diào)用sendMessageAtTime方法來(lái)發(fā)送消息,而post方法其實(shí)就是調(diào)用getPostMessage方法將Runnable對(duì)象儲(chǔ)存到msg的callback成員變量中然后也是send出去。sendMessageAtTime方法就是把當(dāng)前handler對(duì)象存到msg的target中,然后往Queue中插入一條消息。* 然后調(diào)用MessageQueue的enqueueMessage方法讓消息入列* Looper取出消息,然后取出msg里的target(這個(gè)儲(chǔ)存的就是發(fā)送時(shí)用的handler),調(diào)用handler的dispatchMessage方法來(lái)分發(fā)消息。handler的dispatchMessage方法是在handler所在線程執(zhí)行的。dispatchMessage方法如下: public void dispatchMessage(Message msg) {//第一種處理消息方式if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {//第二種處理消息方式if (mCallback.handleMessage(msg)) {return;}}//第三種處理消息方式handleMessage(msg);} }我們通過(guò)post發(fā)送消息時(shí),就會(huì)執(zhí)行第一種處理方法,這里的callback就是我們的runnable;handleCallback方法就是調(diào)用runnable的run方法;
當(dāng)我們創(chuàng)建Handler對(duì)象時(shí),在構(gòu)造中傳入Handler.Callback接口的實(shí)現(xiàn)時(shí),就會(huì)采用第二種處理方法;這種構(gòu)造Handler實(shí)例的方式是不會(huì)派生Handler子類的。
我們實(shí)現(xiàn)Handler.Callback時(shí),在其handleMessage方法最終返回false;或者構(gòu)造Handler時(shí)用的無(wú)參構(gòu)造,就會(huì)調(diào)用第三種處理方法,即我們派生Handler子類時(shí)重寫的handleMessage方法
主線程內(nèi)部也有個(gè)Handler,ActivityThread通過(guò)ApplicationThread與AMS通信,AMS完成請(qǐng)求后回調(diào)ApplicationThread的對(duì)應(yīng)方法,然后ApplicationThread會(huì)發(fā)送消息到這個(gè)Handler,Handler收到消息后會(huì)將邏輯切換到ActivityThread中執(zhí)行,即切換到了主線程。
總結(jié)
如果一個(gè)線程Thread想要使用Handler類來(lái)發(fā)送消息,就必須先調(diào)用Looper類中的prepare成員方法來(lái)做一些準(zhǔn)備工作,然后調(diào)用Looper類中的loop方法啟動(dòng)該線程的循環(huán)機(jī)制。Handler類把封裝成Message類的消息發(fā)送到Looper類中的消息隊(duì)列中,然后Looper類中的loop循環(huán)方法中分發(fā)消息,最后將相應(yīng)的消息對(duì)象分發(fā)到相應(yīng)的target handler類中去處理消息。
由此我們知道:一個(gè)線程Thread中只能擁有一個(gè)Looper對(duì)象,且一個(gè)Looper對(duì)象中只能擁有一個(gè)MessageQueue消息隊(duì)列,但是一個(gè)Thread線程中可以有多個(gè)Handler對(duì)象,而多個(gè)Handler對(duì)象共享同一個(gè)MessageQueue消息隊(duì)列。
備注:本文作者 經(jīng)閱讀多本Android 相關(guān)書(shū)籍, 和代碼實(shí)踐 .自己總結(jié).僅供學(xué)習(xí)參考.謝謝
總結(jié)
以上是生活随笔為你收集整理的Android 消息机制 Handler总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: php的buffer缓存区
- 下一篇: Android 中的线程及 AsyncT