原文鏈接:http://riggaroo.co.za/fixing-memory-leaks-in-android-outofmemoryerror/
注:本文在原文基礎上在如何判斷內存是否泄露方面進行了補充
安卓開發中經常出現內存溢出的情況,沒有防備的開發者可能一天會不經意間寫好幾個內存溢出的漏洞。你可能不會發現這些漏洞,甚至都不知道它們存在,直到你看到這種異常:
java.lang.OutOfMemoryError: Failed to allocate a 4308492 byte allocation with 467872 free bytes and 456KB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)
at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:988)
at android.content.res.Resources.loadDrawableForCookie(Resources.java:2580)
at android.content.res.Resources.loadDrawable(Resources.java:2487)
at android.content.res.Resources.getDrawable(Resources.java:814)
at android.content.res.Resources.getDrawable(Resources.java:767)
at com.nostra13.universalimageloader.core.DisplayImageOptions.getImageOnLoading(DisplayImageOptions.java:134)
這是啥意思呢?難道我的Bitmap太大了?
這種異常信息可能會讓你產生誤解。當你看到OutOfMemoryError的時候,十有八九是內存泄露。我第一次看到這個就以為我的Bitmap太大了,唉當時的我太年輕了。
什么是內存泄露?
內存泄露指程序中沒有正確的釋放用完的內存空間,導致運行異常或錯誤
Android中內存泄露是如何發生的?
事實上Android中內存泄露很容易發生。最大的問題就是Context對象。
每 一個App都有一個全局的Application Context對象,可以通過getApplicationContext()方法獲得。每一個Activity都是Context的子類,會存儲有關當 前Activity的信息。很多情況下,內存泄露與Activity泄露有關。比如有的開發者會將Context對象在各個線程里傳來傳去,寫一些靜態的 TextView,讓它們持有Activity的引用,這就是一個典型的錯誤。
判斷是否有內存泄露
Memory Monitor
-
將設備或模擬器與Android Studio連接。
-
在Android Studio中運行程序。
-
在 屏幕下方點擊Android標簽,再點擊Memory標簽,選擇設備與要監控的應用。一旦Memory Monitor開始監控設備,就會出現藍色的圖表顯示內存使用情況。深藍色部分為當前應用正在使用的內存,淺藍色(深藍色上面那一塊)為可以使用、未被分 配的內存空間。

-
點擊垃圾車圖標(上圖右側那個小圖標)可以觸發GC(內存回收)。
有內存溢出漏洞的應用在不斷操作並觸發GC后的圖表不斷上升的:

內存泄露被解決后的應用不斷操作並不斷觸發GC后圖表波動但維持穩定:

DDMS
打 開DDMS界面,在左側面板中選擇要觀察的進程,點擊左上角的Update Heap按鈕(紅圈圈出的圓柱體按鈕),再點擊右側面板中的Heap標簽,然后不斷操作並持續點擊Cause GC按鈕,觀察表中data object的Total Size的數值變化。如果持續增高而不下降,那就很可能是有內存泄露發生了。

有內存泄露時,應用不會重新得到用過的內存空間,當用到300MB的時候,OutOfMemoryError就發生了。從修復了內存泄露的應用的圖表可以看出,應用可以回收一些內存,並處在長期穩定狀態。
如何避免內存泄露?
-
不要將Context對象傳給activity與fragment以外的對象。
-
永遠不要將Context和View存儲在靜態變量中。
private static TextView textView;//不要這么干 private static Context context;//不要這么干 -
在onPause()/onDestroy()方法中解除監聽器,包括在Android自己的Listener,Location Service或Display Manager Service以及自己寫的Listener。
-
不要在后台線程與AsyncTask中存儲activity的強引用。不然當Activity被關閉后,由於AsyncTask仍在執行且持有Activity的強引用,導致Activity無法被回收。
-
使用Application Context而不是Activity的Context
-
盡量不要用非靜態內部類,因為它會持有外部類的引用。在非靜態內部類中存儲Activity或View的引用會導致內存泄露。如需存儲就使用WeakReference。
如何解決內存泄露?
解決內存泄露需要大量的錯誤積累與練習。內存泄露可能會很難追蹤,但借助一些工具可以幫助你識別可能的漏洞。這里我們使用MAT進行內存泄露的檢測。
-
打開Android Studio,點擊下方的Android Monitor標簽。
-
運行應用,並選擇應用來監控。
-
反復進行某個可能存在內存泄露的操作,比如返回打開關閉某個Activity。
-
在拋出OutOfMemoryException之前點擊Android Monitor中的Memory標簽。此時圖表開始繪制,過一會點擊GC圖標。(剛才說的那個紅色垃圾車圖標)

-
點擊垃圾車下面那個有綠色箭頭的圖標”Dump Java Heap”,並等待幾秒。然后會生成一個.hprof文件,這個文件就是用來分析內存使用的。
-
下載MAT。
-
使用sdk中platform-tools路徑下的hprof-conv文件將剛才生成的.hprof文件轉碼成MAT可以解析的文件。
./hprof-conv path/file.hprof exitPath/heap-converted.hprof -
使用MAT打開轉碼后的文件。選擇”Leak Suspects Report”然后點擊”Finish”。

-
點擊界面上方三條豎杠的圖標。而后你會看到占用內存的對象的列表。

-
這個列表可能難以理解,你可以輸入類名來過濾結果。我建議在輸入框中輸入package name,如下:

-
現 在我們可以看到VideoDetailActivity的9個實例,這顯然不對,因為我們只應該有一個。我們可以右鍵單擊這一條目並選擇”Merge Paths to Shortest GC Root”並點擊“exclude all phantom/weak/soft etc.references”來看是什么持有VideoDetailActivity的引用。

而后持有引用的的線程就會顯示出來。而后你就可以深入觀察什么持有了Activity的引用。
-
通過下面的信息我們可以看到有一個DisplayListener被注冊了但從未解除。

所以這個漏洞就找到了,只需要解除監聽器就可以了。
不是所有的內存泄露漏洞都這么容易找到。希望這篇文章可以幫你避免內存泄露並在內存泄露發生時找到漏洞。
推薦:
