【簡介】
???之前在做一個無埋點(diǎn)SDK相關(guān)開發(fā)的時候戳气,由于上報邏輯比較復(fù)雜蛛壳,故而想到了用hander隊(duì)列的形式處理事件的上報,但是SDK上線之后齐邦,發(fā)現(xiàn)出了一個句柄泄漏的bug,百思不得其解第租,后來看了Handler的底層源碼措拇,又做了一些句柄數(shù)的追蹤和分析,解決了這個問題慎宾。
????一丐吓、Handler在java層的機(jī)制
????如下圖浅悉,handler的應(yīng)用層機(jī)制很簡單,不同的線程通過handler券犁,發(fā)送message到messageQueue里面术健,對應(yīng)的Looper開啟一個死循環(huán),然后一直輪詢粘衬,如果隊(duì)列里有待處理的消息荞估,就處理消息;如果沒有消息稚新,則開始休眠勘伺,節(jié)約資源。個中細(xì)節(jié)就不在贅述褂删,如有需要飞醉,可以自行搜索相關(guān)資料。
????二屯阀、Handler的native層機(jī)制
????我們首先來看Looper.loop()的源碼
????我們發(fā)現(xiàn)缅帘,在這個死循環(huán)里,它調(diào)用了MessageQueue下的next方法难衰,那么我們再看看這個next方法干了些什么:
????關(guān)鍵方法是這個nativePollOnce():當(dāng)運(yùn)行到這里時钦无,會調(diào)用native層的方法,系統(tǒng)去輪詢一次盖袭,如果MessageQueue當(dāng)前沒有要處理的Message铃诬,則線程休眠,不占用資源苍凛,這里為什么休眠不會產(chǎn)生ANR呢趣席,我們后面會分析。
? ? 我們現(xiàn)在注意一下nextPollTimeoutMillis這個變量醇蝴,這個變量代表MessageQueue下次被喚醒的時間宣肚。我們知道,MessageQueue里Message在加入隊(duì)列的時候悠栓,會按照執(zhí)行的時間順序排列霉涨;每次消息入隊(duì)列時,MessageQueue都會盡量計(jì)算出一個精確的時間惭适,假如這個時間是計(jì)算出來是2000ms笙瑟,此時消息隊(duì)列中沒有消息需要馬上處理時,會判斷用戶是否設(shè)置了Idle Handler癞志,如果有的話往枷,則會嘗試處理mIdleHandlers中所記錄的所有Idle Handler,此時會逐個調(diào)用這些Idle Handler的queueIdle()成員函數(shù),只會會再次調(diào)用nativePollOnce()方法错洁,線程阻塞住秉宿,不占用資源。當(dāng)時間到了屯碴,會往管道流中寫入字節(jié)流描睦,喚醒線程,處理Message导而。
? ? 我們知道忱叭,安卓的底層是Linux系統(tǒng)。當(dāng)Looper休眠時今艺,用的是底層的epoll機(jī)制來完成阻塞動作韵丑,故而不會產(chǎn)生ANR。
? ? 源碼的喚醒調(diào)用如圖:
? ? 最終調(diào)用了Looper.cpp源碼的wake()方法
? ? 我們可以看到洼滚,喚醒只是往管道流里寫了一個"w"的字符流。所以喚醒機(jī)制技潘,我們可以直觀的理解為:
? ? 在Linux底層遥巴,每個線程所能操作的句柄上限是1024個,一旦超過了這個值享幽,則會報句柄泄漏的錯誤铲掐,導(dǎo)致崩潰。
? ? 所以值桩,是不是我們的上報無埋點(diǎn)的數(shù)據(jù)時摆霉,頻繁的休眠喚醒導(dǎo)致句柄數(shù)超過了上限呢?
????三奔坟、查看線程的句柄數(shù)
? ? 我們需要一個root了的手機(jī)携栋,如果手頭沒有能用的測試機(jī),可以使用模擬器咳秉。
? ? 我用了一個低版本的模擬器(5.0版本)婉支,因?yàn)楦甙姹镜哪M器也不好直接獲取root。
? ? 我們先要獲取進(jìn)程的id澜建,這個可以通過AS的Logcat查看向挖。
? ? 之后通過adb shell進(jìn)入模擬器,cd到/proc/進(jìn)程id/fd文件夾下炕舵,然后ls -al何之,就可以在Logcat中打印出當(dāng)前線程所消耗的句柄。如果你沒有root權(quán)限咽筋,fd文件夾是訪問不了的溶推。
????筆者在啟動APP后,正常使用了一下APP,接著打印出了當(dāng)前程序占用的句柄數(shù)悼潭,發(fā)現(xiàn)有800多個句柄開銷庇忌,大多數(shù)是數(shù)據(jù)庫所持有的,搞了半天原來是數(shù)據(jù)庫的問題舰褪。但是為了防止意外皆疹,筆者已經(jīng)把SDK中的所有Handler替換掉了。
? ? 四占拍、總結(jié)
? ? 在android開發(fā)中略就,我們的這些操作會消耗句柄數(shù):
? ? 1.數(shù)據(jù)庫的讀寫,不關(guān)及時關(guān)流會導(dǎo)致句柄開銷增大晃酒;
? ? 2.文件流的讀寫表牢,如果操作不好,也容易導(dǎo)致句柄消耗過大贝次;
? ? 3.Handler頻繁喚醒等崔兴。
? ? 一旦出現(xiàn)句柄泄漏的問題,核心思想就去排查流的讀寫有沒有出問題蛔翅。因?yàn)樵诎沧康讓忧们眩琇inux對于句柄的操作很多都是通過流來體現(xiàn)的,如果句柄數(shù)超過了上限山析,肯定會出問題堰燎。