Android-Stability【Fdleak】: Android Fd泄漏問(wèn)題分析

Android-Stability【Fdleak】: Android Fd泄漏問(wèn)題分析

1. Fd leak問(wèn)題概述

1.1 fd limit

如圖可以查看某個(gè)進(jìn)成的fd信息:


在這里插入圖片描述

系統(tǒng)對(duì)進(jìn)程使用系統(tǒng)資源有相關(guān)限制cat /proc/pid/limits 即可看到:


在這里插入圖片描述

soft Limit 軟限制 : 系統(tǒng)資源的使用的上限值
hard Limit 硬限制 :但不能超出hard limits值

如上看到系統(tǒng)對(duì)3207進(jìn)程的的資源限制情況,其中對(duì)于FD資源限制為max open file一行,上限制是1024,即使用的fd如果已滿1024了,就會(huì)沒(méi)有fd資源提供,再需要fd的時(shí)候就會(huì)出現(xiàn)異常,即fd泄漏問(wèn)題。

1.2 更改limit方法

進(jìn)程可以通過(guò)如下方法獲取fd限制信息,也可以通過(guò)setrlimit方法對(duì)本進(jìn)程fd限制進(jìn)行調(diào)整,但最大不能超過(guò)hardlimit限制:
a. Native 方法:
getrlimit(RLIMIT_NOFILE, &rlim);
setrlimit(RLIMIT_NOFILE, &rlim);
b. Java 方法:
android.system.Os.getrlimit(OsConstants.RLIMIT_NOFILE);
java方法未實(shí)現(xiàn)setrlimit方法,如果是手機(jī)廠商的話可以嘗試添加一個(gè)setrlimit方法的java接口。
c. 終端:
這里要注意的是只有security組的成員可使更改rlimit永久生效,普通用戶的更改的進(jìn)程退出侯失效,重新進(jìn)入該進(jìn)程后將恢復(fù)使用默認(rèn)的1024.

1.3 Fd泄漏問(wèn)題

由上可以看出,fd資源不足時(shí),再需要fd的線程可能就會(huì)產(chǎn)生異常,而需要fd資源的場(chǎng)景很多,即在fd資源不足時(shí),這些需要fd資源的很多場(chǎng)景,代碼都有可能出現(xiàn)異常從而進(jìn)程crash或功能異常,因此這些場(chǎng)景可能只是躺槍導(dǎo)致進(jìn)程FC,真正罪魁禍?zhǔn)讘?yīng)該是大量占用fd資源的地方。
因此,F(xiàn)d泄漏的原因一般都有如下特點(diǎn):
1. 同一個(gè)問(wèn)題可能出現(xiàn)不同堆棧
2. 比較隱晦

2. 需要open Fd的場(chǎng)景

同一個(gè)問(wèn)題可能有不同的堆棧,但都一般都是需要fd時(shí)的堆棧,因此,如果遇到類似需要fd的堆棧都可以懷疑是否是發(fā)生了fd泄漏了,當(dāng)然日志中伴隨者大量“Too many open files”的字眼。
如下Java層的Error Msg均有fd泄漏的嫌疑:
"Too many open files"
"Could not allocate JNI Env"
"Could not allocate dup blob fd"
"Could not read input channel file descriptors from parcel"
"pthread_create * "
"InputChannel is not initialized"
"Could not open input channel pair"
...
當(dāng)然還有許多其他類型的Error Msg,后續(xù)補(bǔ)充。
大致看下比較常見(jiàn)需要fd的場(chǎng)景:

2.1 Resource相關(guān)

image.png

使用BufferedReader去讀取文件內(nèi)容是常見(jiàn)的方式,bufferedReadernew出來(lái)需要及時(shí)關(guān)閉,注意如上br.close在try里面,如過(guò)close之前發(fā)生異常未走到close則這里便有fd泄漏的風(fēng)險(xiǎn),因此br.close應(yīng)當(dāng)在finally塊中操作,保證一定能close掉。
詳細(xì)流程(雖然基于M的code來(lái)看,但基本如下最終調(diào)用系統(tǒng)函數(shù)open,會(huì)生成一個(gè)int的fd值):


在這里插入圖片描述

簡(jiǎn)化過(guò)程:


image.png

2.2 HandlerThread

HandlerThread是自帶looper的thread,使用時(shí)可以使用該looper構(gòu)造handler來(lái)使用handler的功能,一個(gè)HandlerThread對(duì)應(yīng)一個(gè)Looper成員變量


image.png

而Looper對(duì)象初始化時(shí)Looper.prepare() 需要fd資源,而且是一個(gè)HandlerThread起來(lái)會(huì)消耗一對(duì)fd(eventFd和epollFd),這兩個(gè)fd的目的也很明確,就是用來(lái)實(shí)現(xiàn)線程間通信的。

詳細(xì)過(guò)程如下:


image.png

簡(jiǎn)化過(guò)成如下:


image.png

如過(guò)是HandlerThread異常導(dǎo)致的fd泄漏,我們?cè)谠撨M(jìn)程的Fd信息中,下面是解問(wèn)提時(shí)遇到的systemui的fd泄漏案例:
HandlerThread開(kāi)啟過(guò)多時(shí)的fd信息,ls -la proc/{pid}/fd/ 獲?。?/p>

fd 775: anon_inode:[eventfd]
fd 776: anon_inode:[eventpoll]
fd 777: anon_inode:[eventpoll]
fd 778: anon_inode:[eventfd]
fd 779: anon_inode:[eventfd]
fd 780: anon_inode:[eventpoll]
fd 781: anon_inode:[eventpoll]
fd 782: anon_inode:[eventpoll]
...
fd 808: anon_inode:[eventpoll]
fd 809: anon_inode:[eventfd]
fd 810: /dev/ashmem
fd 811: anon_inode:[eventpoll]
fd 812: anon_inode:[eventfd]
fd 813: anon_inode:dmabuf
fd 815: anon_inode:[eventfd]
fd 816: anon_inode:[eventpoll]
fd 817: anon_inode:sync_file
...
fd 832: anon_inode:[eventpoll]
fd 833: anon_inode:[eventfd]
fd 834: anon_inode:[eventpoll]
fd 835: anon_inode:[eventpoll]
fd 836: anon_inode:[eventfd]
fd 837: anon_inode:[eventpoll]
fd 838: /dev/ashmem
fd 839: anon_inode:[eventpoll]
fd 840: anon_inode:[eventfd]
fd 841: anon_inode:[eventfd]
...
fd 860: anon_inode:[eventpoll]
fd 861: anon_inode:[eventfd]
fd 862: anon_inode:[eventfd]
fd 863: anon_inode:[eventpoll]
fd 864: anon_inode:[eventfd]
fd 865: anon_inode:[eventfd]
fd 866: anon_inode:[eventpoll]
fd 867: anon_inode:[eventpoll]
fd 868: anon_inode:[eventfd]
fd 869: anon_inode:[eventpoll]

可以看到打開(kāi)的eventfd,eventpoll類型問(wèn)提異常的多。
再看下該進(jìn)程的進(jìn)程trace或ps信息中的線程情況:

pid: 11019, tid: 7441, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 7532, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 7534, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 7632, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 7806, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 7856, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8116, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8304, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8388, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8467, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8565, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8697, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8760, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8853, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8940, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9107, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9215, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9311, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9331, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9596, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9930, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10036, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10069, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10127, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10231, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10331, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10449, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10568, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10674, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10772, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10822, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10920, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 11113, name: async_sensor >>> com.android.systemui <<<

可以看到async_sensor這個(gè)線程異常的多,很明顯是該線程異常起的過(guò)多導(dǎo)致了fd泄漏,可以繼續(xù)排查該線程的異常起這么多的原因,從而解決該fd泄漏問(wèn)提。

2.3 Java Thread Start

Java在起線程的時(shí)候也會(huì)需要開(kāi)Fd資源,如果線程生命周期操作不當(dāng),當(dāng)起的線程過(guò)多時(shí)也會(huì)導(dǎo)致Fd泄漏的問(wèn)提,當(dāng)然這里也是會(huì)經(jīng)常躺槍。
可以先閱讀該篇文章進(jìn)行詳細(xì)的學(xué)習(xí):http://www.itdecent.cn/p/e574f0ffdb42
詳細(xì)過(guò)成:

image.png

簡(jiǎn)化過(guò)成:

在這里插入圖片描述

對(duì)于1的地方,如果Fd資源不足了,會(huì)報(bào)出類似如下調(diào)用棧錯(cuò)誤,應(yīng)該是已經(jīng)有Fd泄漏了導(dǎo)致再起線程時(shí)這里創(chuàng)建JNIENV需要打開(kāi)Fd失敗,當(dāng)然這里有可能只是躺槍:

   java.lang.OutOfMemoryError: Could not allocate JNI Env
   at java.lang.Thread.nativeCreate(Native Method)
   at java.lang.Thread.start(Thread.java:729)
   at com.android.server.wifi.WifiNative.startHal(WifiNative.java:1639)
   at com.android.server.wifi.WifiStateMachine.setupDriverForSoftAp(WifiStateMachine.java:3970)
   at com.android.server.wifi.WifiStateMachine.-wrap9(WifiStateMachine.java)
   at com.android.server.wifi.WifiStateMachine$InitialState.processMessage(WifiStateMachine.java:4480)
   at com.android.internal.util.StateMachine$SmHandler.processMsg(StateMachine.java:980)
   at com.android.internal.util.StateMachine$SmHandler.handleMessage(StateMachine.java:799)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:163)
   at android.os.HandlerThread.run(HandlerThread.java:61)

對(duì)于2的地方,如果內(nèi)存資源不足了,或者沒(méi)有連續(xù)虛擬地址可用會(huì)拋出如下類似錯(cuò)誤調(diào)用棧,這個(gè)時(shí)候應(yīng)該去調(diào)查為何虛擬地址不足,這也是OOM的問(wèn)提了:

java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:733)
at com.tencent.mm.sdk.f.b$a.start(SourceFile:61)
at com.tencent.mm.am.a.bU(SourceFile:60)
at com.tencent.mm.ui.MMAppMgr$8.tC(SourceFile:315)
at com.tencent.mm.sdk.platformtools.am.handleMessage(SourceFile:69)
at com.tencent.mm.sdk.platformtools.aj.handleMessage(SourceFile:173)
at com.tencent.mm.sdk.platformtools.aj.dispatchMessage(SourceFile:128)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:6701)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:246)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)

復(fù)現(xiàn)該類問(wèn)提之需要在fd不足時(shí)不斷起java線程,或內(nèi)存緊張時(shí)起線程就比較容易復(fù)現(xiàn)類似的調(diào)用棧。
所以該類調(diào)用棧容易是躺槍,fd leak問(wèn)提需要調(diào)查誰(shuí)消耗的fd過(guò)多,oom問(wèn)提需要找到是誰(shuí)導(dǎo)致內(nèi)存不足。

2.4 InputChannel

fd leak中一種比較常見(jiàn)的錯(cuò)誤調(diào)用桟:
log1:

06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: FATAL EXCEPTION: main
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: Process: com.miui.weather2, PID: 20556
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: java.lang.RuntimeException: Could not read input channel file descriptors from parcel.
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at android.view.InputChannel.nativeReadFromParcel(Native Method)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at android.view.InputChannel.readFromParcel(InputChannel.java:148)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at android.view.InputChannel$1.createFromParcel(InputChannel.java:39)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at android.view.InputChannel$1.createFromParcel(InputChannel.java:37)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at com.android.internal.view.InputBindResult.<init>(InputBindResult.java:68)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at com.android.internal.view.InputBindResult$1.createFromParcel(InputBindResult.java:112)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at com.android.internal.view.InputBindResult$1.createFromParcel(InputBindResult.java:110)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at com.android.internal.view.IInputMethodManager$Stub$Proxy.startInputOrWindowGainedFocus(IInputMethodManager.java:723)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at android.view.inputmethod.InputMethodManager.startInputInner(InputMethodManager.java:1295)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at android.view.inputmethod.InputMethodManager.onPostWindowFocus(InputMethodManager.java:1543)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:4069)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:106)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at android.os.Looper.loop(Looper.java:171)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:6642)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:518)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

log2:

07-22 11:06:05.644  1526  1645 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: android.ui
07-22 11:06:05.644  1526  1645 E AndroidRuntime: java.lang.RuntimeException: Could not open input channel pair.  status=-24
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at android.view.InputChannel.nativeOpenInputChannelPair(Native Method)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at android.view.InputChannel.openInputChannelPair(InputChannel.java:94)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at com.android.server.wm.WindowState.openInputChannel(WindowState.java:2011)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at com.android.server.wm.WindowManagerService.addWindow(WindowManagerService.java:1406)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at com.android.server.wm.Session.addToDisplay(Session.java:197)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at android.view.ViewRootImpl.setView(ViewRootImpl.java:750)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at android.app.Dialog.show(Dialog.java:330)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at com.android.server.am.AppErrors.handleShowAppErrorUi(AppErrors.java:755)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at com.android.server.am.ActivityManagerService$UiHandler.handleMessage(ActivityManagerService.java:1849)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at android.os.Handler.dispatchMessage(Handler.java:105)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at android.os.Looper.loop(Looper.java:171)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at android.os.HandlerThread.run(HandlerThread.java:65)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at com.android.server.ServiceThread.run(ServiceThread.java:46)
07-22 11:06:05.644  1526  1645 E AndroidRuntime:   at com.android.server.UiThread.run(UiThread.java:42)

當(dāng)然類似inputchannel的異常調(diào)用棧不止如上兩個(gè),還有
這里inputchannel也是需要fd資源,AddWindow的時(shí)候需要初始化inputchannel去和InputManagerService進(jìn)行跨進(jìn)程通信來(lái)監(jiān)控Input事件,本質(zhì)上是初始化了一對(duì)socket文件進(jìn)行通信:
詳細(xì)過(guò)程:


image.png

可以通過(guò)該博客進(jìn)行學(xué)習(xí)InpuntChannnel相關(guān)的知識(shí):https://blog.csdn.net/hongzg1982/article/details/54812359

簡(jiǎn)化過(guò)程:


image.png

如上知道addWindow或創(chuàng)建出一對(duì)socket文件用于構(gòu)造inputchannel,從而實(shí)現(xiàn)app從InputDispatcher那里得到input事件,因此,如果此時(shí)Fd資源緊張,則很容易在addWindow時(shí)發(fā)生異常。
不過(guò)如過(guò)異常log顯示inputchannel相關(guān)的居多的話也有必要此時(shí)window存在過(guò)多,或異常添加window為銷(xiāo)毀導(dǎo)致socket增加而fd泄漏。
因此對(duì)于該類的異常,可以看看當(dāng)前的window信息,adb shell dumpsys window或得,即可以比較明確的看出是哪個(gè)window有異常,然后找原因解決。

類似的寫(xiě)個(gè)該類型demo試下
寫(xiě)一個(gè)demo,點(diǎn)擊按鈕讓其不斷的彈AlertDialog:
結(jié)果出現(xiàn)異常:

10-28 20:49:07.547 14246 14246 E AndroidRuntime: FATAL EXCEPTION: main
10-28 20:49:07.547 14246 14246 E AndroidRuntime: Process: com.example.chengang.fdtest, PID: 14246
10-28 20:49:07.547 14246 14246 E AndroidRuntime: java.lang.RuntimeException: Could not read input channel file descriptors from parcel.
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.InputChannel.nativeReadFromParcel(Native Method)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.InputChannel.readFromParcel(InputChannel.java:148)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:759)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.ViewRootImpl.setView(ViewRootImpl.java:669)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:319)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.app.Dialog.show(Dialog.java:325)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.app.AlertDialog$Builder.show(AlertDialog.java:1112)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at com.example.chengang.fdtest.MainActivity.showNormalDialog(MainActivity.java:70)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at com.example.chengang.fdtest.MainActivity.access$000(MainActivity.java:13)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at com.example.chengang.fdtest.MainActivity$1.onClick(MainActivity.java:31)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.View.performClick(View.java:5275)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.View$PerformClick.run(View.java:21559)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:815)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:104)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.os.Looper.loop(Looper.java:207)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:5845)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:768)


10-28 20:49:07.849 9259 9286 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: android.ui
10-28 20:49:07.849 9259 9286 E AndroidRuntime: java.lang.RuntimeException: Failed to initialize display event receiver. status=-2147483648
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.DisplayEventReceiver.nativeInit(Native Method)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.DisplayEventReceiver.<init>(DisplayEventReceiver.java:71)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer$FrameDisplayEventReceiver.<init>(Choreographer.java:824)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer.<init>(Choreographer.java:219)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer.<init>(Choreographer.java:79)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer$1.initialValue(Choreographer.java:116)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer$1.initialValue(Choreographer.java:109)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at java.lang.ThreadLocal$Values.getAfterMiss(ThreadLocal.java:430)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at java.lang.ThreadLocal.get(ThreadLocal.java:65)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer.getInstance(Choreographer.java:249)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.ViewRootImpl.<init>(ViewRootImpl.java:501)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:306)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.app.Dialog.show(Dialog.java:325)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at com.android.server.am.ActivityManagerService$UiHandler.handleMessage(ActivityManagerService.java:1694)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:111)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.os.Looper.loop(Looper.java:207)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.os.HandlerThread.run(HandlerThread.java:61)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at com.android.server.ServiceThread.run(ServiceThread.java:46)

看到不僅demo app crash了,而且system_server也出現(xiàn)了異常crash,手機(jī)重啟了,可怕?。?!
足見(jiàn)fd泄漏問(wèn)題的嚴(yán)重性,也了解到app異常也會(huì)影響到system_server的穩(wěn)定性。

為了不讓系統(tǒng)重啟,簡(jiǎn)單的看log,下面日日志是只彈50個(gè)dialog時(shí)抓取到的信息(其實(shí)也可以復(fù)寫(xiě)UnCatchExceptionHandler,在異常退出前抓取所需的fd,hprof,dumpsys window信息):
看下該異常時(shí)的fd信息:

socket 文件類型變多:
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 100 -> socket:[275165]
...
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 112 -> socket:[275177]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 113 -> socket:[275179]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 114 -> socket:[275181]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 115 -> socket:[275183]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 13 -> socket:[268102]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 20 -> socket:[244415]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 38 -> socket:[264306]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 4 -> socket:[159952]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 44 -> socket:[258994]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 46 -> socket:[262529]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 49 -> socket:[249721]
...
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 86 -> socket:[275153]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 87 -> socket:[275155]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 88 -> socket:[275157]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 89 -> socket:[270815]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 90 -> socket:[266061]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 91 -> socket:[270817]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 92 -> socket:[275159]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 93 -> socket:[275161]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 94 -> socket:[266063]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 95 -> socket:[270819]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 96 -> socket:[272449] 
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 97 -> socket:[266065]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 98 -> socket:[272451]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 99 -> socket...

ashmem類型也變多,可能跟surface創(chuàng)建surfaceflinger需要通過(guò)ashmem類型文件傳遞顯示數(shù)據(jù)導(dǎo)致(暫未證實(shí),后續(xù)調(diào)查)
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 520 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 521 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 522 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 523 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 525 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 526 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 527 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 528 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 529 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 53 -> /dev/ion
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 530 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 531 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 532 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 533 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 534 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 535 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 536 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 537 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 538 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 539 -> anon_inode:dmabuf
lr-x------ u0_a158 u0_a158 2018-10-28 21:04 54 -> /proc/ged
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 540 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 541 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 542 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 543 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 544 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 545 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 546 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 547 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 548 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 549 -> /dev/ashmem

可明顯看到socket類型的文件變多,正是inputchannel增多導(dǎo)致,另外值得注意的是ashmem,dmabuf也明顯增多(后續(xù)調(diào)查原因);
看下該case的hprof對(duì)比:
彈dialog之前:
[圖片上傳失敗...(image-a2582c-1541509372236)]

彈50個(gè)dialog之后:

image.png

明顯看出inputchannel增多。
看下該case下window的情況:

dumpsys window:
WINDOW MANAGER TOKENS (dumpsys window tokens)
WINDOW MANAGER WINDOWS (dumpsys window windows)
Window #56 Window{db0f6a6 u0 AOD}:
Window #55 Window{a450d9c u0 StatusBar}:
Window #54 Window{5261571 u0 KeyguardScrim}:
Window #53 Window{7eadf3a u0 AssistPreviewPanel}:
Window #52 Window{b58497e u0 DockedStackDivider}:
Window #51 Window{3aab4ea u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #50 Window{de9258c u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #49 Window{bce8de u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #48 Window{5843360 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #47 Window{7a80592 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #46 Window{6744bf4 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #45 Window{feaff06 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #44 Window{f5a4348 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #43 Window{b3d893a u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #42 Window{b1ad5c u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #41 Window{f84182e u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #40 Window{a4de30 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #39 Window{983dfe2 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #38 Window{9a0e9c4 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #37 Window{c56d456 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #36 Window{7a9a418 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #35 Window{19fa98a u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #34 Window{86da12c u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #33 Window{e7dd37e u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #32 Window{21a3500 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #31 Window{1418632 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #30 Window{4ef7394 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #29 Window{bdfb5a6 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #28 Window{b9430e8 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #27 Window{f2615da u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #26 Window{e2a00fc u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #25 Window{aaf1ace u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #24 Window{c2137d0 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #23 Window{595f882 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #22 Window{cce964 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #21 Window{3eaa2f6 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #20 Window{6b6e9b8 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #19 Window{ce5ce2a u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #18 Window{db3cccc u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #17 Window{5dcee1e u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #16 Window{7b6e6a0 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #15 Window{5f636d2 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #14 Window{664b34 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #13 Window{69c9c46 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #12 Window{36ece88 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #11 Window{4b3d27a u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #10 Window{598049c u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #9 Window{68c4d6e u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #8 Window{4984170 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #7 Window{a974122 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #6 Window{1a89904 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #5 Window{2daa196 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #4 Window{ad8df58 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #3 Window{12522ca u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #2 Window{ff6eac7 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #1 Window{2624127 u0 com.android.systemui/com.android.systemui.recents.RecentsActivity}:
Window #0 Window{df85d5b u0 com.android.systemui.ImageWallpaper}:

這一看window信息就很明顯了com.example.chengang.fdtest.MainActivity有異常addWindow,
如此就可以確定異常code深入分析了。

2.5 Sqlite Cursor

類似的案例在該博客中記錄的比較詳細(xì):https://blog.csdn.net/jk198310/article/details/43765263
意思是遇到
"E CursorWindow: Could not allocate CursorWindow "
"Can’t open DB due to too many open files "
等錯(cuò)誤Msg時(shí)可以懷疑可能有Fd 泄漏,但肯定不全是。
這里只看下cursor查詢數(shù)據(jù)時(shí)也是需要打開(kāi)fd的,值得注意的是CursorWindow默認(rèn)大小是2MB,如過(guò)使用不當(dāng)可能不僅導(dǎo)致fd泄漏風(fēng)險(xiǎn),還有內(nèi)存泄漏風(fēng)險(xiǎn)。

通過(guò)Uri查詢數(shù)據(jù)庫(kù)所得到的數(shù)據(jù)集,保存在native層的CursorWindow中。CursorWindow的實(shí)質(zhì)是共享內(nèi)存的抽象,以實(shí)現(xiàn)跨進(jìn)程數(shù)據(jù)共享。共享內(nèi)存所採(cǎi)用的實(shí)現(xiàn)方式是文件映射。
在ContentProvider端透過(guò)SQLiteDatabase的封裝查詢到的數(shù)據(jù)集保存在CursorWindow所指向的共享內(nèi)存中。然后通過(guò)Binder把這片共享內(nèi)存?zhèn)鬟f到ContentResolver端,即查詢端。
這樣客戶就能夠通過(guò)Cursor來(lái)訪問(wèn)這塊共享內(nèi)存中的數(shù)據(jù)集了。

詳細(xì)打開(kāi)fd過(guò)程:


image.png

2.6 Bitmap IPC

BitMap在IPC傳遞時(shí)需要打開(kāi)fd進(jìn)行傳遞,比如如下情況:
一個(gè)應(yīng)用使用包含一個(gè)bitmap的RemoteView去更新通知欄通知,更新一個(gè)通知實(shí)際上涉及到3個(gè)進(jìn)程,app將通知IPC給system_server,system_server將通知轉(zhuǎn)給注冊(cè)收通知的進(jìn)程,比如systemui 通知欄。
而如果通知中還有Bitmap,bitmap在進(jìn)行IPC時(shí)實(shí)際上是通過(guò)傳遞指向bitmap所在的匿名共享內(nèi)存的fd進(jìn)行傳遞的,即大致如下:


image.png

詳細(xì)打開(kāi)fd的過(guò)程:


image.png

IPC時(shí)傳遞該Fd即可而不是傳遞整個(gè)Bitmap對(duì)象。
即大致如下:
a. app 中得bitmap存到ashmem中,IPC給system_Server時(shí)將指向該ashmem文件的fd writetoparcel成parcel對(duì)象傳遞給system_server
b. system_server得到parcel對(duì)象再createFromParcel由該parcel中的fd得到bitmap所在的ashmem,即system_server中已IPC得到了app 傳來(lái)的bitmap對(duì)象。
c. system_server將bitmap傳地給注冊(cè)監(jiān)聽(tīng)的進(jìn)程比如systemui通知欄使用同樣的方式,開(kāi)辟ashmem存儲(chǔ)文件保存bitmap,使用封裝指向該ashmem的fd的parcel對(duì)象傳地給有關(guān)進(jìn)程。
d. systemui同樣使用createFromParcel獲取到傳遞過(guò)來(lái)的fd從而等到該fd指向的ashmem,最后構(gòu)造出最終的bitmap對(duì)象。


在這里插入圖片描述

由以上可知,如過(guò)app斷更新帶bitmap的通知太過(guò)頻繁,可能會(huì)不僅導(dǎo)致本進(jìn)程fd泄漏,還有可能影響到其他進(jìn)程,甚至影響到system_Server導(dǎo)致重啟,畢竟system_Server作為中轉(zhuǎn)需要更多的fd資源。

類似case,log:

10-17 12:13:02.007 2096 2096 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main
10-17 12:13:02.007 2096 2096 E AndroidRuntime: java.lang.RuntimeException: Could not copy bitmap to parcel blob.
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.graphics.Bitmap.nativeWriteToParcel(Native Method)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.graphics.Bitmap.writeToParcel(Bitmap.java:1553)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews$BitmapCache.writeBitmapsToParcel(RemoteViews.java:984)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews.writeToParcel(RemoteViews.java:2854)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews.clone(RemoteViews.java:1903)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.app.Notification.cloneInto(Notification.java:1521)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.app.Notification.clone(Notification.java:1495)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.service.notification.StatusBarNotification.clone(StatusBarNotification.java:161)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.notification.NotificationManagerService$NotificationListeners.notifyPostedLocked(NotificationManagerService.java:3398)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.notification.NotificationManagerService$8.run(NotificationManagerService.java:2228)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:742)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:95)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Looper.loop(Looper.java:157)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.SystemServer.run(SystemServer.java:302)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.SystemServer.main(SystemServer.java:176)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:746)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)

看下該case的fd信息:

09-11 14:13:34.144 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/396 -----> /dev/ashmem
09-11 14:13:34.144 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/397 -----> /dev/ashmem
09-11 14:13:34.144 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/398 -----> /dev/ashmem
09-11 14:13:34.144 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/399 -----> /dev/ashmem
...
09-11 14:13:34.145 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/412 -----> /dev/ashmem
09-11 14:13:34.146 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/413 -----> /dev/ashmem
09-11 14:13:34.146 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/414 -----> socket:[9368579]
09-11 14:13:34.146 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/415 -----> /dev/ashmem
...
09-11 14:13:34.157 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/524 -----> /dev/ashmem
09-11 14:13:34.157 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/526 -----> /dev/ashmem
09-11 14:13:34.157 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/527 -----> /dev/ashmem
09-11 14:13:34.158 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/529 -----> /dev/ashmem
...
09-11 14:13:34.161 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/552 -----> /dev/ashmem
09-11 14:13:34.161 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/553 -----> /dev/ashmem
09-11 14:13:34.161 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/555 -----> /dev/ashmem

fd信息中ashmem信息明顯很多,單通常只根據(jù)該異常調(diào)用棧,和ashmem較多的fd信息還不足以斷定就是包含bitmap的通知彈的過(guò)多導(dǎo)致,這時(shí)需要查看logcat,需要看下此時(shí)通知是否真得事很多,比如該case的logcat中搜notification相關(guān)的日志出現(xiàn)大量更新notification的信息,且很多和notification相關(guān)的異常,即可以朝這個(gè)方向去找是誰(shuí)發(fā)的notification有大量的bitmap傳遞。
一般這樣的app如閱讀類app,計(jì)步類app會(huì)頻繁更新通知欄的應(yīng)用嫌疑比較大。
關(guān)于bitmap的remoteview更新notification的問(wèn)題事實(shí)上是aosp的原生bug,具體不在此討論。

當(dāng)然,不僅bitmap,類似的需要使用到fd進(jìn)行IPC傳遞的對(duì)象都有可能異常傳地而導(dǎo)致fd泄漏問(wèn)題,或躺槍,==。

3. Fd泄漏問(wèn)題分析需要什么信息

3.1 logcat 查看異常棧情況,什么信息在頻繁的打印,涉及到fd open的日志頻繁打印是最直接懷疑的地方。

3.2 fdinfo 查看進(jìn)程fd的打開(kāi)情況,ls -la /proc/{pid}/fd/ 即可,或者 lsof命令打印指定進(jìn)程或所有進(jìn)程的fd信息,根據(jù)fd的打開(kāi)類型推測(cè)是什么類型文件的泄漏,多了fd類型線索然后根據(jù)日志去推測(cè)。

3.3 trace or ps信息 跟線程相關(guān)的fd泄漏通過(guò)進(jìn)程的信息可以很快定位出異常線程。

3.4 dumpsys window 通過(guò)window信息可以很快定位出異常window用于解決inputchannel相關(guān)的fd泄漏問(wèn)提。

4. Fd泄漏問(wèn)題信息獲取

當(dāng)然知道怎么分析fd泄漏問(wèn)題還是遠(yuǎn)遠(yuǎn)不夠的,對(duì)于fd泄漏的問(wèn)提最重要的應(yīng)該就是相關(guān)信息的日志獲取了,沒(méi)有米怎么做飯,沒(méi)有日志怎么分析,因此下面討論下關(guān)于fd泄漏相關(guān)的日志獲取。
一. 容易復(fù)現(xiàn),fd泄漏時(shí)可能會(huì)伴隨卡頓異常等現(xiàn)象,可以在進(jìn)程復(fù)現(xiàn)未掛的時(shí)候抓取想要的信息:
1.查看fd信息adb shell ls -a -l /proc/<pid>/fd ,lsof
2.查看進(jìn)程線程信息:ps -t <pid>,或者抓進(jìn)程trace, kill -3 <pid>
3.抓取hprof定位資源使用情況
二.難復(fù)現(xiàn),通常fd泄漏都不會(huì)是那么容易復(fù)現(xiàn)的,那就需要特殊的日志信息獲取方式了:
1.對(duì)于應(yīng)用自身fd泄漏發(fā)生JE時(shí)可以在復(fù)寫(xiě)UncatchHandlerException在應(yīng)用crash的時(shí)候通過(guò)readlink的方式讀取/proc/self/fd的信息,以獲取fd信息,抓取進(jìn)程的ps信息或者trace信息,抓取window情況,dumpsys window等;

2.O之后NE的Tombstone文件中有open files,可以查看打開(kāi)的fd信息;

3.針對(duì)rom廠商,可以在RuntimeInit.java中LoggingHandler 中對(duì)于Error Msg進(jìn)行判斷,如懷疑有fd泄漏即讀取/proc/self/fd/中的fd信息,記得使用readlink方法,當(dāng)然當(dāng)fd滿了的時(shí)候再讀的時(shí)候可能會(huì)讀不成功,嘗試使用i前面提供的方法提高進(jìn)程的fd rlimit再讀即可(我廠手機(jī)就是這么干的):
這種方法可以針對(duì)所有java進(jìn)程的fd泄漏異常時(shí)的fd信息獲取,RuntimeInit.java跑在各個(gè)應(yīng)用的進(jìn)程中;
如過(guò)再做個(gè)daemon進(jìn)程進(jìn)行判斷是否需要抓取fd信息則可以根據(jù)情況進(jìn)行打印,比如在跑穩(wěn)定性測(cè)試時(shí)默認(rèn)打印所有fd泄漏的進(jìn)程的fd信息,方便debug分析。

4.如過(guò)自動(dòng)化測(cè)試可以復(fù)現(xiàn),也可以嘗試寫(xiě)個(gè)腳本,一定時(shí)間獲取下指定進(jìn)程的fd信息,通過(guò)lsof,或ls -la /proc/<pid>/fd/方式獲取到fd數(shù)量,當(dāng)fd數(shù)量高于指定值比如900后執(zhí)行抓取需要的log信息腳本,比如logcat,trace,hprof,dumpsys window等;

總之就是根據(jù)打開(kāi)的文件類型,以及上下文的log來(lái)推斷重復(fù)打開(kāi)為關(guān)閉的文件句柄泄漏的代碼位置。

5. 總結(jié)

以上時(shí)序圖的uml文件及簡(jiǎn)化圖片的draw.io的xml文件已分享至百度云,如有修改可以下載自行修改:
https://pan.baidu.com/s/1I9GlkVmeSCKAS8JEqCbSaA

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

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

  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 13,897評(píng)論 2 59
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,716評(píng)論 25 709
  • 每個(gè)地方都有其獨(dú)特的魅力,內(nèi)蒙響沙灣的落日讓我印象深刻 【照片拍攝于國(guó)慶去內(nèi)蒙響沙灣旅游期間】 落日時(shí)整片天空被晚...
    江醬_閱讀 441評(píng)論 1 2
  • 總想打翻時(shí)光的杯子 你進(jìn)的來(lái) 或者,我出的去 再重復(fù)一場(chǎng)僅有的 相遇 而我,也絕不會(huì)急著尋求未來(lái) 苛刻明日 看著你...
    道夫123閱讀 386評(píng)論 12 39

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