Android 圖片內存溢出(Out of Memory)

       Android 在加載圖片時經常會出現OOM的情況,也就是內存溢出,特別是同時加載多張圖片或者比較大的圖片時非常容易出現,給開發者造成了很大的麻煩,這篇文章也是我在開發的過程中遇到的所作出的解決方案,還有在網上找到的解決方法,列出來提供大家參考。


方案一:讀取圖片時,儘量要進行壓縮後再進行顯示,還要選擇合適的讀取方式。

儘量不要使用setImageBitmapsetImageResourceBitmapFactory.decodeResource來設置一張大圖,因爲這些函數在完成decode後,最終都是通過java層的createBitmap來完成的,需要消耗更多內存。 因此,改用先通過BitmapFactory.decodeStream方法,創建出一個bitmap,再將其設爲ImageView的  source,decodeStream最大的祕密在於其直接調用JNI>>nativeDecodeAsset()來完成decode,無需再使用java層的createBitmap,從而節省了java層的空間。

         InputStream is = this.getResources().openRawResource(R.drawable.pic1);

         BitmapFactory.Options options = new  BitmapFactory.Options();

         options.inJustDecodeBounds =  false;

         options.inSampleSize =  10;   // widthhight設爲原來的十分一

         Bitmap btp =  BitmapFactory.decodeStream(is, null,  options);


如果在讀取時加上圖片的Config參數,可以更有效減少加載的內存,從而跟有效阻止拋out of Memory異常。 

   /**

     *  以最省內存的方式讀取本地資源的圖片

     *  @param context

     *  @param resId

     *  @return

      */

    public  static  Bitmap readBitMap(Context  context, int resId){ 

         BitmapFactory.Options opt = new  BitmapFactory.Options();

         opt.inPreferredConfig =  Bitmap.Config.RGB_565;

         opt.inPurgeable = true;

         opt.inInputShareable = true;

         //  獲取資源圖片

        InputStream is =  context.getResources().openRawResource(resId);

         return  BitmapFactory.decodeStream(is, null, opt);

         }

另外,decodeStream直接拿圖片來讀取字節碼,不會根據機器的各種分辨率來自動適應,使用了decodeStream之後,需要在hdpi和mdpi,ldpi中配置相應的圖片資源,  否則在不同分辨率機器上都是同樣大小(像素點數量),顯示出來的大小就不對了。 


方案二:在適當的時候及時回收圖片佔用的內存  

通常Activity或者Fragment在onStop/onDestroy時候就可以釋放圖片資源:  

 if(imageView !=  null && imageView.getDrawable() != null){

      Bitmap oldBitmap =  ((BitmapDrawable) imageView.getDrawable()).getBitmap();    

      imageView.setImageDrawable(null);    

      if(oldBitmap !=  null){    

            oldBitmap.recycle();     

            oldBitmap =  null;   

      }    

 }   

 //  Other code.

 System.gc();


在釋放資源時,需要注意釋放的Bitmap或者相關的Drawable是否有被其它類引用。如果正常的調用,可以通過Bitmap.isRecycled()方法來判斷是否有被標記回收;而如果是被UI線程的界面相關代碼使用,就需要特別小心避免回收有可能被使用的資源,不然有可能拋出系統異常: E/AndroidRuntime: java.lang.IllegalArgumentException: Cannot draw recycled  bitmaps 並且該異常無法有效捕捉並處理。 
方案三:不必要的時候避免圖片的完整加載 

只需要知道圖片大小的情形下,可以不完整加載圖片到內存。 在使用BitmapFactory壓縮圖片的時候,BitmapFactory.Options設置inJustDecodeBounds爲true後,再使用decodeFile()等方法,可以在不分配空間狀態下計算出圖片的大小。示例:   

 BitmapFactory.Options opts =  new  BitmapFactory.Options();     

 //  設置inJustDecodeBounds爲false     

 opts.inJustDecodeBounds = false   

 //  使用decodeFile方法得到圖片的寬和高    

 BitmapFactory.decodeFile(path,  opts);    

 //  打印出圖片的寬和高

 Log.d("example", opts.outWidth + "," + opts.outHeight);

(ps:原理其實就是通過圖片的頭部信息讀取圖片的基本信息) 


方案四:優化Dalvik虛擬機的堆內存分配  

堆(HEAP)是VM中佔用內存最多的部分,通常是動態分配的。堆的大小不是一成不變的,通常有一個分配機制來控制它的大小。比如初始的HEAP是4M大,當4M的空間被佔用超過75%的時候,重新分配堆爲8M大;當8M被佔用超過75%,分配堆爲16M大。倒過來,當16M的堆利用不足30%的時候,縮減它的大小爲8M大。重新設置堆的大小,尤其是壓縮,一般會涉及到內存的拷貝,所以變更堆的大小對效率有不良影響。 Heap  Utilization是堆的利用率。當實際的利用率偏離這個百分比的時候,虛擬機會在GC的時候調整堆內存大小,讓實際佔用率向個百分比靠攏。使用  dalvik.system.VMRuntime類提供的setTargetHeapUtilization方法可以增強程序堆內存的處理效率。  

 private final static float  TARGET_HEAP_UTILIZATION = 0.75f;    

 //  在程序onCreate時就可以調用

 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);


方案五、自定義堆(Heap)內存大小
  

對於一些Android項目,影響性能瓶頸的主要是Android自己內存管理機制問題,目前手機廠商對RAM都比較吝嗇,對於軟件的流暢性來說RAM對性能的影響十分敏感,除了優化Dalvik虛擬機的堆內存分配外,我們還可以強制定義自己軟件的對內存大小,我們使用Dalvik提供的  dalvik.system.VMRuntime類來設置最小堆內存爲例:  

 private final static int  CWJ_HEAP_SIZE = 6 * 1024 * 1024  ;

 VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);  //  設置最小heap內存爲6MB大小。

但是上面方法還是存在問題,函數setMinimumHeapSize其實只是改變了堆的下限值,它可以防止過於頻繁的堆內存分配,當設置最小堆內存大小超過上限值(Max Heap  Size)時仍然採用堆的上限值,對於內存不足沒什麼作用。  
最後介紹一下圖片佔用進程的內存算法。android中處理圖片的基礎類是Bitmap,顧名思義,就是位圖。佔用內存的算法如:圖片的width*height*Config。 如果Config設置爲ARGB_8888,那麼上面的Config就是4。一張480*320的圖片佔用的內存就是480*320*4  byte。 在默認情況下android進程的內存佔用量爲16M,因爲Bitmap他除了java中持有數據外,底層C++的  skia圖形庫還會持有一個SKBitmap對象,因此一般圖片佔用內存推薦大小應該不超過8M。這個可以調整,編譯源代碼時可以設置參數。

參考了開源中國社區中的

Android加載圖片導致內存溢出(Out of Memory異常)


發佈了55 篇原創文章 · 獲贊 17 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章