標籤:
今天做項目,發現需要顯示一張超大圖片,處理過後,還有561Kb
載入的時候,就crash --- OOM
shortMsg:java.lang.OutOfMemoryError
longMsg:java.lang.OutOfMemoryError: bitmap size exceeds VM budget
stackTrace:java.lang.OutOfMemoryError: bitmap size exceeds VM budget
at android.graphics.Bitmap.nativeCreate(Native Method)
at android.graphics.Bitmap.createBitmap(Bitmap.java:477)
at android.graphics.Bitmap.createBitmap(Bitmap.java:444)
at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:349)
at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:512)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:487)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)
代碼如下:
//this line will lead to OOM
detailView=(ImageView)findViewById(R.id.detailView); detailView.setBackgroundResource(R.drawable.more_info);
換成這種:
detailView.setImageResource(R.drawable.more_info); //也同樣會OOM
後來找到了solution:
/** * 以最省記憶體的方式讀取本地資源的圖片 * @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); }
取得bitmap之後,再 detailView.setImageBitmap(pdfImage); 就ok了!
那是為什麼,會導致oom呢:
原來當使用像
imageView.setBackgroundResource,
imageView.setImageResource,
BitmapFactory.decodeResource 這樣的方法來設定一張大圖片的時候
這些函數在完成decode後,最終都是通過java層的createBitmap來完成的,需要消耗更多記憶體。
因此,
1、改用先通過BitmapFactory.decodeStream方法,建立出一個bitmap
2、再將其設為ImageView的 source,decodeStream最大的秘密在於其直接調用JNI>>nativeDecodeAsset()來完成decode,無需再使用java層的createBitmap,從而節省了java層的空間。
如果在讀取時加上圖片的Config參數,可以跟有效減少載入的記憶體,從而跟有效阻止拋out of Memory異常。
另外,需要特別注意:
decodeStream是直接讀取圖片資料的位元組碼的, 不會根據機器的各種解析度來自動適應,使用了decodeStream之後,需要在hdpi和mdpi,ldpi中配置相應的圖片資源,否則在不同解析度機器上都是同樣大小(像素點數量),顯示出來的大小就不對了。
在載入圖片資源時,可採用以下一些方法來避免OOM的問題:
1,在Android 2.3.3以及之前,建議使用Bitmap.recycle()方法,及時釋放資源。
1,在Android 3.0開始
可設定BitmapFactory.options.inBitmap值,(從緩衝中擷取)達到重用Bitmap的目的。如果設定,則inPreferredConfig屬性值會被重用的Bitmap該屬性值覆蓋。
2,通過設定Options.inPreferredConfig值來降低記憶體消耗:
ARGB_8888: 每個像素4位元組. 共32位。 它為預設值
Alpha_8: 只儲存透明度,共8位,1位元組。
ARGB_4444: 共16位,2位元組。
RGB_565:共16位,2位元組。
如果不需要透明度,可把預設值ARGB_8888改為RGB_565,節約一半記憶體。
3,通過設定Options.inSampleSize 對大圖片進行壓縮,可先設定Options.inJustDecodeBounds,擷取Bitmap的外圍資料,寬和高等。然後計算壓縮比例,進行壓縮。
4,設定Options.inPurgeable和inInputShareable:讓系統能及時回收記憶體。
1、inPurgeable:
(1)設定為True,則使用BitmapFactory建立的Bitmap用於儲存Pixel的記憶體空間,在系統記憶體不足時可以被回收,當應用需要再次訪問該Bitmap的Pixel時,系統會再次調用BitmapFactory 的decode方法重建Bitmap的Pixel數組。
(2)設定為False時,表示不能被回收。
2、inInputShareable:設定是否深拷貝,與inPurgeable結合使用,inPurgeable為false時,該參數無意義。
True: share a reference to the input data(inputStream, array,etc)
False :a deep copy
5,使用decodeStream代替其他decodeResource,setImageResource,setImageBitmap等方法來載入圖片。
區別:
(1)decodeStream直接讀取圖片位元組碼,調用nativeDecodeAsset/nativeDecodeStream來完成decode。無需使用Java空間的一些額外處理過程,節省dalvik記憶體。但是由於直接讀取位元組碼,沒有處理過程,因此不會根據機器的各種解析度來自動適應,需要在hdpi,mdpi和ldpi中分別配置相應的圖片資源,否則在不同解析度機器上都是同樣的大小(像素點數量),顯示的實際大小不對。
decodeResource會在讀取完圖片資料後,根據機器的解析度,進行圖片的適配處理,導致增大了很多dalvik記憶體消耗。
decodeStream調用過程: decodeStream(InputStream,Rect,Options) -> nativeDecodeAsset/nativeDecodeStream
decodeResource調用過程:即finishDecode之後,調用額外的Java層的createBitmap方法,消耗更多dalvik記憶體。
decodeResource(Resource,resId,Options) -> decodeResourceStream (設定Options的inDensity和inTargetDensity參數) -> decodeStream() (在完成Decode後,進行finishDecode操作)
finishDecode() -> Bitmap.createScaleBitmap()(根據inDensity和inTargetDensity計算scale) -> Bitmap.createBitmap()
以上方法的組合使用,合理避免OOM錯誤。
Android--Bitmap效能最佳化