圖片加載
在瞭解圖片壓縮前,先簡單介紹常用的幾種圖片加載 方式,在BitmapFactory
類中提供了decodeFile
、decodeResource
、decodeStream
、decodeByteArray
、decodeFileDescriptor
幾種靜態方法,通過使用這些靜態方法可以將具體的文件、流、比特數組、文件描述符加載成爲Bitmap
對象。
圖片壓縮
老實說“圖片壓縮”這個詞,太過於含糊,因爲在運行過程中考慮到Bitmap內存問題,則需要在讀取得到Bitmap(位圖對象)是進行有選擇性讀取,在傳輸或存儲過程中爲了降低圖片文件大小,會對位圖信息進行壓縮然後寫入文件,但是前一種壓縮和後一種壓縮往往被混淆一談。
Bitmap(位圖)的壓縮
在介紹Bitmap的壓縮前,應該瞭解Bitmap的內存佔用,這樣才能更好地去理解Bitmap的壓縮。
色位深度
首先,需要了解色階深度。
屏幕上的每個像素點,在內存中都有一塊空間用來描述該像素點的顏色信息。同樣每張顯示在屏幕上的圖像,都有一個對應的Bitmap對象用來存儲圖像上每個像素點的信息,所以在Bitmap中有一塊大小爲:(圖像像素高 x 圖像像素寬 x 描述單個像素點需要的位)
的空間去存儲圖像中所有像素點的信息。描述單個像素點需要的位數空間
指的就是色位深度。Bitmap
中使用Bitmap.Config
來決定色位深度,目前Config有四個值:ALPHA_8、RGB_565、ARGB_4444、ARGB_8888。
(A:透明度 、R:紅、G:綠、B:藍)。
Config | 每個像素佔用的字節 | 說明 |
---|---|---|
ALPHA_8 | 1 bytes | 每個像素僅僅儲存透明度通道 |
RGB_565 | 2 bytes | 每個像素的RGB通道會保存,透明度不會保存,紅色通道5位,有25=32種表現形式;綠色通道6位,有26=64種表現形式;藍色通道5位,有2^5=32種表現形式 |
ARGB_4444 | 2 bytes | 每個像素的ARGB通道都會保存,透明度/紅色/綠色/藍色通道4位,有2^4=16種表現形式 |
ARGB_8888 | 4 bytes | 每個像素的ARGB通道都會保存,透明度/紅色/綠色/藍色通道8位,有2^8=256種表現形式 |
圖片文件與Bitmap
Android中常用的圖片文件通常以.png
、.jpeg
、 .jpg
格式進行存儲。png
文件是一種無損壓縮的位圖圖像存儲格式,而jpg
、jpeg
則是有損壓縮的位圖圖像存儲格式。所以相同寬高像素(或像素面面積)相同的圖像,保存爲png
文件的文件大小要比jpg
、jpeg
文件要大。
通過調用BitmapFactory
的圖片文件解析加載函數可將圖片文件加載進入內存當中成爲Bitmap
對象,由於png
、jpg
、jpeg
圖片文件均是Bitmap
壓縮以後寫入的文件,所以通常Bitmap
對象的大小大於文件大小。
壓縮–位圖讀取
由於Bitmap
所佔內存的大小主要取決於圖像像素寬
、圖像像素高
、色位深度
,故而在加載圖片時爲避免OOM的產生,可通過縮減寬高與減少色位深度的bit位數來達到壓縮Bitmap
所需空間。
- 通過設置加載選項
BitmapFactory.Options
的inSampleSize
實現高寬等比縮小達到壓縮效果,在第一次解析碼時,並不需要將文件全部解碼成Bitmap
對象,這裏只需要Bitmap
的原始高寬,所以Options.isJustDecodeBounds
設置爲true
。在計算得到合適的比例後,再設置採樣率參數inSampleSize
來得到適當大小的bitmap
對象;
/** * 取樣(尺寸等比)壓縮,由於reqWidth和reqHeight通常取 * ImageView的大小,爲了使圖片將ImageView填充滿避免出現 * 黑色空白區域,故而選取小比例作爲壓縮比值 * * @param resources * @param resId * @param reqWidth request width * @param reqHeight request height * */ public static Bitmap compressInSampleSize(Resources resources, int resId, int >reqWidth, int reqHeight) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmap bitmap = BitmapFactory.decodeResource(resources, resId, options); options.inSampleSize = getInSampleSize(bitmap, reqWidth, reqHeight); options.inJustDecodeBounds = false; bitmap.recycle(); Bitmap resultBitmap = BitmapFactory.decodeResource(resources, resId, options); return resultBitmap; } private static int getInSampleSize(Bitmap bitmap, int reqWidth, int reqHeight){ int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); int inSampleSize = 1; //若希望更改比例選擇策略,將'&&'改爲'||'即可 while (bitmapWidth/inSampleSize > reqWidth && bitmapHeight/inSampleSize > reqHeight){ inSampleSize *= 2; } return inSampleSize; }
- 通過設置色位深度減少
Bitmap
的佔用內存
/** * 選擇色階深度加載圖片 * * @param resources * @param resId * @param config Color order depth * */ public static Bitmap compressInPixColorBit(Resources resources, int resId, Bitmap.Config config) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = config == null? Bitmap.Config.RGB_565 : config; Bitmap resultBitmap = BitmapFactory.decodeResource(resources, resId, options); return resultBitmap; }
高寬等比縮小與設置色位深度實現內存佔用降低可以聯合使用,使
Bitmap
佔用內存更低,但同時也意味着更多信息的缺失,例如:如果採用RGB_565則圖片將沒有透明度通道。
壓縮–位圖寫入
網上很多博客均將質量壓縮作爲圖片加載防止OOM的一種解決方案,然而這是對“質量壓縮”的誤解。他們給出的質量壓縮方案,通常如下:
/**
* 質量壓縮
*
* @param bitmap
* @param targetSize target size equal to targetSize(kb)
* */
public static Bitmap compressInQuality(Bitmap bitmap, int targetSize) {
ByteArrayOutputStream resultBitmapStream = new ByteArrayOutputStream();
int quality = 100;
do{
resultBitmapStream.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, resultBitmapStream);
quality -= 10;
}while (resultBitmapStream.toByteArray().length/1024 > targetSize && quality > 0);
bitmap.recycle();
return BitmapFactory.decodeStream(new ByteArrayInputStream(resultBitmapStream.toByteArray()));
}
然後,如果把這個理解成“可以按質量quality
壓縮bitmap對象”便是大錯特錯。直接使用這樣的方案來試圖降低Bitmap
非但達不到效果,還會使內存提前用完。
對compress的使用反思
- 爲何
compress
這樣處理得到的OutputStream
,再經過解析後不能達到降低內存的效果內?
compress
是按照壓縮算法(算法根據第一個參數而定,PNG則是採用無損壓縮算法,JPEG則是採用有損壓縮算法)對bitmap對象中的信息進行壓縮然後寫入OutputStream
,具體壓縮率則取決於quality
(0~100,當採用無損壓縮PNG時,此參數被忽略),但是此時的bitmap
對象任然是原來的bitmap
對象,當使用decodeStream
對compress
產生的OutputStream
進行解析時,解析得到的新Bitmap
高寬與原來的Bitmap
高寬相等,不選擇色位深度的話,則時默認色位深度,新的Bitmap
的內存佔用甚至更高,所以這種處理方式會在內存中存在兩個bitmap
對象,還多申請了一份ByteArrayOutputStream
空間。 - 那質量壓縮
compress
什麼時候用呢?
compress
只是將一個bitmap
對象的信息壓縮進一個OutputStream
,如果選擇有損壓縮則在壓縮的過程中還會選擇性放棄一些圖片信息,無損壓縮則會保留圖片的全部信息,但是無損壓縮後的文件任然會比Bitmap
在內存中佔用的內存大小要小。所以,在網絡傳輸或者在存儲時使用compress
顯然是可行的。同時,如果Bitmap
是再加載過程中使用了壓縮,在compress
使用完成以後,由於Bitmap
所含信息減少,質量壓縮得到的數據流將會變得更小。對數據流進行解析的得到的Bitmap
的內存佔用也隨之降低。
注意事項
- 圖片加載需要考慮是否需要緩存
- 在對文件流進行解析時,應考慮是否一步解析,如果需要多步讀取流,應考慮到文件流的有序性,經過讀取後,流的起始位置發生變化,導致再次讀取爲
null
,此時建議使用文件描述符進行操作 - 位圖壓縮讀取與壓縮寫入爲IO操作,不應在主線程中進行