Android句柄泄漏(Fd leak)排查

背景

句柄泄漏(Fd leak)不是什麼新鮮問題了,網上一搜這種問題,有不少種解決辦法。 但沒有系統性,致使排查問題效率極低。筆者但願經過這一篇文章,幫助你們理清思路,快速解決問題。java

排查問題的黃金步驟

  1. 查log
  2. 找復現步驟
  3. 查代碼

本文也將按照這幾步,逐步深刻到問題中。linux

查log

通常發生這種問題時,堆棧以下:android

java.lang.RuntimeException
Could not read input channel file descriptors from parcel.
android.view.InputChannel.nativeReadFromParcel(Native Method)
android.view.InputChannel.readFromParcel(InputChannel.java:148)
android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:804)
android.view.ViewRootImpl.setView(ViewRootImpl.java:770)
android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3716)
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2901)
android.app.ActivityThread.-wrap11(Unknown Source:0)
android.app.ActivityThread$H.handleMessage(ActivityThread.java:1616)
android.os.Handler.dispatchMessage(Handler.java:106)
android.os.Looper.loop(Looper.java:173)
android.app.ActivityThread.main(ActivityThread.java:6653)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:821)android.view.InputChannel.nativeReadFromParcel(Native Method)
複製代碼

顯然這種堆棧是沒什麼卵用的,惟一的用處就是,你把這log去網上一搜,你們說這多是句柄泄漏(Fd leak)問題。shell

找復現步驟

這一步就有些學問了。 你有2種手段來找到這個問題的復現步驟:bash

  1. 利用「嚴格模式」,讓app主動輸出log,直接定位到發生問題的代碼。
  2. 經過純黑盒的方式,找到發生crash的操做步驟。

利用"嚴格模式(Strick Mode)"輸出日誌

嚴格模式的具體使用,你們能夠移步官網:developer.android.com/reference/a…app

public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.setThreadPolicy(new ThreadPolicy.Builder()
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()   // or .detectAll() for all detectable problems
                 .penaltyLog()
                 .build());
         StrictMode.setVmPolicy(new VmPolicy.Builder()
                 .detectLeakedSqlLiteObjects()
                 .detectLeakedClosableObjects()
                 .penaltyLog()
                 .penaltyDeath()
                 .build());
     }
     super.onCreate();
 }
複製代碼

咱們只要加入detectLeakedClosableObjects() penaltyLog()這兩個方法,logcat裏面搜索「Strick Mode」關鍵字,就能直接定位到是哪一個Closable沒有關閉。而每每大量的Closeable沒有關閉,會致使句柄泄漏(Fd leak)的發生。oop

可是,嚴格模式不是那麼的可靠,他並不能幫你發現全部的問題。我就經歷過靠嚴格模式排查了一遍事後,問題依然存在的狀況。最後仍是靠找到復現步驟、人工查代碼解決的。因此接下來的東西也很重要:ui

找到發生crash的操做步驟

找復現步驟,能夠經過靜置手機、跑monkey等等手段,「等待」crash的發生。但,這太沒效率。本文但願經過如下步驟,縮短這個「等待」的時間spa

找到一部fd上限低的手機

咱們知道致使句柄泄漏(Fd leak)的緣由是:當前進程所持有的fd(file descriptor)數量,超過了操做系統所設置的上限。那咱們首先要知道發生crash的手機對每一個進程設置的fd上限是怎樣的。操作系統

根據個人調研,華爲mate10 fd上限是3萬多,這種手機是不大容易復現問題的,上限過高。小米note1,華爲p9,上限是1024,這類手機是比較容易復現問題的。另外,從發生crash時先後的logcat來看,有些機型,會在發生句柄泄漏(Fd leak)問題後,動態的調高fd上限。好比小米mix 6。

那怎麼找呢?執行

adb shell ulimit -a (無需root)

你將會看到:

nofiles(descriptors)一項所示的數量,就是每一個進程可持有的fd上限。

更快一些:在到達fd上限以前,就找到步驟

首先,你須要一部root的手機。 進入adb shell,su,如下命令,能夠查看你的app進程所佔用的fd數量:

lsof | grep <進程號> | wc -l

你每作一些操做(或者靜置手機不動),就運行一下這個命令,對比先後幾回fd數量的變化,能夠大大縮小問題的範圍。

by the way:

每次都運行命令笨不笨呢?有沒有更好的辦法呢?能夠嘗試使用linux中的watch命令。例如:

watch -n 2 'adb shell su lsof | grep <進程號> | wc -l'

不過若是直接adb shell su lsof的話,查出來的結果爲0。爲什麼adb shell su這命令直接運行,不能正確的給adb提權到root?我也沒深究。有經驗的朋友能夠在評論區回覆,筆者不勝感激。

查代碼

在進行了上面的排查步驟後,查代碼的範圍也被大大的縮小了。你能夠經過搜索各類「Stream」字段,來看他們有沒有正確的關閉。或者,經過刪代碼這種「作減法」的方式,逐漸定位問題。筆者就是經過「作減法」的方式,定位到是公司內部的一個組件庫的問題。

相關文章
相關標籤/搜索