面試題總結(jié)
Handler是一個(gè)比較重要的東西,所以把網(wǎng)上發(fā)的Handler中的面試題總結(jié)了一下,這些面試題沒(méi)問(wèn)題的話(huà),Handler源碼相關(guān)的內(nèi)容就應(yīng)該沒(méi)問(wèn)題了,有空的話(huà)會(huì)再寫(xiě)一個(gè)Handler源碼篇。
[Q1:用一句話(huà)概括Handler,并簡(jiǎn)述其原理。]
Handler是Android系統(tǒng)的根本,在Android應(yīng)用被啟動(dòng)的時(shí)候,會(huì)分配一個(gè)單獨(dú)的虛擬機(jī),虛擬機(jī)會(huì)執(zhí)行ActivityThread中的main方法,在main方法中對(duì)主線(xiàn)程Looper進(jìn)行了初始化,也就是幾乎所有代碼都執(zhí)行在Handler內(nèi)部。Handler也可以作為主線(xiàn)程和子線(xiàn)程通訊的橋梁。
Handler通過(guò)sendMessage發(fā)送消息,將消息放入MessageQueue中,在MessageQueue中通過(guò)時(shí)間的維度來(lái)進(jìn)行排序,Looper通過(guò)調(diào)用loop方法不斷的從MessageQueue中獲取消息,執(zhí)行Handler的dispatchMessage,最后調(diào)用handleMessage方法。
[Q2:為什么系統(tǒng)不建議在子線(xiàn)程訪(fǎng)問(wèn)UI?(為什么不能在子線(xiàn)程更新UI?)]
在某些情況下,在子線(xiàn)程中是可以更新UI的。但是在ViewRootImpl中對(duì)UI操作進(jìn)行了checkThread,但是我們?cè)贠nCreate和onResume中可以使用子線(xiàn)程更新UI,由于我們?cè)贏ctivityThread中的performResumeActivity方法中通過(guò)addView創(chuàng)建了ViewRootImpl,這個(gè)行為是在onResume之后調(diào)用的,所以在OnCreate和onResume可以進(jìn)行更新UI。
但是我們不能在子線(xiàn)程中更新UI,因?yàn)槿绻砑恿撕臅r(shí)操作之后,一旦ViewRootImpl被創(chuàng)建將會(huì)拋出異常。一旦在子線(xiàn)程中更新UI,容易產(chǎn)生并發(fā)問(wèn)題。
[Q3:一個(gè)Thread可以有幾個(gè)Looper?幾個(gè)Handler?]
一個(gè)線(xiàn)程只能有一個(gè)Looper,可以有多個(gè)Handler,在線(xiàn)程中我們需要調(diào)用Looper.perpare,他會(huì)創(chuàng)建一個(gè)Looper并且將Looper保存在ThreadLocal中,每個(gè)線(xiàn)程都有一個(gè)LocalThreadMap,會(huì)將Looper保存在對(duì)應(yīng)線(xiàn)程中的LocalThreadMap,key為T(mén)hreadLocal,value為L(zhǎng)ooper
[Q4:可以在子線(xiàn)程直接new一個(gè)Handler嗎?那該怎么做?]
可以在子線(xiàn)程中創(chuàng)建Handler,我們需要調(diào)用Looper.perpare和Looper.loop方法。或者通過(guò)獲取主線(xiàn)程的looper來(lái)創(chuàng)建Handler
[Q5:Message可以如何創(chuàng)建?哪種效果更好,為什么?]
Message.obtain來(lái)創(chuàng)建Message。這樣會(huì)復(fù)用之前的Message的內(nèi)存,不會(huì)頻繁的創(chuàng)建對(duì)象,導(dǎo)致內(nèi)存抖動(dòng)。
[Q6:主線(xiàn)程中Looper的輪詢(xún)死循環(huán)為何沒(méi)有阻塞主線(xiàn)程?]
Looper輪詢(xún)是死循環(huán),但是當(dāng)沒(méi)有消息的時(shí)候他會(huì)block,ANR是當(dāng)我們處理點(diǎn)擊事件的時(shí)候5s內(nèi)沒(méi)有響應(yīng),我們?cè)谔幚睃c(diǎn)擊事件的時(shí)候也是用的Handler,所以一定會(huì)有消息執(zhí)行,并且ANR也會(huì)發(fā)送Handler消息,所以不會(huì)阻塞主線(xiàn)程。
[Q7:使用Hanlder的postDealy()后消息隊(duì)列會(huì)發(fā)生什么變化?]
Handler發(fā)送消息到消息隊(duì)列,消息隊(duì)列是一個(gè)時(shí)間優(yōu)先級(jí)隊(duì)列,內(nèi)部是一個(gè)單向鏈表。發(fā)動(dòng)postDelay之后會(huì)將該消息進(jìn)行時(shí)間排序存放到消息隊(duì)列中
[Q8:點(diǎn)擊頁(yè)面上的按鈕后更新TextView的內(nèi)容,談?wù)勀愕睦斫猓浚ò⒗锩嬖囶})]
點(diǎn)擊按鈕的時(shí)候會(huì)發(fā)送消息到Handler,但是為了保證優(yōu)先執(zhí)行,會(huì)加一個(gè)標(biāo)記異步,同時(shí)會(huì)發(fā)送一個(gè)target為null的消息,這樣在使用消息隊(duì)列的next獲取消息的時(shí)候,如果發(fā)現(xiàn)消息的target為null,那么會(huì)遍歷消息隊(duì)列將有異步標(biāo)記的消息獲取出來(lái)優(yōu)先執(zhí)行,執(zhí)行完之后會(huì)將target為null的消息移除。(同步屏障)
[Q9:生產(chǎn)者-消費(fèi)者設(shè)計(jì)模式懂不?]
舉個(gè)例子,面包店廚師不斷在制作面包,客人來(lái)了之后就購(gòu)買(mǎi)面包,這就是一個(gè)典型的生產(chǎn)者消費(fèi)者設(shè)計(jì)模式。但是需要注意的是如果消費(fèi)者消費(fèi)能力大于生產(chǎn)者,或者生產(chǎn)者生產(chǎn)能力大于消費(fèi)者,需要一個(gè)限制,在java里有一個(gè)blockingQueue。當(dāng)目前容器內(nèi)沒(méi)有東西的時(shí)候,消費(fèi)者來(lái)消費(fèi)的時(shí)候會(huì)被阻塞,當(dāng)容器滿(mǎn)了的時(shí)候也會(huì)被阻塞。Handler.sendMessage相當(dāng)于一個(gè)生產(chǎn)者,MessageQueue相當(dāng)于容器,Looper相當(dāng)于消費(fèi)者。
[Q10:Handler是如何完成子線(xiàn)程和主線(xiàn)程通信的?]
在主線(xiàn)程中創(chuàng)建Handler,在子線(xiàn)程中發(fā)送消息,放入到MessageQueue中,通過(guò)Looper.loop取出消息進(jìn)行執(zhí)行handleMessage,由于looper我們是在主線(xiàn)程初始化的,在初始化looper的時(shí)候會(huì)創(chuàng)建消息隊(duì)列,所以消息是在主線(xiàn)程被執(zhí)行的。
[Q11:關(guān)于ThreadLocal,談?wù)勀愕睦斫猓縘
ThreadLocal類(lèi)似于每個(gè)線(xiàn)程有一個(gè)單獨(dú)的內(nèi)存空間,不共享,ThreadLocal在set的時(shí)候會(huì)將數(shù)據(jù)存入對(duì)應(yīng)線(xiàn)程的ThreadLocalMap中,key=ThreadLocal,value=值
[Q12:享元設(shè)計(jì)模式有用到嗎?]
享元設(shè)計(jì)模式就是重復(fù)利用內(nèi)存空間,減少對(duì)象的創(chuàng)建,Message中使用到了享元設(shè)計(jì)模式。內(nèi)部維護(hù)了一個(gè)鏈表,并且最大長(zhǎng)度是50,當(dāng)消息處理完之后會(huì)將消息內(nèi)的屬性設(shè)置為空,并且插入到鏈表的頭部,使用obtain創(chuàng)建的Message會(huì)從頭部獲取空的Message
[Q13: Handler內(nèi)存泄漏問(wèn)題及解決方案 ]
內(nèi)部類(lèi)持有外部類(lèi)的引用導(dǎo)致了內(nèi)存泄漏,如果Activity退出的時(shí)候,MessageQueue中還有一個(gè)Message沒(méi)有執(zhí)行,這個(gè)Message持有了Handler的引用,而Handler持有了Activity的引用,導(dǎo)致Activity無(wú)法被回收,導(dǎo)致內(nèi)存泄漏。使用static關(guān)鍵字修飾,在onDestory的時(shí)候?qū)⑾⑶宄?/p>
[Q14: Handler異步消息處理(HandlerThread)]
內(nèi)部使用了Handler+Thread,并且處理了getLooper的并發(fā)問(wèn)題。如果獲取Looper的時(shí)候發(fā)現(xiàn)Looper還沒(méi)創(chuàng)建,則wait,等待looper創(chuàng)建了之后在notify
[Q15: 子線(xiàn)程中維護(hù)的Looper,消息隊(duì)列無(wú)消息的時(shí)候處理方案是什么?有什么用? ]
子線(xiàn)程中創(chuàng)建了Looper,當(dāng)沒(méi)有消息的時(shí)候子線(xiàn)程將會(huì)被block,無(wú)法被回收,所以我們需要手動(dòng)調(diào)用quit方法將消息刪除并且喚醒looper,然后next方法返回null退出loop
[Q16: 既然可以存在多個(gè)Handler往MessageQueue中添加數(shù)據(jù)(發(fā)消息是各個(gè)handler可能處于不同線(xiàn)程),那他內(nèi)部是如何確保線(xiàn)程安全的? ]
在添加數(shù)據(jù)和執(zhí)行next的時(shí)候都加了this鎖,這樣可以保證添加的位置是正確的,獲取的也會(huì)是最前面的。
[Q17: 關(guān)于IntentService,談?wù)勀愕睦斫?]
HandlerThread+Service實(shí)現(xiàn),可以實(shí)現(xiàn)Service在子線(xiàn)程中執(zhí)行耗時(shí)操作,并且執(zhí)行完耗時(shí)操作時(shí)候會(huì)將自己stop。
[Q18: Glide是如何維護(hù)生命周期的? ]
一般想問(wèn)的應(yīng)該都是這里
@NonNull
private RequestManagerFragment getRequestManagerFragment(
@NonNull final android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
if (isParentVisible) {
current.getGlideLifecycle().onStart();
}
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}
1.為什么會(huì)判斷兩次null,再多次調(diào)用with的時(shí)候,commitAllowingStateLoss會(huì)被執(zhí)行兩次,所以我們需要使用一個(gè)map集合來(lái)判斷,如果map中已經(jīng)有了證明已經(jīng)添加過(guò)了2.handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();我們需要將map里面的記錄刪除。