Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> [轉]File Descriptor洩漏導致Crash: Too many open files,descriptorcrash

[轉]File Descriptor洩漏導致Crash: Too many open files,descriptorcrash

編輯:關於android開發

[轉]File Descriptor洩漏導致Crash: Too many open files,descriptorcrash


在實際的Android開發過程中,我們遇到了一些奇奇怪怪的Crash,通過sigaction再配合libcorkscrew以及一些第三方的Crash Reporter都捕獲不到發生Crash的具體信息,十分頭疼。然後我們通過Bugly上報的Java的CallStack觀察發現這些Crash發現了一些共同的信息:

看來是和OpenGL有關系,於是我們進一步對程序輸出的log進行觀察,又發現:

從這個log裡面我們獲得了幾個信息:

我們對我們已有的設備反復試驗,確實了只有Adreno的設備(小米3,HTC M8,華為P7等)會在特定條件下出現這種奇奇怪怪的隨機Crash。而其他設備例如小米Pad(Tegra),三星S3(Mali)等都不會出現這種問題。這個問題確實頭疼,在網上搜索了很久也沒找到有用的信息。直到在某次小米3上再次測試的時候,發現了log裡面還有一條必然出現的信息:

E/MemoryHeapBase(18703): error creating ashmem region: Too many open files

這個信息間接的指出了問題,也給了我們一些提示:似乎打開了過多的文件。於是靠著這個靈光,我們嘗試著在程序中輸出所有已打開的文件:

 SHOW FILE HANDLES:
 0  (socket:[285038]): read-write
 1  (/dev/null): read-write
 2  (/dev/null): read-write
 3  (/dev/log/main): cloexec write-only
 4  (/dev/log/radio): cloexec write-only
 5  (/dev/log/events): cloexec write-only
 6  (/dev/log/system): cloexec write-only
 7  (/sys/kernel/debug/tracing/trace_marker): write-only
 8  (/dev/__properties__):
 9  (/dev/binder): cloexec read-write
10  (/dev/log/main): cloexec write-only
11  (/dev/log/radio): cloexec write-only
12  (/dev/log/events): cloexec write-only
13  (/dev/log/system): cloexec write-only
14  (/system/framework/framework-res.apk):
15  (/system/framework/core-libart.jar):
16  (pipe:[282578]): nonblock
17  (/dev/alarm):
18  (/dev/cpuctl/tasks): cloexec write-only
19  (/dev/cpuctl/bg_non_interactive/tasks): cloexec write-only
20  (socket:[282569]): read-write
21  (pipe:[282570]):
22  (pipe:[282570]): write-only
23  (pipe:[282578]): nonblock write-only
24  (anon_inode:[eventpoll]): read-write
25  (/data/app/---app_name---/base.apk):
26  (/data/data/---app_name---/databases/bugly_db): cloexec read-write
27  (socket:[285047]): read-write
28  (anon_inode:mali-8938): cloexec
29  (socket:[282605]): nonblock read-write
30  (socket:[283605]): nonblock read-write
31  (/dev/null): read-write
32  (/dev/ump): read-write
33  (socket:[285045]): nonblock read-write
34  (/dev/null): read-write
35  (/dev/mali): read-write
36  (anon_inode:mali-8938): cloexec
37  (anon_inode:mali-8938): cloexec
38  (/data/app/---app_name---/base.apk):
39  (anon_inode:mali-8938): cloexec
40  (anon_inode:mali-8938): cloexec
41  (/dev/null): read-write
42  (/dev/null): read-write
43  (/data/app/---app_name---/base.apk):
44  (/dev/null): read-write
45  (anon_inode:mali-8938): cloexec
46  (/data/data/---app_name---/files/DefaultFont.ttf):
47  (/data/app/---app_name---/base.apk):
48  (anon_inode:sync_fence):
49  (/dev/null): read-write
50  (socket:[285060]): cloexec read-write
52  (anon_inode:mali-8938): cloexec
53  (anon_inode:mali-8938): cloexec
54  (/dev/null): read-write
55  (anon_inode:sync_fence):
56  (pipe:[284134]): write-only
58  (anon_inode:sync_fence):
62  (anon_inode:sync_fence):
63  (anon_inode:sync_fence):

通過不停測試程序,發現已打開的文件數量一直有增無減,而當這些被打開的文件數量接近1024的時候,上面的eglSwapBuffers必然出錯。於是乎我們得出一個中間結論:

  • 如果程序打開的文件數量過多,會導致OpenGL swap buffer失敗!

這從字面上看著似乎有些扯淡,因為這兩者總感覺沒啥聯系。這個問題只會出現在Adreno的GPU上面,於是我們猜想:

  • Adreno的驅動在swap buffer的時候,需要申請新的FD,這個FD可能是某些硬件IO,具體不得而知;
  • 如果程序中其他的各種FD使用過多接近上限,會導致Adreno的驅動申請不到必要的FD,因此導致swap buffer失敗。

這樣看起來似乎就比較有道理了。雖然sawp buffer本身是不會Crash的,他並沒有raise任何signal,只是簡單的返回了一個錯誤的結果,但這會導致上層邏輯出現異常。這些異常在不同的設備上表現不一樣:

  • 有的設備會在Java層的eglSwapBuffers觸發Java層的Exception導致Crash;
  • 有的設備不會出現異常,但是會導致OpenGL停止工作(halt rendering),其表現結果就是程序卡住無響應;
  • 有的設備可能什麼都不會發生,但是如果你的交互觸發了其他邏輯:比如按回退鍵彈出對話框,對話框也需要FD,但是獲得不到,那麼彈出對話框的邏輯將拋出異常,

於是這就有了各種奇奇怪怪的Crash。

解決方案

通過對代碼的排查,我們發現在使用SoundPool處理音效的時候,確實存在FD洩露的情況:

1  private SoundPool m_soundPool;
2  public int loadSound(String path) {
3      int soundID = m_soundPool.load(getAssets().openFd(path), 0);
4      return soundID;
5  }
6  public unloadSound(int soundID) {
7      m_soundPool.unload(soundID);
8  }

雖然我們在不需要這些音效的時候,對其進行了卸載處理,但不知道是SoundPool類自身的缺陷,還是我們的使用不當,在實際測試中我們發現unload過後,在load中通過openFd打開的FD並沒有被釋放掉。強制調用System.gc()在一些設備(例如小米3)上可以釋放掉這部分FD,但是另一些設備(例如HTC M8)即使強制gc這無法卸載掉它們,於是便出現了FD洩露的情況。

最終我們自行對這些FD進行管理,並且在unload的時候手動調用這些FD的close方法:

 1  private SoundPool m_soundPool;
 2  private HashMap<Integer, AssetFileDescriptor> m_soundFdMap
 3  public int loadSound(String path) {
 4      AssetFileDescriptor fd = getAssets().openFd(path);
 5      int soundID = m_soundPool.load(fd, 0);
 6      m_soundFdMap.put(soundID, fd);
 7      return soundID;
 8  }
 9  public unloadSound(int soundID) {
10     m_soundPool.unload(soundID);
11     m_soundFdMap.get(soundID).close();
12  }

這之後FD再無洩露的情況發生,之前的各種設備上面的各種奇奇怪怪的Crash都被處理好了。

小結

這個問題粗略說起來就是:因為播放了太多的音效,導致Adreno底層渲染失敗,以至於上層邏輯各種失措,產生了很多奇奇怪怪的Crash。准確的解釋應該是:程序中的FD洩露如同內存洩露一樣是同樣需要得到關注的問題,FD的耗盡如同內存的耗盡一樣會導致程序的各種異常情況發生,但是前者不如後者那麼知名也不如後者容易被察覺。

 

轉自:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=28

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved