Android圖片的加載與壓縮

圖片加載

在瞭解圖片壓縮前,先簡單介紹常用的幾種圖片加載 方式,在BitmapFactory類中提供了decodeFiledecodeResourcedecodeStreamdecodeByteArraydecodeFileDescriptor幾種靜態方法,通過使用這些靜態方法可以將具體的文件、流、比特數組、文件描述符加載成爲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文件是一種無損壓縮的位圖圖像存儲格式,而jpgjpeg則是有損壓縮的位圖圖像存儲格式。所以相同寬高像素(或像素面面積)相同的圖像,保存爲png文件的文件大小要比jpgjpeg文件要大。
通過調用BitmapFactory的圖片文件解析加載函數可將圖片文件加載進入內存當中成爲Bitmap對象,由於pngjpgjpeg圖片文件均是Bitmap壓縮以後寫入的文件,所以通常Bitmap對象的大小大於文件大小。

壓縮–位圖讀取

由於Bitmap所佔內存的大小主要取決於圖像像素寬圖像像素高色位深度,故而在加載圖片時爲避免OOM的產生,可通過縮減寬高與減少色位深度的bit位數來達到壓縮Bitmap所需空間。

  1. 通過設置加載選項BitmapFactory.OptionsinSampleSize實現高寬等比縮小達到壓縮效果,在第一次解析碼時,並不需要將文件全部解碼成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;
   }
  1. 通過設置色位深度減少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的使用反思

  1. 爲何compress這樣處理得到的OutputStream,再經過解析後不能達到降低內存的效果內?
    compress是按照壓縮算法(算法根據第一個參數而定,PNG則是採用無損壓縮算法,JPEG則是採用有損壓縮算法)對bitmap對象中的信息進行壓縮然後寫入OutputStream,具體壓縮率則取決於quality(0~100,當採用無損壓縮PNG時,此參數被忽略),但是此時的bitmap對象任然是原來的bitmap對象,當使用decodeStreamcompress產生的OutputStream進行解析時,解析得到的新Bitmap高寬與原來的Bitmap高寬相等,不選擇色位深度的話,則時默認色位深度,新的Bitmap的內存佔用甚至更高,所以這種處理方式會在內存中存在兩個bitmap對象,還多申請了一份ByteArrayOutputStream空間。
  2. 那質量壓縮compress什麼時候用呢?
    compress只是將一個bitmap對象的信息壓縮進一個OutputStream,如果選擇有損壓縮則在壓縮的過程中還會選擇性放棄一些圖片信息,無損壓縮則會保留圖片的全部信息,但是無損壓縮後的文件任然會比Bitmap在內存中佔用的內存大小要小。所以,在網絡傳輸或者在存儲時使用compress顯然是可行的。同時,如果Bitmap是再加載過程中使用了壓縮,在compress使用完成以後,由於Bitmap所含信息減少,質量壓縮得到的數據流將會變得更小。對數據流進行解析的得到的Bitmap的內存佔用也隨之降低。

注意事項

  • 圖片加載需要考慮是否需要緩存
  • 在對文件流進行解析時,應考慮是否一步解析,如果需要多步讀取流,應考慮到文件流的有序性,經過讀取後,流的起始位置發生變化,導致再次讀取爲null,此時建議使用文件描述符進行操作
  • 位圖壓縮讀取與壓縮寫入爲IO操作,不應在主線程中進行
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章