1. 程式人生 > >Android遇到記憶體溢位(Out Of Memory)BUG的經驗與解決方法

Android遇到記憶體溢位(Out Of Memory)BUG的經驗與解決方法

突然出現的Out Of Memory這個BUG導致我們專案中斷了好幾天,在經過不斷地摸索之後,今天終於得到了解決。鑑於其強大的破壞力與多發性(尤其是當開發圖形豐富的軟體時),在此將解決方法同大家分享,希望大家以後少走彎路,而本人水平有限,如有不當,還望指教!

那麼,首先讓我們來看看遇到這個BUG時系統輸出的Log:


按我們的經驗一行一行地分析,發現了報錯的原因:bitmap size exceeds VM budget,

中文意思是bitmap佔用的記憶體大小超過了虛擬機器(DVM)的允許值。

帶著這個資訊,我去問谷哥和度娘,果然有大把大把的人遇到了這個問題,有的人還長久以來身陷其中,難以自拔~~

而解決方案則是五花八門,但是有的網友卻反映這些網上通用的解決方案完全沒作用!?

我並沒有嘗試所有網上的解決方法,在嘗試了部分之後確實沒有起到多少作用,該出BUG的地方照出不誤,

搞得我甚至有點懷疑這是Google的一個設計缺陷。

經過資訊檢索,我弄清了這樣一個事實:Android虛擬機器不允許單個程式中的Bitmap佔用超過8M的記憶體,一旦超過了就會報錯,

而報的錯正是bitmap size exceeds VM budget.

現在好了,這一切看似如此簡單:要想程式的bitmap小於8M,要麼就在用了bitmap後立即回收這部分記憶體,要麼就壓縮圖片的大小啊。

依據這兩點思路,我在我的專案中進行了實踐。

(一般而言,只用這兩種方法就可以解決大部分Out Of Memory的BUG,如果還不能解決,請繼續往下看)

第一種方法--及時回收bitmap記憶體:

一般而言,回收bitmap記憶體可以用到以下程式碼

  1. if(bitmap != null && !bitmap.isRecycled()){  
  2.         bitmap.recycle();  
  3.         bitmap = null;  
  4. }  
  5. System.gc();  

bitmap.recycle()方法用於回收該bitmap所佔用的記憶體,接著將bitmap置空,最後,別忘了用System.gc()呼叫一下系統的垃圾回收器。

在這裡要宣告一下,bitmap可以有多個(以為著可以有多個if語句),但System.gc()最好只有一個(所以我將它寫在了if語句外),因為System.gc()

每次呼叫都要將整個記憶體掃描一遍,因而如果多次呼叫的話會影響程式執行的速度。為了程式的效率,我將它放在了所有回收語句之後,

這樣已經起到了它的效果,還節約的時間。

回收bitmap已經知道了,那麼“及時”怎麼理解呢?

根據我的實際經驗,bitmap發揮作用的地方要麼在View裡,要麼在Activity裡(當然肯定有其他區域,但是原理都是類似的),

回收bitmap的地方最好寫在這些區域剛剛不使用bitmap了的時刻。

比如說View如果使用了bitmap,就應該在這個View不再繪製了的時候回收,或者是在跳轉到的下一個區域的程式碼中回收;

再比如說SurfaceView,就應該在onSurfaceDestroyed這個方法中回收;

同理,如果Activity使用了bitmap,就可以在onStop或者onDestroy方法中回收......

結合以上的共同點,“及時回收”的原理就是在使用了bitmap的區域結束時或結束後回收。

第二種方法--壓縮圖片:

這個方法當然很簡單了,就是使圖片體積大小變小,

可以有兩種方式:

一種是使圖片質量降低(解析度不變),

另一種是使圖片解析度降低(解析度改變)。

總之,使圖片大小變小就行了。

實踐證明,使圖片質量降低(解析度不變)可以大幅度地減小體積,而且質量的差異肉眼看上去並不明顯。

我剛開始使用的就是這兩種方法,原理很簡單,可是,我的BUG發生雖然沒那麼頻繁了,但是它依然存在!!

後來經過幾天的努力與嘗試,結合我專案的一些具體情況,我終於解決了這個令人頭痛的BUG,但是事實卻有點出乎我的意料。

當我使用了上述兩種方法BUG依然還沒解決的時候,我開始懷疑,bitmap超過8M會報錯,可現在我把前前後後的bitmap都回收了,

不可能還有8M了,那為什麼還會報錯呢?

終於我發現了這個原因:當記憶體中已經被一些bitmap使用過之後,無論被回收與否,它都會變得特別“敏感”,這個時候,

如果bitmap突然要佔用大量的記憶體,即使和之前已經剩下的記憶體加起來不到8M,系統也會報錯,原因是它變“敏感”了!

我不知道這個用底層原理如何解釋比較好,但是我想“敏感”這個詞應該可以很形象地進行解釋。

於是,為了順應記憶體的“敏感性”,我將那個需要同時裝載多個大體積bitmap的地方進行了修改,用到了以下方法:


  1. //壓縮,用於節省BITMAP記憶體空間--解決BUG的關鍵步驟
  2.  BitmapFactory.Options opts = new BitmapFactory.Options();  
  3. opts.inSampleSize = 2;    //這個的值壓縮的倍數(2的整數倍),數值越小,壓縮率越小,圖片越清晰
  4. //返回原圖解碼之後的bitmap物件
  5.  bitmap = BitmapFactory.decodeResource(Context, ResourcesId, opts);  

即先將圖片縮小一倍,再將這縮小了一倍的圖片作為bitmap存入記憶體,這樣一來,它佔用的bitmap記憶體大大減小。

後來經測試,BUG果然解決了。圖片縮小一倍後,順應了記憶體的“敏感性”,也就不會再報錯了。

以上方法應該足以解決大多數bitmap記憶體溢位問題,但是具體情況還是要具體分析。