java.lang.OutofMemoryError: bitmap size exceeds VM budget
Android開發(fā)者應(yīng)該對上面這個(gè)錯(cuò)誤都不陌生陪捷。Android系統(tǒng)對每個(gè)應(yīng)用使用的內(nèi)存是有限制的厂庇,一旦應(yīng)用使用的總內(nèi)存超過這個(gè)閥值善绎,系統(tǒng)就會(huì)拋出上面的錯(cuò)誤導(dǎo)致應(yīng)用crash。內(nèi)存溢出的錯(cuò)誤是開發(fā)者必須要解決的永淌,根據(jù)經(jīng)驗(yàn)來說蔫耽,內(nèi)存溢出很多場景都是由圖片資源使用不當(dāng)引起的。Android官方的開發(fā)者文檔中也有專門的文章來介紹這個(gè)問題抑党。這個(gè)系列的文章是閱讀官方文檔后的一個(gè)筆記。
(本文出處:http://www.reibang.com/p/590f61222637)
高效的加載高分辨率的圖片
我們以加載Galaxy Nexus拍攝的照片為例撵摆。500萬的攝像頭拍攝的照片分辨率為2592x1936像素底靠,如果我們使用ARGB_8888設(shè)置(android 2.3以后的默認(rèn)設(shè)置,這種設(shè)置規(guī)定用四個(gè)字節(jié)來存儲(chǔ)一個(gè)像素值)來加載這張圖片特铝,它將占用19M的內(nèi)存(2592*1936*4字節(jié))苛骨。在某些限制每個(gè)應(yīng)用最多使用16M內(nèi)存的手機(jī)上,這一張圖片就會(huì)導(dǎo)致內(nèi)存溢出苟呐。
出現(xiàn)這種情況怎么辦呢痒芝?冷靜分析我們會(huì)發(fā)現(xiàn)在手機(jī)上展示圖片時(shí)我們并不需要這么高分辨率的圖片。比如在屏幕分辨率為1920x1080的手機(jī)上牵素,即使你要展示圖片的imageview充滿了整個(gè)屏幕严衬,也最多需要1920x1080的圖片,更高分辨率的圖片對我們的展示效果并沒有提升笆呆,只會(huì)白白浪費(fèi)我們寶貴的內(nèi)存空間请琳。所以對這種加載高分辨率的圖片情況我們應(yīng)該加載一個(gè)低分辨率版本的圖片到內(nèi)存中。接下來我們看下具體的操作步驟赠幕。
- 加載圖片尺寸和類型
針對不同的圖片數(shù)據(jù)來源俄精,BitmapFactory提供了不同的解碼方法(decodeResource()、decodeFile()...)榕堰,這些方法在構(gòu)造圖片的時(shí)候會(huì)申請相應(yīng)的內(nèi)存空間竖慧,所以它們經(jīng)常拋出內(nèi)存溢出的異常。這些方法都允許傳入一個(gè)BitmapFactory.Options類型的參數(shù)來獲取將要構(gòu)建的圖片的屬性逆屡。如果將inJustDecodeBounds的值設(shè)置成true圾旨,這些方法將不會(huì)真正的創(chuàng)建圖片,也就不會(huì)占用內(nèi)存魏蔗,它們的返回值將會(huì)是空砍的。但是它們會(huì)讀取圖片資源的屬性,我們就可以從BitmapFactory.Options中獲取到圖片的尺寸和類型莺治。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
解碼圖片之前先檢查圖片的大小可以有效的避免內(nèi)存溢出廓鞠,除非你很確定你要加載的圖片不會(huì)導(dǎo)致內(nèi)存溢出。
- 加載縮小比例的圖片到內(nèi)存中
現(xiàn)在圖片的尺寸已經(jīng)獲取了谣旁,接下來就是判斷是將圖片全尺寸加載到內(nèi)存還是加載一個(gè)縮小版床佳。判斷的標(biāo)準(zhǔn)有以下幾條:- 全尺寸的圖片需要占用多大內(nèi)存空間;
- 應(yīng)用能夠提供給這張圖片的內(nèi)存空間的大新凇夕土;
- 展示這張圖片的imageview或其他UI組件的大小;
- 設(shè)備的屏幕大小和密度怨绣。
例如角溃,填充100x100縮略圖imageview時(shí)就沒有必要將1024x768的圖片全尺寸的加載到內(nèi)存中。這時(shí)就要設(shè)置BitmapFactory.Options中的inSampleSize屬性來告訴解碼器來加載一個(gè)縮小比例的圖片到內(nèi)存中篮撑。如果以inSampleSize=4的設(shè)置來解碼這張1024x768的圖片减细,將會(huì)得到一張256x196的圖片,加載這張圖片使用的內(nèi)存會(huì)從3M減少到196K 赢笨。inSampleSize的值是根據(jù)縮放比例計(jì)算出來的一個(gè)2的n次冪的數(shù)未蝌。下面是計(jì)算inSampleSize常用的方法。
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
加載縮小比例圖片大致有三個(gè)步驟:inJustDecodeBounds設(shè)置為true獲取原始圖片尺寸 --> 根據(jù)使用場景計(jì)算縮放比例inSampleSize --> inJustDecodeBounds設(shè)置為false茧妒,傳遞inSampleSize給解碼器創(chuàng)建縮小比例的圖片萧吠。示例代碼如下:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
有了上面的方法我們就可以很輕松的為大小為100x100縮略圖imageview加載圖片了。
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
本文是《Android中高效的顯示圖片》專題中的第一篇
- Android中高效的顯示圖片 - 加載大圖
- Android中高效的顯示圖片 - 非UI線程加載
- Android中高效的顯示圖片 - 圖片緩存
- Android中高效的顯示圖片 - Bitmap的內(nèi)存模型