句柄泄漏和Handler的底層機制

【簡介】

???之前在做一個無埋點SDK相關(guān)開發(fā)的時候,由于上報邏輯比較復(fù)雜,故而想到了用hander隊列的形式處理事件的上報,但是SDK上線之后,發(fā)現(xiàn)出了一個句柄泄漏的bug,百思不得其解,后來看了Handler的底層源碼,又做了一些句柄數(shù)的追蹤和分析,解決了這個問題。

????一、Handler在java層的機制

????如下圖,handler的應(yīng)用層機制很簡單,不同的線程通過handler,發(fā)送message到messageQueue里面,對應(yīng)的Looper開啟一個死循環(huán),然后一直輪詢,如果隊列里有待處理的消息,就處理消息;如果沒有消息,則開始休眠,節(jié)約資源。個中細節(jié)就不在贅述,如有需要,可以自行搜索相關(guān)資料。

Handler的應(yīng)用層機制


????二、Handler的native層機制

????我們首先來看Looper.loop()的源碼

Looper.loop()源碼

????我們發(fā)現(xiàn),在這個死循環(huán)里,它調(diào)用了MessageQueue下的next方法,那么我們再看看這個next方法干了些什么:

MessageQueue的next()方法

????關(guān)鍵方法是這個nativePollOnce():當運行到這里時,會調(diào)用native層的方法,系統(tǒng)去輪詢一次,如果MessageQueue當前沒有要處理的Message,則線程休眠,不占用資源,這里為什么休眠不會產(chǎn)生ANR呢,我們后面會分析。

? ? 我們現(xiàn)在注意一下nextPollTimeoutMillis這個變量,這個變量代表MessageQueue下次被喚醒的時間。我們知道,MessageQueue里Message在加入隊列的時候,會按照執(zhí)行的時間順序排列;每次消息入隊列時,MessageQueue都會盡量計算出一個精確的時間,假如這個時間是計算出來是2000ms,此時消息隊列中沒有消息需要馬上處理時,會判斷用戶是否設(shè)置了Idle Handler,如果有的話,則會嘗試處理mIdleHandlers中所記錄的所有Idle Handler,此時會逐個調(diào)用這些Idle Handler的queueIdle()成員函數(shù),只會會再次調(diào)用nativePollOnce()方法,線程阻塞住,不占用資源。當時間到了,會往管道流中寫入字節(jié)流,喚醒線程,處理Message。

? ? 我們知道,安卓的底層是Linux系統(tǒng)。當Looper休眠時,用的是底層的epoll機制來完成阻塞動作,故而不會產(chǎn)生ANR。

? ? 源碼的喚醒調(diào)用如圖:

MessageQueue.cpp的nativeWake()

? ? 最終調(diào)用了Looper.cpp源碼的wake()方法

Lopper.cpp的wake()方法

? ? 我們可以看到,喚醒只是往管道流里寫了一個"w"的字符流。所以喚醒機制,我們可以直觀的理解為:

喚醒機制

? ? 在Linux底層,每個線程所能操作的句柄上限是1024個,一旦超過了這個值,則會報句柄泄漏的錯誤,導(dǎo)致崩潰。

? ? 所以,是不是我們的上報無埋點的數(shù)據(jù)時,頻繁的休眠喚醒導(dǎo)致句柄數(shù)超過了上限呢?


????三、查看線程的句柄數(shù)

? ? 我們需要一個root了的手機,如果手頭沒有能用的測試機,可以使用模擬器。

? ? 我用了一個低版本的模擬器(5.0版本),因為高版本的模擬器也不好直接獲取root。

? ? 我們先要獲取進程的id,這個可以通過AS的Logcat查看。

? ? 之后通過adb shell進入模擬器,cd到/proc/進程id/fd文件夾下,然后ls -al,就可以在Logcat中打印出當前線程所消耗的句柄。如果你沒有root權(quán)限,fd文件夾是訪問不了的。

????筆者在啟動APP后,正常使用了一下APP,接著打印出了當前程序占用的句柄數(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)句柄泄漏的問題,核心思想就去排查流的讀寫有沒有出問題。因為在安卓底層,Linux對于句柄的操作很多都是通過流來體現(xiàn)的,如果句柄數(shù)超過了上限,肯定會出問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容