java.lang.OutOfMemoryError: bitmap size exceeds VM budget,
甚至有時候程式執行到一半時,
程式會突然重開,甚至關閉電源。
後來發現會出現這些狀況,大部份原因是因為記憶體用太兇了。
在Android裡,每new出一個實體時、做了太多的指派(Reference),
在heap裡都會占用記憶體。
據我目前所知,大部份的Android硬體裝置,
native heap都分配了16MB左右的空間,
但是隨著每臺硬體配備的不同,真正能用的記憶體大小,
又會低於16MB。
一占超出了這個使用空間,就會很容易丟出OutOfMemoryError這個錯誤訊息。
我目前遇到最常發生的時候就是在new一個圖檔Bitmap時,
Android在載入Bitmap時,每個圖點的換算是很耗記憶體的,
圖檔尺寸越大,所占的記憶體也越大
Android在繪圖時,每一個圖檔的算法是︰
圖檔的長*寬*圖檔類型
假設我今天有一張圖
該圖長︰177像素
寬︰111像素
圖檔類型︰RGB8888(每個8代表8bit,共有32bits=4Bytes)
那麼,這張圖記憶體的占用大小就會是
(177*111*4)/1024 = 76KB
我真實去試過,把原圖縮小後,記憶體占用量也真的降低了,
所以,去對圖檔做壓縮或者事先調整尺寸、甚至靈活的運用Bitmap類裡的recycle()這個回收函式,都是不錯的解決辦法。
Bitmap的使用要小心。
另外,我們常常會看到很多範例書裡,在寫ListView的Adapter時,
Adapter裡的getView()函式中,
常會有類似的以下片斷
@Override public View getView(int position,View convertView, ViewGroup parent){ ViewHolder holder; if(convertView == null){ convertView = mInflater.inflate(R.layout.file_row, null);//告訴系統我們有自己畫的View holder = new ViewHolder(); holder.text = (TextView) convertView.findViewById(R.id.text); holder.icon = (ImageView) convertView.findViewById(R.id.icon); convertView.setTag(holder); }else{ holder = (ViewHolder) convertView.getTag(); } holder.text.setText("test"); . . . return convertView; } //用來存放每個格子的資料的實體類別 private class ViewHolder{ TextView text; ImageView icon; }
ListView的每一個格子,都是Android系統呼叫getView()這個函式後,
去畫出來的。
getView()這個函式決定你每一格格子要畫成什麼鬼樣子(View)。
當我們要開始畫ListView時,
系統就先去看看convertView裡有沒有之前畫好的View,
想當然,一開始畫時怎麼可能會有畫好的View呢?
convertView當然會回傳null,
於是程式開始跑convertView == null 這個判斷式
告訴Android我們每一格ListView要畫成我們自訂的View: file_row(第5行)
(file_row裡我畫了ImageView、TextView等等的組合。你要讓每一個格子變成什麼樣子,就寫成什麼樣子)
而holder則是幫我們記住每個格子裡的資訊內容。
像我手上這臺Tablet一次能畫出5個View,
像剛才說的,系統會從convertView裡去找有沒有之前畫好的View,
ok,程式一次抓了5個convertView,都呈現成null,也就是之前並沒有畫好並產生出來的實體View。
於是,程式開始一口氣畫了5個View(並建立了5個holder實體,程式第6行)。
當我們看到系統將ListView畫出了5個格子並呈現給你看後,
使用者此時將手指往上滑動,ok,第1格、第2格、第3格格子陸續消失,
第6格、第7格、第8格也準備要出現了。
於是,Android又回去找convertView,
找到之前畫好的View,也new出來的holder實體,
於是,reuse,然後只是把holder裡面的內容改一改。
我們就看到第6格、第7格、第8格...又是新的資料。
但其實,那些實體是一直被reuse的。
=================================
好,有沒有想過這些View和實體為什麼要reuse?
我曾經沒有寫這個判斷式
if(convertView == null){ . . . }else{ . }而把每一個getView,都寫成新的View
convertView = mInflater.inflate(R.layout.file_row, null); holder = new ViewHolder(); holder.text = (TextView) convertView.findViewById(R.id.text); holder.icon = (ImageView) convertView.findViewById(R.id.icon); convertView.setTag(holder);也就是不斷new出實體和畫新的View。
問題來了,
這就是為什麼我會遇到記憶體超出的原因。
原來Android系統,擁有著這種reuse機制。
利用有限的資源,去做無限的事。
相關文章︰
1.使用MAT(記憶體分析)工具檢查Memory Leak
2.Memory Leak經驗分享-Drawable篇
3.何謂Memory Leak(記憶體洩漏)
可是 資源reuse 不會有錯亂的危險嗎?
ReplyDelete以這個例子來說
那個不停的被reuse的view
其中一格如果忽然有了什麼變動
例如 臨時要增加一個view進去 或是變化背景
會不會變成修改view (進而影響到所有格子) 而不是修改單一格子?
或許是程式碼不夠嚴謹才會發生這種事
或許是reuse真的有這種疑慮
想請教版主的經驗跟看法