在Android中解決內存溢出 – OutOfMemoryError


原文鏈接: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

  1. 將設備或模擬器與Android Studio連接。

  2. 在Android Studio中運行程序。

  3. 在 屏幕下方點擊Android標簽,再點擊Memory標簽,選擇設備與要監控的應用。一旦Memory Monitor開始監控設備,就會出現藍色的圖表顯示內存使用情況。深藍色部分為當前應用正在使用的內存,淺藍色(深藍色上面那一塊)為可以使用、未被分 配的內存空間。

    演示圖片

  4. 點擊垃圾車圖標(上圖右側那個小圖標)可以觸發GC(內存回收)。

有內存溢出漏洞的應用在不斷操作並觸發GC后的圖表不斷上升的:

有內存泄露

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

無內存泄露

DDMS

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

DDMS

有內存泄露時,應用不會重新得到用過的內存空間,當用到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進行內存泄露的檢測。

  1. 打開Android Studio,點擊下方的Android Monitor標簽。

  2. 運行應用,並選擇應用來監控。

  3. 反復進行某個可能存在內存泄露的操作,比如返回打開關閉某個Activity。

  4. 在拋出OutOfMemoryException之前點擊Android Monitor中的Memory標簽。此時圖表開始繪制,過一會點擊GC圖標。(剛才說的那個紅色垃圾車圖標)

    hprof1

  5. 點擊垃圾車下面那個有綠色箭頭的圖標”Dump Java Heap”,並等待幾秒。然后會生成一個.hprof文件,這個文件就是用來分析內存使用的。

  6. 下載MAT

  7. 使用sdk中platform-tools路徑下的hprof-conv文件將剛才生成的.hprof文件轉碼成MAT可以解析的文件。

     ./hprof-conv path/file.hprof exitPath/heap-converted.hprof
    
  8. 使用MAT打開轉碼后的文件。選擇”Leak Suspects Report”然后點擊”Finish”。

    hprof2

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

    hprof3

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

    hprof4

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

    hprof5

    而后持有引用的的線程就會顯示出來。而后你就可以深入觀察什么持有了Activity的引用。

  12. 通過下面的信息我們可以看到有一個DisplayListener被注冊了但從未解除。

    hprof6

所以這個漏洞就找到了,只需要解除監聽器就可以了。

不是所有的內存泄露漏洞都這么容易找到。希望這篇文章可以幫你避免內存泄露並在內存泄露發生時找到漏洞。

推薦:

Android 多種方式正確的加載圖像,有效避免oom


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM