Glide中解析圖片(靜態圖片)

wyman_1007發表於2017-12-26

對Glide的載入圖片流程不瞭解的可以看看之前寫的Glide範例原始碼分析

這篇主要介紹Glide在網路獲取流之後解析圖片,這裡講靜態圖片。

我們一般在Android中解析一張圖片一般是這樣的:

 public static Bitmap decodeBitmap(byte[] source,int reqesutWidth,int requestHeight){
Bitmap resultBitmap = null;
BitmapFactory.Options option = new BitmapFactory.Options();
//設定option.inJustDecodeBounds 為true獲取圖片寬高
option.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(source,0,source.length,option);
//通過BitmapFactory.Options.inSampleSize的值儲存壓縮比例值
option.inSampleSize = caulateInSampleSize(option,reqesutWidth,requestHeight);
option.inJustDecodeBounds = false;
resultBitmap = BitmapFactory.decodeByteArray(source,0,source.length,option);
return resultBitmap;
}
/**
* 計算壓縮比例
* 通過BitmapFactory.Options.inSampleSize的值儲存該值
* */

private static int caulateInSampleSize(BitmapFactory.Options option, int reqesutWidth, int requestHeight) {
//inSampleSize為1即圖片原本大小 如:4 即寬高比為原來1/4
int inSampleSize = 1;
//原圖片寬高
int width = option.outWidth;
int height = option.outHeight;
//原圖片寬高其中一個大於要求的寬高才壓縮比例
if(height > requestHeight || width > reqesutWidth){
inSampleSize = Math.min(Math.round((float)height / requestHeight), Math.round((float)width / reqesutWidth));
}
return inSampleSize;
}
複製程式碼

BitmapFactory.decodeByteArray(source,0,source.length,option);
這句話可以換成BitmapFactory.decodeSteam()或BitmapFactory.decodeResource()

這裡拋磚引玉:其實Glide的核心程式碼也是這樣解析的,沒有什麼特別之處;只不過逼格高一點;

Glide的decode是Downsampler類

Downsampler類

這個截圖是上篇文章的,該類是一個抽象類:
image.png

是這裡抽象:

image.png

該方法的作用就是上面丟擲程式碼計算BitmapFactory.Options.inSampleSize的;
這樣說吧:其實就是通過該欄位進行縮放;Glide也是在Downsampler類上實現了三種getSampleSize()該方法:

其中AT_LEAST的計算就是和上面丟擲的程式碼一毛一樣:

image.png

另外兩個:
image.png

image.png

BitmapFactory.inSampleSize是什麼

這裡要說一下inSampleSize幹什麼的,看官方解釋:

/**
* If set to a value > 1, requests the decoder to subsample the original
* image, returning a smaller image to save memory. The sample size is
* the number of pixels in either dimension that correspond to a single
* pixel in the decoded bitmap. For example, inSampleSize == 4 returns
* an image that is 1/4 the width/height of the original, and 1/16 the
* number of pixels. Any value <= 1 is treated the same as 1. Note: the
* decoder uses a final value based on powers of 2, any other value will
* be rounded down to the nearest power of 2.
*/
public int inSampleSize;

簡單理解就是該值越大,解析出來的Bitmap就越小。該值最小為1,取值為2的冪次。

下面是通過google翻譯得出:

如果設定為大於1的值,則請求解碼器對原始樣本進行二次取樣
影象,返回一個較小的影象,以節省記憶體。 樣本大小是
任一維度中對應於單個畫素的畫素數量
畫素在解碼的點陣圖中。 例如,inSampleSize == 4返回
原始寬度/高度的1/4,1/16的影象
畫素數。 任何值<= 1的處理都與1相同。注意:
解碼器使用基於2的冪的最終值,任何其他值將會
向下舍入到最接近的2的冪。

知道inSampleSize後,再回Downsampler類的decode()方法

Downsampler類decode()方法:

public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) {}

該方法返回一個Bitmap即解析流後的點陣圖。

  • InputStream is 網路返回的流
  • BitmapPool pool 用於解析Bitmap的快取
  • int outWidth ImageView要求的寬
  • int outHeight ImageView要求的高
  • DecodeFormat decodeFormat 要求解析的格式(預設為RGB_565)
    DecodeFormat類

RGB565為一個畫素佔2個位元組,而ARGB8888為一個畫素佔4個位元組;所以用ARGB_8888解析出來的圖片會佔記憶體。

decode()方法前期準備

image.png

  • decode()方法開始會 建立兩個64k大小的位元組陣列;

  • 獲取BitmapFactory.Options物件,該物件通過一個佇列管理,儘量減少建立新物件:
    BitmapFactory.Options物件管理

  • RecyclableBufferedInputStream 建立一個緩衝區,用於解析圖片頭部判別圖片方向

  • ExceptionCatchingInputStream 用於在讀取流中檢索異常(感覺挺牛B的,可以學習學習)

  • MarkEnforcingInputStream最終通過此流解析圖片

解析圖片的方向

image.png

這裡在流中設定一個標記位,目的就是通過最小讀取流來判斷圖片的方向。標記位最大值為5M

設定臨時儲存bitmap的容量;獲取圖片寬高;獲取圖片旋轉的角度

image.png

  • options.inTempStorage該值是用於臨時儲存Bitmap,官網建議是16K
    image.png
  • getDimensions(invalidatingStream, bufferedStream, options);
    該方法其實最開始已經有程式碼寫了,就是獲取寬高
    image.png

decodeStream

該方法後面真正解析圖片還會呼叫一次,這裡呼叫就是為了獲取寬高:
image.png
這裡因為不是解析bitmap,所以result為null,目的之前說了就是為了獲取寬高,所以也設定一個標記位減少讀取時間。

  • final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight);

該方法就是之前說的通過計算寬高比獲取sampleSize,最開頭說了,Glide預設用AT_LEAST

核心方法downsampleWithSize 解析圖片

這裡直接上原始碼:

private Bitmap downsampleWithSize(MarkEnforcingInputStream is, RecyclableBufferedInputStream  bufferedStream,
BitmapFactory.Options options, BitmapPool pool, int inWidth, int inHeight, int sampleSize,
DecodeFormat decodeFormat
)
{
// Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.
//在KitKat之前,inBitmap大小必須與我們正在解碼的點陣圖大小完全匹配
Bitmap.Config config = getConfig(is, decodeFormat);
//sampleSize,該值為1 即圖片原大小
options.inSampleSize = sampleSize;
options.inPreferredConfig = config;
//當版本高於KITKAT 時候使用 BitmapPool
if ((options.inSampleSize == 1 || Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) && shouldUsePool(is)) {
int targetWidth = (int) Math.ceil(inWidth / (double) sampleSize);
int targetHeight = (int) Math.ceil(inHeight / (double) sampleSize);
// BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
//在寫入BitmapFactory之前,BitmapFactory會清除Bitmap,所以getDirty是安全的
setInBitmap(options, pool.getDirty(targetWidth, targetHeight, config));
}
return decodeStream(is, bufferedStream, options);
}
複製程式碼

Android4.1版本使用:ARGB8888; image.png 如果圖片有透明度使用:ARGB8888;

當inSampleSize為1或大於Android4.4版本,並且屬於:
JPEG,PNG_A,PNG型別使用BitmapPool和BitmapFactory.Options.inBitmap
當然如果你的版本是4.4但是inSampleSize為1是那三種圖片型別還是可以使用上面說的兩個做快取;

最後其實就是一句:
final Bitmap result = BitmapFactory.decodeStream(is, null, options);
所以萬變不離其中;當然Glide團隊使用了各種快取,優化解析工作。

最後總結一下:

  • 其實解析圖片還是 final Bitmap result = BitmapFactory.decodeStream(is, null, options);
  • 通過ExceptionCatchingInputStream此包裝類可以捕獲在流讀取過程中的異常,值的學習。
  • 通過集合管理物件,儘量減少物件的建立,畢竟物件的建立對記憶體有一定開銷。

最尾說一下:BitmapPool這個類
image.png

相關文章