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”字段,来看他们有没有正确的关闭。或者,经过删代码这种“作减法”的方式,逐渐定位问题。笔者就是经过“作减法”的方式,定位到是公司内部的一个组件库的问题。

相关文章
相关标签/搜索