概念:
MessageQueue是一個(gè)優(yōu)先級(jí)隊(duì)列,messge中的when字段有其執(zhí)行的時(shí)間挡爵,新加入的message會(huì)與已有的消息比較時(shí)間南誊,較早的在前面。
Message是消息實(shí)體锭环,通常需要用到what和obj字段傳遞消息,內(nèi)部還有when字段用于存儲(chǔ)消息執(zhí)行的時(shí)間泊藕,target用來(lái)存儲(chǔ)發(fā)送Message的Handler對(duì)象正勒。
Looper是輪詢(xún)器。
Handler是消息發(fā)送器穴亏。
流程:
Handler通過(guò)sendMessage()將Message發(fā)送到MessageQueue中挠乳,這是存消息的過(guò)程;
Looper通過(guò)調(diào)用MessageQueue的next()方法取出Message對(duì)象讼呢,由于這是一個(gè)時(shí)間優(yōu)先級(jí)的隊(duì)列撩鹿,所以會(huì)取到隊(duì)頭的Message,然后拿當(dāng)前時(shí)間去跟它的執(zhí)行時(shí)間悦屏,也就是when參數(shù)去對(duì)比节沦,如果時(shí)間還沒(méi)到,就先不會(huì)執(zhí)行础爬;
1甫贯、一個(gè)線程有幾個(gè)Handler?
多個(gè)看蚜,通常我們開(kāi)發(fā)過(guò)程中就會(huì)new出不止一個(gè)Handler叫搁。
2、一個(gè)線程有幾個(gè)Looper失乾?如何保證常熙?
1個(gè)。
Looper的構(gòu)造是私有的碱茁,只有通過(guò)其prepare()方法構(gòu)建出來(lái)裸卫,當(dāng)調(diào)用了Looper的prepare()方法后,會(huì)調(diào)用ThreadLocal中的get()方法檢查T(mén)hreadLocalMap中是否已經(jīng)set過(guò)Looper纽竣,如果有墓贿,則會(huì)拋出異常茧泪,提示每個(gè)線程只能有一個(gè)Looper,如果沒(méi)有聋袋,則會(huì)往ThreadLocalMap中set一個(gè)new出來(lái)的Looper對(duì)象队伟。這樣可以保證ThreadLocalMap和Looper一一對(duì)應(yīng),即一個(gè)ThreadLocalMap只會(huì)對(duì)應(yīng)一個(gè)Looper幽勒。而這里的ThreadLocalMap是在Thread中的一個(gè)全局變量嗜侮,也只會(huì)有一個(gè),所以就可以保證一個(gè)Thread中只有一個(gè)Looper啥容。
3锈颗、Handler內(nèi)存泄漏的原因?為什么其他的內(nèi)部類(lèi)沒(méi)有說(shuō)過(guò)有這個(gè)問(wèn)題咪惠?
內(nèi)部類(lèi)持有外部的引用击吱。
Handler原理:由于Handler可以發(fā)送延遲消息,所以為了保證消息執(zhí)行完畢后遥昧,由同一個(gè)Handler接收到覆醇,所以發(fā)送出去的Message中會(huì)持有Handler的引用,這個(gè)引用存在Message的target字段中炭臭,是Handler所有的sendMessage()方法最后都會(huì)調(diào)用enqueueMessage()永脓,而在enqueueMessage()中會(huì)給Message的target字段賦值this。
因此Message持有Handler的引用徽缚,Handler又持有Activity的引用憨奸,所以在Message處理完之前革屠,如果Activity被銷(xiāo)毀了凿试,就會(huì)造成內(nèi)存泄漏。
解決似芝?可以使用static修飾Handler對(duì)象那婉。
4、為何主線程可以new Handler党瓮?如果想要在子線程中new Handler要做些什么準(zhǔn)備详炬?
因?yàn)樵贏ctivityThread中的main()已經(jīng)對(duì)Looper進(jìn)行了prepar()操作,所以可以直接在主線程new Handler寞奸。
如果想在子線程中new Handler呛谜,則需要先手動(dòng)調(diào)用Looper的prepare()方法初始化Looper,再調(diào)用Looper的loop()方法使Looper運(yùn)轉(zhuǎn)枪萄。
5隐岛、子線程中維護(hù)的Looper,消息隊(duì)列無(wú)消息的時(shí)候的處理方案是什么瓷翻?有什么用聚凹?
如果不處理的話割坠,會(huì)阻塞線程,處理方案是調(diào)用Looper的quitSafely()妒牙;
這個(gè)方法會(huì)調(diào)用MessageQueue的quit()方法彼哼,清空所有的Message,并調(diào)用nativeWake()方法喚醒之前被阻塞的nativePollOnce()湘今,使得方法next()方法中的for循環(huán)繼續(xù)執(zhí)行敢朱,接下來(lái)發(fā)現(xiàn)Message為null后就會(huì)結(jié)束循環(huán),Looper結(jié)束摩瞎。如此便可以釋放內(nèi)存和線程蔫饰。
6、既然可以存在多個(gè)Handler往MessageQueue中添加數(shù)據(jù)(發(fā)消息時(shí)各個(gè)Handler可能處于不同線程)愉豺,那它內(nèi)部是如何確保線程安全的篓吁?Handler的delay消息(延遲消息)時(shí)間準(zhǔn)確嗎?
添加消息的方法enqueueMessage()中有synchronize修飾蚪拦,取消息的方法next()中也有synchronize修飾杖剪。
由于上述的加鎖操作,所以時(shí)間不能保證完全準(zhǔn)確驰贷。
7盛嘿、我們使用Message時(shí)應(yīng)該如何創(chuàng)建它?
使用Message的obtain()方法創(chuàng)建括袒,直接new出來(lái)容易造成內(nèi)存抖動(dòng)次兆。
內(nèi)存抖動(dòng)是由于頻繁new對(duì)象,gc頻繁回收導(dǎo)致锹锰,而且由于可能被別的地方持有導(dǎo)致無(wú)法及時(shí)回收所以會(huì)導(dǎo)致內(nèi)存占用越來(lái)越高芥炭。
使用obtain()對(duì)內(nèi)存復(fù)用,可以避免內(nèi)存抖動(dòng)的發(fā)生恃慧。其內(nèi)部維護(hù)了一個(gè)Message池园蝠,其是一個(gè)鏈表結(jié)構(gòu),當(dāng)調(diào)用obtain()的時(shí)候會(huì)復(fù)用表頭的Message痢士,然后會(huì)指向下一個(gè)彪薛。如果表頭沒(méi)有可復(fù)用的message則會(huì)創(chuàng)建一個(gè)新的對(duì)象,這個(gè)對(duì)象池的最大長(zhǎng)度是50怠蹂。
8善延、使用Handler的postDelay后消息隊(duì)列會(huì)有什么變化?
如果此時(shí)消息隊(duì)列為空城侧,不會(huì)執(zhí)行易遣,會(huì)計(jì)算消息需要等待的時(shí)間,等待時(shí)間到了繼續(xù)執(zhí)行赞庶。
9训挡、Looper死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用卡死澳骤?
卡死就是ANR,產(chǎn)生的原因有2個(gè):
1澜薄、在5s內(nèi)沒(méi)有響應(yīng)輸入的事件(例如按鍵为肮,觸摸等),2肤京、BroadcastReceiver在10s內(nèi)沒(méi)有執(zhí)行完畢颊艳。
事實(shí)上我們所有的Activity和Service都是運(yùn)行在loop()函數(shù)中,以消息的方式存在忘分,所以在沒(méi)有消息產(chǎn)生的時(shí)候棋枕,looper會(huì)被block(阻塞),主線程會(huì)進(jìn)入休眠妒峦,一旦有輸入事件或者Looper添加消息的操作后主線程就會(huì)被喚醒重斑,從而對(duì)事件進(jìn)行響應(yīng),所以不會(huì)導(dǎo)致ANR
簡(jiǎn)單來(lái)說(shuō)looper的阻塞表明沒(méi)有事件輸入肯骇,而ANR是由于有事件沒(méi)響應(yīng)導(dǎo)致窥浪,所以looper的死循環(huán)并不會(huì)導(dǎo)致應(yīng)用卡死。
其他問(wèn)題:
1笛丙、兩個(gè)時(shí)間相等的消息怎么處理漾脂?
由于Handler發(fā)送消息時(shí),最終會(huì)調(diào)用sendMessageDelayed()胚鸯,這個(gè)方法會(huì)調(diào)用sendMessageAtTime()骨稿,在這個(gè)方法里會(huì)用系統(tǒng)時(shí)間加上傳入的delayed值,而系統(tǒng)時(shí)間是每時(shí)每刻都在變化的姜钳,所以幾乎不可能會(huì)有兩個(gè)時(shí)間相等的消息坦冠。
2、ThreadLocal是什么傲须?
ThreadLocal里存的是<key,value>的鍵值對(duì)蓝牲,有點(diǎn)像SparseArray,但是與之不同的是SparseArray內(nèi)部維護(hù)了2個(gè)數(shù)組泰讽,而ThreadLocal內(nèi)部只有一個(gè)數(shù)組,它存數(shù)據(jù)都是成對(duì)的往里存昔期,key存到index中已卸,value就存在了index+1的位置。其中key存的是ThreadLocal硼一,value存的是Looper累澡。
3、ThreadLocalMap是什么般贼?
它是ThreadLocal中的一個(gè)內(nèi)部類(lèi)愧哟,每個(gè)Thread中都會(huì)有一個(gè)全局的ThreadLocalMap對(duì)象奥吩。
4、Message使用了享元設(shè)計(jì)模式蕊梧。
5霞赫、App的啟動(dòng)流程?
在Launcher界面點(diǎn)擊應(yīng)用圖標(biāo) -》 啟動(dòng)Application -》zygote為應(yīng)用程序分配一個(gè)虛擬機(jī) -》 啟動(dòng)ActivityThread -》在ActivityThread中的main()函數(shù)對(duì)主線程的looper初始化并運(yùn)行肥矢。
*ActivityThread中存在一個(gè)main函數(shù)端衰,是應(yīng)用的入口,所以首先啟動(dòng)的是ActivityThread甘改。在