android 載入大量圖片

weixin_33711641發表於2013-01-31

引用:http://blog.csdn.net/ouyangtianhan/article/details/8450225

  1. <span style="font-family: Arial; ">  
  2. </span>  
  1. <span style="font-family: Arial; ">  
  2. 引用自:</span><a href="http://blog.csdn.net/awp258/article/details/7951932" style="font-family: Arial; ">http://blog.csdn.net/awp258/article/details/7951932</a>  

 

 

 

最近在xoom上開發應用,碰到ui設計都是使用圖片,而且是多個activity。開始沒覺得怎麼樣,就開始做唄。等做完了,開始在前三個activity執行沒問題,一切ok。但在最後一個activity裡,會經常出現oom(out of memory),由於在最後一個activity,需要開啟一個pdf,然後render,隨著multi-touch,reander的pdf頁縮放,由於reander的圖片本身就比較大(比如,如果pdf放大到當前螢幕的兩倍,pdf圖片佔用的記憶體為1280*800*4*2/(1024*1024),約等於8m),而且由於為了視覺上感受好,會在其中快取圖片(為了不讓使用者在使用過程中感受操作有停滯感),所以總是導致oom異常。

oh,my god!最怕碰到這種情況,android對於記憶體heap size限制讓人比較崩潰,ios雖然也號稱一個應用有記憶體限制,但是在實際使用中一個應用使用的記憶體往往可以超過100m,所以還是挺容易做一個效能滿意的應用程式。

我的應用到底哪些地方使用了這麼多記憶體,因為android3.0預設heap size為48m,按道理來說還是可以接受的,怎麼應用沒跑幾下就oom呢?沒辦法,只能通過ddms來分析,在ddms中“update heap”-》“cause gc”,來檢視應用的記憶體使用情況,發現每進入一個activity,1-byte array(byte[], boolean[])的值總是會相應的增加,到最後一個activity的時候啥都不幹,heap size已經快30m了,oh。。。怎麼會這樣。。。冷靜冷靜。。。通過分析,1-byte array就是bitmap的佔用空間,這就說明不斷有新的bitmap在記憶體中。由於ui使用了很多圖片,比如大背景圖,按鈕圖片等等,看來是這些圖片都會存在記憶體中,即使當前activity已經銷燬進入下一個activity,前一個activity的圖片資源也沒有銷燬。

原因找到了,但不是太想得通。因為在onCreate中我用mBtn.setBackgroundResource(R.drawable.splash)為控制元件設定背景圖,然後在onDestroy中會用((BitmapDrawable)mBtn.getBackground()).setCallback(null)清理背景圖。按道理來說圖片資源應該已經清理掉了的。百思不得其解,仔細看Bitmap的原始碼,它其實起的作用是銷燬java物件BitmapDrawable,而android為了提高效率,Bitmap真正的點陣圖資料是在ndk中用c寫的,所以用setCallback是不能銷燬點陣圖資料的,應該呼叫Bitmap的recycle()來清理記憶體。

所以想當然的在onDestroy加上((BitmapDrawable)mBtn.getBackground()).getBitmap().recycle(),這樣跑下來,記憶體情況很理想,不管在哪個activity中,使用的資源僅僅是當前activity用到的,就不會象之前到最後一個activity的時候,所有之前使用的資源都累積在記憶體中。在每個activity資源和class等使用的記憶體都在10m左右,已經很理想了(當然如果是在android低版本比如1.5,16時還是不行的,這得重新構架應用),可以為顯示pdf預留了比較多記憶體了。

但新的問題又出現了,當返回之前的activity時,會出現“try to use a recycled bitmap"的異常。這真是按了葫蘆起了瓢啊,內心那個沮喪。。。沒辦法,繼續分析。看來是後加上recycle引起的, 點陣圖肯定在記憶體中有引用,在返回之前的activity時,因為點陣圖資料其實已經被銷燬了,所以才造成目前的情況。在看了setBackgroundResource的原始碼以後,恍然大悟,android對於直接通過資源id載入的資源其實是做了cache的了,這樣下次再需要此資源的時候直接從cache中得到,這也是為效率考慮。但這樣做也造成了用過的資源都會在記憶體中,這樣的設計不是很適合使用了很多大圖片資源的應用,這樣累積下來應用的記憶體峰值是很高的。看了sdk後,

 

  1. //我用:  
  2. Bitmap bm = BitmapFactory.decodeResource(this.getResources(), R.drawable.splash);  
  3. BitmapDrawable bd = new BitmapDrawable(this.getResources(), bm);  
  4. mBtn.setBackgroundDrawable(bd);  
  5. //來代替mBtn.setBackgroundResource(R.drawable.splash)。  
  6. //銷燬的時候使用:  
  7. BitmapDrawable bd = (BitmapDrawable)mBtn.getBackground();  
  8. mBtn.setBackgroundResource(0);//別忘了把背景設為null,避免onDraw重新整理背景時候出現used a recycled bitmap錯誤  
  9. bd.setCallback(null);  
  10. bd.getBitmap().recycle();  



 

這樣調整後,避免了在應用裡快取所有的資源,節省了寶貴的記憶體,而其實這樣也不會造成太大效率問題,畢竟重新載入資源是非常快速,不會對效能造成很嚴重的影響,在xoom裡我沒有感受到和之前有什麼區別。

總之,在android上使用大量點陣圖是個比較痛苦的事,記憶體限制的存在對應用是個很大的瓶頸。但不用因噎費食,其實弄明白了它裡面的機制,應用可以突破這些限制的。這只是其中的一種處理方法,還可以考慮BitmapFactory.Options的inSampleSize來減少記憶體佔用。

相關文章