天天看點

查找并修複Android中的記憶體洩露—OutOfMemoryError

啥?這是什麼意思?是說我的位圖(bitmap)太大了嗎?

不幸的是,這種堆棧跟蹤往往帶點迷惑性。通常,如果遇到 outofmemoryerror 錯誤,十有八九是因為記憶體洩露。當筆者第一次遇到這種堆棧跟蹤時,也感到迷惑不解,想着是不是位圖太大了……實際上,我那會兒真是大錯特錯。

記憶體洩露是指程式釋放廢棄記憶體失敗,導緻性能受損或出現中斷。

每個 app 都有一個全局的應用上下文對象( <code>getapplicationcontext()</code>)。每個 activity (活動)都是 <code>context</code> 的子類,存儲着與目前活動相關的資訊。通常,記憶體洩露都與已洩露的活動(leaked activtiy)相關。

通常,一般的開發者會把上下文對象(context object)傳給需要的線程。建立一些靜态的 textviews 以存儲指向活動的引用。但是,你懂的,這樣可行嗎?

在此情況下,如果使用記憶體螢幕就會發現,app 的記憶體使用率不斷增加,正如下面的 android 記憶體監控器所示:

查找并修複Android中的記憶體洩露—OutOfMemoryError

存在記憶體洩露問題的 app 在運作時,android 記憶體監控器的情況

查找并修複Android中的記憶體洩露—OutOfMemoryError

解決記憶體洩露問題後,android 記憶體監控器的情況

如你所見,在第一張圖中,app 永遠都無法回收一部分已經使用的記憶體。在 outofmemoryerror 錯誤出現之前,它一度使用了300mb的記憶體。而第二張圖則顯示,app 能順利進行垃圾回收,重得一部分記憶體,進而保持相當穩定的記憶體使用量。

避免在 activity 或 fragment 之外傳遞 context 對象。

永遠永遠不要建立靜态的 context 或 view 對象,或者将二者存儲于靜态變量中。這是記憶體洩露的首要标志。

private static textview textview; //do not do this

private static context context; //do not do this

總是記得在 onpause() 或 ondestroy() 方法中的取消注冊監聽器(listeners)。這包括 android

監聽器,以及位置服務、顯示管理器服務,還有自定義的一些監聽器。

不要在 asynctasks(異步任務)或背景線程中存儲指向 activities 的強引用。activity 可能會關閉,但是

asynctask 會繼續執行,一直儲存着對該 activity 的引用。

如果可以,使用 context-application (getapplicationcontext()),而不是某個 activity

的 context 對象。

盡力避免使用非靜态的内部類。将引用存儲至某個 activity 或 view 内部會導緻記憶體洩露。如果不得不存儲引用,請使用

修複記憶體洩露問題需要許多實踐,不斷嘗試、試錯,才能取得成功。通常,記憶體洩露并不容易定位。值得慶幸的是,有許多現成的工具可以幫你找出潛在的洩露問題。

1、打開 android studio,打開 android monitor(監控器)選項。

2、運作你的應用,從可選應用中進行選擇你的應用,并運作之。

3、在 app 中進行一些操作,以達到類似的效果。譬如筆者,打開了一個新視訊,播放了50次。(為此,筆者寫了一個測試程式。)

4、此處的關鍵,是在出現 outofmemoryexception 異常之前捕獲應用的問題。

5、點選 android 監控器中的記憶體選項。

查找并修複Android中的記憶體洩露—OutOfMemoryError

6、你會看到一張動态繪制的圖表。準備好之後,點選“啟動垃圾回收(initiate gc)“(紅色的垃圾卡車圖示)。

9、運作下面的指令,将 android 的 .hprof 檔案轉換為 mat 能夠了解的格式。(hprof-conv 工具位于 sdk 的平台工具檔案夾下)

10、轉換完成後,在 mat 中打開該檔案。選擇“洩露疑點報告(leak suspects report)”,之後點選完成。

查找并修複Android中的記憶體洩露—OutOfMemoryError

打開 eclipse 記憶體分析器 —— 選擇洩露疑點報告

11、點選頂部的三個藍色柱形圖示,“為任意對象集合建立一個直方圖”。你會看到占用記憶體的一列對象。

查找并修複Android中的記憶體洩露—OutOfMemoryError

eclipse 記憶體分析器 — 直方圖

12、檢視這麼多對象或許會讓人摸不到頭腦。其實,你可以根據類名進行過濾,是以筆者建議你在類名過濾器中輸入類名。

查找并修複android中的記憶體洩露—outofmemoryerror 技術分享 第6張根據類名在 eclipse 記憶體分析器中過濾對象

13、現在,我們看到 <code>videodetailactivity</code> 存在9個執行個體。這顯然是不對的,因為我們其實隻需要一個。進一步檢視誰儲存着 <code>videodetailactivity</code> 的引用,右鍵點選該項目,選擇“合并垃圾回收根的最短路徑(merge paths to shortest gc root)”,然後點選“排除所有虛/弱/軟引用(exclude all phantom/weak/soft etc. references)。”

查找并修複Android中的記憶體洩露—OutOfMemoryError

eclipse 記憶體分析器——合并垃圾回收根的最短路徑

現在,儲存着引用的線程就會顯示出來。之後,你可以追根溯源,找到存儲該 activity 引用的具體執行個體。

14、根據下面的資訊,顯然,有一個 displaylistener 對象在登記之後從未登出過。

查找并修複Android中的記憶體洩露—OutOfMemoryError

eclipse 記憶體分析器 — 記憶體洩露識别

是以,對這個此前登記的顯示監聽器(display listener)調用登出方法,就能解決此記憶體洩露問題。

參考連結:

<a href="http://developer.android.com/tools/performance/comparison.html">記憶體分析器</a>

<a href="http://developer.android.com/tools/performance/heap-viewer/index.html">堆記憶體檢視器介紹</a>

<a href="http://developer.android.com/tools/performance/memory-monitor/index.html">記憶體監控器</a>

<a href="http://android-developers.blogspot.co.za/2009/01/avoiding-memory-leaks.html">在 android 中避免記憶體洩露</a>

繼續閱讀