最近開發(fā)功能的時(shí)候monkey總是能跑出一個(gè)bug,
java.lang.RuntimeException: Could not read input channel file descriptors from parcel.
百思不得其解,認(rèn)為是系統(tǒng)上面的bug,實(shí)時(shí)證明自己還是太年輕.現(xiàn)在開始分析一下這個(gè)bug產(chǎn)生的原因.
一.為什么會(huì)產(chǎn)生句柄泄露?
眾所周知Android是linux內(nèi)核,也就是可以理解linux下,一切資源都是句柄,每個(gè)進(jìn)程都有自己的句柄上限,而超過了這個(gè)句柄上線,就會(huì)發(fā)生異常.一般android的App都是在單個(gè)進(jìn)程下運(yùn)行的,FD的句柄上限是1024,這個(gè)在后面的會(huì)說明.一切的重點(diǎn)都在proc(Processes,虛擬的目錄,是系統(tǒng)內(nèi)存的映射??芍苯釉L問這個(gè)目錄來獲取系統(tǒng)信息。 )這個(gè)文件夾下的內(nèi)容.
這里有參考The proc File System.
The Linux kernel has two primary functions: to control access to physical devices on the computer and to schedule when and how processes interact with these devices. The /proc/ directory — also called the proc file system — contains a hierarchy of special files which represent the current state of the kernel — allowing applications and users to peer into the kernel's view of the system.
Within the /proc/ directory, one can find a wealth of information detailing the system hardware and any processes currently running. In addition, some of the files within the /proc/ directory tree can be manipulated by users and applications to communicate configuration changes to the kernel.
Linux系統(tǒng)上的/proc目錄是一種文件系統(tǒng),即proc文件系統(tǒng)。與其它常見的文件系統(tǒng)不同的是,/proc是一種偽文件系統(tǒng)(也即虛擬文件系統(tǒng)),存儲(chǔ)的是當(dāng)前內(nèi)核運(yùn)行狀態(tài)的一系列特殊文件,用戶可以通過這些文件查看有關(guān)系統(tǒng)硬件及當(dāng)前正在運(yùn)行進(jìn)程的信息,甚至可以通過更改其中某些文件來改變內(nèi)核的運(yùn)行狀態(tài)。
二.查看進(jìn)程的句柄信息
我們通過proc這個(gè)虛擬文件系統(tǒng),可以獲取到當(dāng)前App的進(jìn)程,然后查看具體的進(jìn)程信息,當(dāng)前App是否存在句柄泄露的問題.具體方法如下.
public class MainActivity extends Activity {
private PageView mPageView;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
exeCommand("ps");
}
private void exeCommand(String command){
Runtime runtime = Runtime.getRuntime();
try {
Process proc = runtime.exec(command);
BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
這樣我們看到在Android Studio的logcat中打印出了當(dāng)前所有的進(jìn)程號(hào).

然后我們繼續(xù)修改我們的代碼.
exeCommand("ps | grep com.behanga");
com.behanga是我測(cè)試App的名字.

這是我們可以看到該App的進(jìn)程號(hào)是16023,也就是說對(duì)應(yīng)在proc中對(duì)應(yīng)的文件夾為/proc/16023/,在這個(gè)文件夾下我們看到當(dāng)前App的信息,好我們繼續(xù)往下查看.
這時(shí)我們打開terminal,輸入
adb shell
進(jìn)入adb shell,然后輸入
cd /proc/16023 && ls -al
現(xiàn)在展示在我們面前的就是當(dāng)前進(jìn)程下信息.

我們現(xiàn)在可以查看一下當(dāng)前進(jìn)程一些限制.
cat limits

這里我們看到Max open fils的限制為1024,硬件限制為4096,也就是說我們當(dāng)前打開文件句柄的最大數(shù)為1024.
這時(shí)我們繼續(xù)往下看,因?yàn)槭且治鰂ile descriptors的問題,那么自然要進(jìn)入fd目錄下一看究竟.
cd fd && ls -al

當(dāng)前展示了所在進(jìn)程下所有已經(jīng)被打開的文件句柄.我們可以在當(dāng)前文件夾下統(tǒng)計(jì)所有的句柄總數(shù).
ls | wc -l
這時(shí)打印出所有的句柄總數(shù)為64.這時(shí)我們已經(jīng)完成對(duì)當(dāng)前進(jìn)程的一些句柄信息查看.
三.模擬句柄泄露情況
如果我們要模擬句柄泄漏的情況,我們可以在App中啟動(dòng)一個(gè)HandlerThread,每次在onCreate的時(shí)候HandlerThread.start(),但是在onDestory()時(shí),并不調(diào)用HandlerThread.quit()方法.重復(fù)進(jìn)入.然后在terminal中統(tǒng)計(jì)FD的總數(shù),查看是否有變化,如果FD的總數(shù)一直在增加,就可以確認(rèn)你當(dāng)前App中已經(jīng)出現(xiàn)了句柄泄露的情況.
四.如何避免句柄泄露的發(fā)生
提高對(duì)編碼意識(shí),另外就是monkey的測(cè)試,很多時(shí)候會(huì)發(fā)現(xiàn)一些黑盒測(cè)試無法發(fā)現(xiàn)的問題.