1. 程式人生 > >解決大屏手機上setImageResource()記憶體溢位

解決大屏手機上setImageResource()記憶體溢位

說明這個問題,首先來看一下實際的記憶體佔用情況。

我們建立一個最簡單的android應用,一個Activity,內容是一張圖片,圖片放在drawable-hdpi目錄下。佈局檔案:

<?xmlversion="1.0" encoding="utf-8"?>

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:background="@drawable/welcome"

android:id="@+id/splash_layout">

</RelativeLayout>

圖片檔案大小145k。

開啟DDMS,檢視記憶體佔用的情況:

在米1(小屏手機)上的記憶體佔用情況:

而在大屏手機上顯示的記憶體佔用情況:

記憶體佔用了12M,1-byte array(byte,boolean)欄中,記憶體佔了8.5M,而最大一個物件佔了4.16M,應該就是圖片。而實際上圖片大小也就145K。之所以大屏手機會出現這種情況,主要是因為我們把圖片放在hdpi目錄下,而大屏手機實際上是xhdpi甚至是xxhdpi。根據Android官方文件提供的資料:

By default, Android scales your bitmapdrawables (.png,.jpg, and.giffiles) and Nine-Patch drawables (.9.pngfiles) sothat they render at the appropriate physical size on each device. For example,if your application provides bitmap drawables only for the baseline, mediumscreen density (mdpi), then the systemscales them up when on ahigh-density screen, and scales them down when on a low-densityscreen.

Android會自動拉伸圖片以適應當前手機的解析度。因此導致了OOM記憶體溢位。

那麼如果我們把hdpi的圖片複製一份到xhdpi和xxhdpi中會怎樣?

檢視執行的結果:

在米1(小屏手機)上的記憶體佔用情況:

而在大屏手機上顯示的記憶體佔用情況:

記憶體已經有所減少,特別是1-bytearray(byte[], boolean[])這一行。米1由於對應的是hdpi的圖片,所以不受影響。

但是這樣做也有問題,首先我們得把圖片都拷貝到不同解析度對應的目錄下(最正確的做法是分別做一套對應解析度的圖片);其次圖片佔用記憶體的大小雖然已經減小,但還是太大。

再想一想,我們在佈局檔案裡面寫android:background和android:src這些屬性,實際上解析之後執行的是view.setBackgroundResource和view.setImageResource方法,這兩個方法實際上是拿到資源ID再去獲取資源的drawable。他們會decode圖片後,最終都是通過java層的createBitmap來完成的,需要消耗更多記憶體。

實際上我們可以用decodeStream來替代,因為decodeStream直接呼叫JNI>>nativeDecodeAsset()來完成decode,無需再使用java層的createBitmap,從而節省了java層的空間.另外我們可以設定圖片的引數,例如設定為Bitmap.Config.RGB_565來減少記憶體開銷。因為在android文件中描述Bitmap.Config.RGB_565每一個畫素存在2個位元組中,而預設的Bitmap.Config.ARGB_8888每一個畫素則需要4個位元組,理論上足足節省了一半空間。

瞭解這些之後,我把佈局檔案中的android:background去掉,在java檔案中來設定背景。

如下:

BitmapFactory.Options opt = newBitmapFactory.Options();

opt.inPreferredConfig = Bitmap.Config.RGB_565;

opt.inPurgeable = true;

opt.inInputShareable = true;

//獲取資源圖片

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

Bitmap bitmap = BitmapFactory.decodeStream(is,null, opt);

is.close();

returnnew BitmapDrawable(context.getResources(),bitmap);

執行之後在來看效果:

在米1(小屏手機)上的記憶體佔用情況:

而在大屏手機上顯示的記憶體佔用情況:

記憶體瞬間降下來,1-bytearray(byte[], boolean[])這一行最大值為151K。

=================另外一篇解釋=============

android中用setBackgroundResource載入圖片時出現oom

用setBackgroundResource顯示多張圖片時,會出現oom,

在看了setBackgroundResource的原始碼以後,恍然大悟,android對於直接通過資源id載入的資源其實是做了cache的了,這樣下次再需要此資源的時候直接從cache中得到,這也是為效率考慮。但這樣做也造成了用過的資源都會在記憶體中,這樣的設計不是很適合使用了很多大圖片資源的應用,這樣累積下來應用的記憶體峰值是很高的。

 程式碼如下:

      detailView=(ImageView)findViewById(R.id.detailView);

      detailView.setBackgroundResource(R.drawable.more_info);//this line will lead to OOM

       換成這種:

      detailView.setImageResource(R.drawable.more_info); //也同樣會OOM

       後來找到了solution:

        /**
          * 以最省記憶體的方式讀取本地資源的圖片
          * @param context
          *@param resId
          * @return
          */  

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

opt.inPreferredConfig = Bitmap.Config.RGB_565;

opt.inPurgeable = true;

opt.inInputShareable = true;

InputStream is = getResources().openRawResource(

 R.drawable.splash );

Bitmap bm = BitmapFactory.decodeStream(is, null, opt);

BitmapDrawable bd = new BitmapDrawable(getResources(), bm);

holder.iv.setBackgroundDrawable(bd);

    取得bitmap之後,再 detailView.setImageBitmap(pdfImage); 就ok了!      

     那是為什麼,會導致oom呢:

原來當使用像 imageView.setBackgroundResource,imageView.setImageResource, 或者 BitmapFactory.decodeResource  這樣的方法來設定一張大圖片的時候,

         這些函式在完成decode後,最終都是通過java層的createBitmap來完成的,需要消耗更多記憶體。

因此,改用先通過BitmapFactory.decodeStream方法,創建出一個bitmap,再將其設為ImageView的 source,decodeStream最大的祕密在於其直接呼叫JNI>>nativeDecodeAsset()來完成decode,無需再使用java層的createBitmap,從而節省了java層的空間。如果在讀取時加上圖片的Config引數,可以跟有效減少載入的記憶體,從而跟有效阻止拋out of Memory異常

        另外,需要特別注意: 

         decodeStream是直接讀取圖片資料的位元組碼了, 不會根據機器的各種解析度來自動適應,使用了decodeStream之後,需要在hdpi和mdpi,ldpi中配置相應的圖片資源,否則在不同解析度機器上都是同樣大小(畫素點數量),顯示出來的大小就不對了。