前言
Glide歸根結底是一個圖片載入框架,它一定會涉及到BitmapFactory
相關API把Bitmap
讀取到記憶體;可能大家已經很熟悉如何高效的載入Bitmap(比如使用inSample等),這一章還是要看一看Glide是如何玩轉的;
本文主要分析這兩個類:
DownsampleStrategy
Downsampler
從"Glide會對原圖進行放大"案例開始
假設我們有一張寬高100x200的網路圖片,需要載入到300x300畫素的ImageView上(scaleType屬性為CenterCrop),用Glide載入,不做任何處理會得到多大的Bitmap?
Glide.with(MainActivity.this).load(URL).listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
if (resource instanceof BitmapDrawable){
Bitmap bitmap = ((BitmapDrawable) resource).getBitmap();
Log.d("onResourceReady",bitmap.toString());
}
return false;
}
}).into(vh.imageView);
複製程式碼
不出意外會得到300*300的Bitmap,Bitmap比例顯然被放大了;
我懷疑是進行了Transformation
操作,所以我們要禁用Transformation
;
假設我們對RequestOptions加上noTransformation的屬性,比如這個樣子:
RequestOptions requestOptions = RequestOptions.noTransformation();
Glide.with(MainActivity.this).load(URL).apply(requestOptions).listener(xxx).into(vh.imageView);
複製程式碼
不出意外會得到300*600的Bitmap,Bitmap比例依然在被放大,甚至更大了;
如果我們不想讓Bitmap被放大,可以用載入原圖尺寸的方式,比如設定override(Target.SIZE_ORIGINAL,Target.SIZE_ORIGINAL)
或者用SimpleTarget
這樣的Target
,但是載入原圖終究不是解決方案,為什麼?記憶體的原因,大部分情況下我們還是希望Glide來對圖片進行縮小,載入原圖的操作等於是直接把APP往OOM送近了一步;
Bitmap被放大不是Glide發明的,是Android官方帶的頭,比如同一張圖片,放在res中不同drawable資料夾下,得到的尺寸不一樣;既然如此,Bitmap被放大肯定是有積極意義的,也難怪Glide很少提及這事;
但是,總會有強迫症患者不想Bitmap被放大,Glide肯定也想到了這一點,具體怎麼限制Bitmap不會放大,答案的使用RequestOptions.downsample()方法
;
RequestOptions.downsample()
方法接受引數型別為:DownsampleStrategy
,那麼我們從DownsampleStrategy
開始分析;
DownsampleStrategy
DownsampleStrategy
顧名思義就是下采樣策略,下采樣是影象進行縮小的一種方式;
DownsampleStrategy.java
public abstract class DownsampleStrategy {
//獲取縮放比例
public abstract float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth,
int requestedHeight);
//獲取SampleSize策略
public abstract SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight,
int requestedWidth, int requestedHeight);
}
public enum SampleSizeRounding {
//記憶體優先
MEMORY,
//圖片質量優先
QUALITY,
}
複製程式碼
DownsampleStrategy
是抽象類,提供兩個抽象方法,getScaleFactor()
顧名思義是獲取縮放比例的,getSampleSizeRounding()
可能是獲取SampleSize的,DownsampleStrategy
真是的實現類在其內部,幾個巢狀內部類FitCenter
,CenterOutside
,AtLeast
,AtMost
,None
,CenterInside
,除此之外,DownsampleStrategy
還定義對應類的靜態變數;
DownsampleStrategy.java
public static final DownsampleStrategy FIT_CENTER = new FitCenter();
public static final DownsampleStrategy CENTER_OUTSIDE = new CenterOutside();
public static final DownsampleStrategy AT_LEAST = new AtLeast();
public static final DownsampleStrategy AT_MOST = new AtMost();
public static final DownsampleStrategy CENTER_INSIDE = new CenterInside();
public static final DownsampleStrategy NONE = new None();
public static final DownsampleStrategy DEFAULT = CENTER_OUTSIDE;
複製程式碼
其中DEFAULT
應該是Glide預設的策略,它指向的是CENTER_OUTSIDE
,我們簡單看一下CenterOutside
的邏輯;
CenterOutside.java
private static class CenterOutside extends DownsampleStrategy {
@Synthetic
CenterOutside() { }
@Override
public float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth,
int requestedHeight) {
//寬度比例(控制元件/圖片)
float widthPercentage = requestedWidth / (float) sourceWidth;
//高度比例(控制元件/圖片)
float heightPercentage = requestedHeight / (float) sourceHeight;
//誰大取誰
return Math.max(widthPercentage, heightPercentage);
}
@Override
public SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight,
int requestedWidth, int requestedHeight) {
return SampleSizeRounding.QUALITY;
}
}
複製程式碼
主要關注是CenterOutside
的getScaleFactor()
邏輯,該邏輯非常簡單,主要是拿控制元件寬高/圖片寬高
,得到尺寸取最大值,我們回到文章開頭的那個例子,假設此時控制元件寬高是300x300,圖片寬高100x200:
widthPercentage = 300/100 = 3.0f;
heightPercentage = 300/200 = 1.5f;
Math.max(widthPercentage, heightPercentage) = 3.0f;
從這個推理來看,文章開頭那個圖片確實會被放大3倍;
文章開頭試圖找控制縮放比例不超過1的,就是隻縮小不放大的,有沒有這樣的策略,其實是有的,比如AtLeast
,AtMost
;
AtLeast.java
private static class AtLeast extends DownsampleStrategy {
@Synthetic
AtLeast() { }
@Override
public float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth,
int requestedHeight) {
int minIntegerFactor = Math.min(sourceHeight / requestedHeight, sourceWidth / requestedWidth);
return minIntegerFactor == 0 ? 1f : 1f / Integer.highestOneBit(minIntegerFactor);
}
@Override
public SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight,
int requestedWidth, int requestedHeight) {
return SampleSizeRounding.QUALITY;
}
}
複製程式碼
AtLeast
對getScaleFactor
的邏輯簡要分析:
獲取圖片寬高/佈局寬高的比例的最小值,轉成int值;
如果這個值等於0,直接返回1,等於0其實就意味著圖片寬高/佈局寬高至少有一個是小於1的,也就遮蔽了需要放大的情況;
如果minIntegerFactor
不等一0,肯定是需要縮小的,最後返回1f / Integer.highestOneBit(minIntegerFactor)
,其中Integer.highestOneBit(minIntegerFactor)
是取minIntegerFactor
的二進位制形式最左邊的最高一位且高位後面全部補零,最後返回int型的結果;
其餘的DownsampleStrategy
實現類就不講解了,DownsampleStrategy
是負責計算縮放比例和SampleSize策略,那麼真正去進行Bitmap計算的是Downsampler
;
重頭戲Downsampler
Downsampler.java
Downsampler
這個類在我們之前分析Decode流程時已經遇到過它,該類的主要職責是從輸入流中解析出Bitmap
,核心功能的入口方法在decode()
;
public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
Options options, DecodeCallbacks callbacks) throws IOException {
Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
+ " mark()");
byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
//獲取預設的BitmapFactory
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
bitmapFactoryOptions.inTempStorage = bytesForOptions;
//decodeFormat主要是RGB_8888||RGB_565
DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
//獲取DownsampleStrategy
DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
//是否fixedBitmapSize
boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
//是否支援硬體點陣圖
boolean isHardwareConfigAllowed =
options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);
try {
//解析出Bitmap
Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
requestedHeight, fixBitmapToRequestedDimensions, callbacks);
//BitmapResource包裝Bitmap
return BitmapResource.obtain(result, bitmapPool);
} finally {
releaseOptions(bitmapFactoryOptions);
byteArrayPool.put(bytesForOptions);
}
}
複製程式碼
首先呼叫getDefaultOptions()
獲取預設的BitmapFactory
;
預設的BitmapFactory
預設的BitmapFactory使用一個佇列快取,初始化設定在resetOptions()
方法;
private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {
decodeBitmapOptions.inTempStorage = null;
decodeBitmapOptions.inDither = false;
decodeBitmapOptions.inScaled = false;
decodeBitmapOptions.inSampleSize = 1;
decodeBitmapOptions.inPreferredConfig = null;
decodeBitmapOptions.inJustDecodeBounds = false;
decodeBitmapOptions.inDensity = 0;
decodeBitmapOptions.inTargetDensity = 0;
decodeBitmapOptions.outWidth = 0;
decodeBitmapOptions.outHeight = 0;
decodeBitmapOptions.outMimeType = null;
decodeBitmapOptions.inBitmap = null;
decodeBitmapOptions.inMutable = true;
}
複製程式碼
繼續decode()
方法分析,首先從Option
中獲取decodeFormat
,fixBitmapToRequestedDimensions
,isHardwareConfigAllowed
等,這個Option
是從RequestOptions
傳遞過來,代表使用者的配置;
decodeFormat
表示解析格式,RGB_565或ARGB_8888;fixBitmapToRequestedDimensions
表示是否填充尺寸,不進行縮放isHardwareConfigAllowed
是否允許硬體點陣圖,詳細瞭解可以看Glide官方文件:muyangmin.github.io/glide-docs-…- 用
BitmapResource
包裝Bitmap
返回; 真正的載入Bitmap邏輯在decodeFromWrappedStreams()
方法:
decodeFromWrappedStreams
decodeFromWrappedStreams()
private Bitmap decodeFromWrappedStreams(InputStream is,
BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
int requestedHeight, boolean fixBitmapToRequestedDimensions,
DecodeCallbacks callbacks) throws IOException {
long startTime = LogTime.getLogTime();
//解析出輸入流圖片尺寸
int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
int sourceWidth = sourceDimensions[0];
int sourceHeight = sourceDimensions[1];
//得到mimeType
String sourceMimeType = options.outMimeType;
if (sourceWidth == -1 || sourceHeight == -1) {
isHardwareConfigAllowed = false;
}
//獲取圖片旋轉方向
int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);
//判斷目標控制元件尺寸
int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;
//獲取image型別
ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);
//計算縮放
calculateScaling();
//計算其他config
calculateConfig();
boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
//處理inBitmap
if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
int expectedWidth;
int expectedHeight;
if (sourceWidth >= 0 && sourceHeight >= 0
&& fixBitmapToRequestedDimensions && isKitKatOrGreater) {
expectedWidth = targetWidth;
expectedHeight = targetHeight;
} else {
float densityMultiplier = isScaling(options)
? (float) options.inTargetDensity / options.inDensity : 1f;
int sampleSize = options.inSampleSize;
int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);
int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);
expectedWidth = Math.round(downsampledWidth * densityMultiplier);
expectedHeight = Math.round(downsampledHeight * densityMultiplier);
}
//設定inBitmap
if (expectedWidth > 0 && expectedHeight > 0) {
setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
}
}
//BitmapFactory.Option配置完畢,呼叫decodeStream解析
Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
callbacks.onDecodeComplete(bitmapPool, downsampled);
Bitmap rotated = null;
if (downsampled != null) {
//重新設定density
downsampled.setDensity(displayMetrics.densityDpi);
//處理旋轉資訊
rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);
if (!downsampled.equals(rotated)) {
//新增進BitmapPool
bitmapPool.put(downsampled);
}
}
return rotated;
}
複製程式碼
decodeFromWrappedStreams()
方法首先呼叫getDimensions()
獲取輸入流圖片尺寸和mimeType
;- 獲取圖片的方向和旋轉角度;
- 確認目標控制元件的寬高,獲取圖片的ImageType;
- 呼叫
calculateScaling()
計算縮放資訊; - 呼叫
calculateConfig()
計算其他配置資訊; - 根據配置,呼叫
setInBitmap()
設定inBitmap; - 上訴流程完成
BitmapFactory.Option
配置,呼叫decodeStream()
解析輸入流; - 得到Bitmap,對Bitmap做最後的操作;
getDimensions()
private static int[] getDimensions(InputStream is, BitmapFactory.Options options,
DecodeCallbacks decodeCallbacks, BitmapPool bitmapPool) throws IOException {
//只解析Bounds
options.inJustDecodeBounds = true;
decodeStream(is, options, decodeCallbacks, bitmapPool);
//重置為false;
options.inJustDecodeBounds = false;
return new int[] { options.outWidth, options.outHeight };
}
複製程式碼
getDimensions()
使用options.inJustDecodeBounds = true
讀取輸入流圖片資訊;
計算縮放
calculateScaling()
private static void calculateScaling(
ImageType imageType,
InputStream is,
DecodeCallbacks decodeCallbacks,
BitmapPool bitmapPool,
DownsampleStrategy downsampleStrategy,
int degreesToRotate,
int sourceWidth,
int sourceHeight,
int targetWidth,
int targetHeight,
BitmapFactory.Options options) throws IOException {
//尺寸不能為0
if (sourceWidth <= 0 || sourceHeight <= 0) {
return;
}
final float exactScaleFactor;
//根據方向和角度獲取exactScaleFactor
if (degreesToRotate == 90 || degreesToRotate == 270) {
exactScaleFactor = downsampleStrategy.getScaleFactor(sourceHeight, sourceWidth,
targetWidth, targetHeight);
} else {
exactScaleFactor =
downsampleStrategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight);
}
//exactScaleFactor不能小於等於0
if (exactScaleFactor <= 0f) {
throw new IllegalArgumentException("");
}
//獲取SampleSizeRounding
SampleSizeRounding rounding = downsampleStrategy.getSampleSizeRounding(sourceWidth,
sourceHeight, targetWidth, targetHeight);
if (rounding == null) {
throw new IllegalArgumentException("Cannot round with null rounding");
}
//獲取Bitmap輸出寬高
int outWidth = round(exactScaleFactor * sourceWidth);
int outHeight = round(exactScaleFactor * sourceHeight);
//轉成int型別的factor
int widthScaleFactor = sourceWidth / outWidth;
int heightScaleFactor = sourceHeight / outHeight;
//根據SampleSizeRounding得到scaleFactor
int scaleFactor = rounding == SampleSizeRounding.MEMORY
? Math.max(widthScaleFactor, heightScaleFactor)
: Math.min(widthScaleFactor, heightScaleFactor);
int powerOfTwoSampleSize;
//不支援下采樣
if (Build.VERSION.SDK_INT <= 23
&& NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {
powerOfTwoSampleSize = 1;
} else {
//在scaleFactor再進行處理,保證是2的指數冪
powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));
if (rounding == SampleSizeRounding.MEMORY
&& powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
powerOfTwoSampleSize = powerOfTwoSampleSize << 1;
}
}
//設定到inSampleSize
options.inSampleSize = powerOfTwoSampleSize;
int powerOfTwoWidth;
int powerOfTwoHeight;
//針對不同圖片格式,重新計算取樣後的寬高
if (imageType == ImageType.JPEG) {
//libjpeg引擎最大采樣size=8,skia會進行二次取樣
int nativeScaling = Math.min(powerOfTwoSampleSize, 8);
powerOfTwoWidth = (int) Math.ceil(sourceWidth / (float) nativeScaling);
powerOfTwoHeight = (int) Math.ceil(sourceHeight / (float) nativeScaling);
//如大大於8,skia會對剩下的進行二次取樣計算邏輯
int secondaryScaling = powerOfTwoSampleSize / 8;
if (secondaryScaling > 0) {
powerOfTwoWidth = powerOfTwoWidth / secondaryScaling;
powerOfTwoHeight = powerOfTwoHeight / secondaryScaling;
}
} else if (imageType == ImageType.PNG || imageType == ImageType.PNG_A) {
powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);
powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
} else if (imageType == ImageType.WEBP || imageType == ImageType.WEBP_A) {
//不同版本取樣不同的計算方式round或者floor
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
powerOfTwoWidth = Math.round(sourceWidth / (float) powerOfTwoSampleSize);
powerOfTwoHeight = Math.round(sourceHeight / (float) powerOfTwoSampleSize);
} else {
powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);
powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
}
} else if (
sourceWidth % powerOfTwoSampleSize != 0 || sourceHeight % powerOfTwoSampleSize != 0) {
int[] dimensions = getDimensions(is, options, decodeCallbacks, bitmapPool);
powerOfTwoWidth = dimensions[0];
powerOfTwoHeight = dimensions[1];
} else {
powerOfTwoWidth = sourceWidth / powerOfTwoSampleSize;
powerOfTwoHeight = sourceHeight / powerOfTwoSampleSize;
}
//計算取樣後進行縮放的比例
double adjustedScaleFactor = downsampleStrategy.getScaleFactor(
powerOfTwoWidth, powerOfTwoHeight, targetWidth, targetHeight);
//計算inTargetDensity和inDensity進行縮放
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//這一塊計算方法沒有弄明白
options.inTargetDensity = adjustTargetDensityForError(adjustedScaleFactor);
options.inDensity = getDensityMultiplier(adjustedScaleFactor);
}
//設定inScaled
if (isScaling(options)){
options.inScaled = true;
} else {
options.inDensity = options.inTargetDensity = 0;
}
}
複製程式碼
calculateScaling程式碼看似複雜,但是仔細分析流程還是很清晰的,該方法主要計算BitmapFactory.Factory
的inSampleSize
和inTargetDensity
、inDensity
,inSampleSize
主要是進行向下取樣,取樣後自然會對圖片進行縮小,但是可能不滿足目標縮放比例,所以再配合inTargetDensity
、inDensity
進行二次縮放;主要針對兩個縮放變數powerOfTwoSampleSize
和adjustedScaleFactor
;具體流程分析:
流程一
- 計算
powerOfTwoSampleSize
過程:powerOfTwoSampleSize
是最終賦值給options.inSampleSize
的;
- 根據圖片角度不同,分別呼叫
downSampleStategy.getScaleFactor()
方法,得到exactScaleFactor
; - 通過
exactScaleFactor
計算出outWidth
和outHeight
,再重新計算widthScaleFactor
和heightScaleFactor
(ps:widthScaleFactor和heightScaleFactor是int型別,而exactScaleFactor是浮點型,結果是不一樣的); - 通過策略得到int型別的比例
scaleFactor
- 判斷是否支援下采樣,計算出最終的縮放比例
powerOfTwoSampleSize
- 賦值給options.inSampleSize
流程二
- 計算
adjustedScaleFactor
過程:adjustedScaleFactor
是最終計算options.inTargetDensity
和options.inDensity
的;
- 不同格式和版本的下采樣邏輯不同,Glide分別實現取樣邏輯的計算,然後生成取樣後的寬高尺寸
powerOfTwoWidth
和powerOfTwoHeight
; - 再此呼叫
downsampleStrategy.getScaleFactor()
得到縮放比adjustedScaleFactor
; - 呼叫
adjustTargetDensityForError()
和getDensityMultiplier()
得到inTargetDensity
和inDensity
並賦值; - 上面
inDensity
依賴options.inScaled = true
,最終還得判斷是否能夠進行scale;
計算硬體點陣圖/Bitmap.Config
calculateConfig()
private void calculateConfig(
InputStream is,
DecodeFormat format,
boolean isHardwareConfigAllowed,
boolean isExifOrientationRequired,
BitmapFactory.Options optionsWithScaling,
int targetWidth,
int targetHeight) {
//如果支援硬體點陣圖
if (hardwareConfigState.setHardwareConfigIfAllowed(
targetWidth,
targetHeight,
optionsWithScaling,
format,
isHardwareConfigAllowed,
isExifOrientationRequired)) {
return;
}
//這種情況,直接配置ARGB_8888
if (format == DecodeFormat.PREFER_ARGB_8888
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
optionsWithScaling.inPreferredConfig = Bitmap.Config.ARGB_8888;
return;
}
//是否有透明的通道
boolean hasAlpha = false;
try {
hasAlpha = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool).hasAlpha();
} catch (IOException e) {
}
//有透明的通道,需要配置成ARGB_8888
optionsWithScaling.inPreferredConfig =
hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
if (optionsWithScaling.inPreferredConfig == Config.RGB_565) {
optionsWithScaling.inDither = true;//565支援抖動介面
}
}
複製程式碼
calculateConfig()
主要是對BitmapFactory.Option
的硬體點陣圖和inPreferredConfig
進行計算;如果使用者配置8888直接配置,如果使用者配置565,還需要判斷是否有透明度通道,如果有透明度通道,依然採用8888解碼器;
硬體點陣圖
HardwareConfigState.java
boolean setHardwareConfigIfAllowed(
int targetWidth,
int targetHeight,
BitmapFactory.Options optionsWithScaling,
DecodeFormat decodeFormat,
boolean isHardwareConfigAllowed,
boolean isExifOrientationRequired) {
if (!isHardwareConfigAllowed
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.O
|| isExifOrientationRequired) {
Android O以上支援
return false;
}
//對尺寸有要求;
boolean result =
targetWidth >= MIN_HARDWARE_DIMENSION
&& targetHeight >= MIN_HARDWARE_DIMENSION
&& isFdSizeBelowHardwareLimit();
if (result) {
optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE;
optionsWithScaling.inMutable = false;
}
return result;
}
複製程式碼
硬體點陣圖Bitmap.Config.HARDWARE 是一種 Android O 新增的新的點陣圖格式。硬體點陣圖僅在視訊記憶體 (graphic memory) 裡儲存畫素資料,並對圖片僅在螢幕上繪製的場景做了優化。
###設定InBitmap
setInBitmap()
private static void setInBitmap(
BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
@Nullable Bitmap.Config expectedConfig = null;
// Avoid short circuiting, it appears to break on some devices.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//硬體點陣圖不設定
if (options.inPreferredConfig == Config.HARDWARE) {
return;
}
//inJustDecodeBoudes時候有可能可以獲取到
expectedConfig = options.outConfig;
}
if (expectedConfig == null) {
expectedConfig = options.inPreferredConfig;
}
options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
}
複製程式碼
設定成硬體點陣圖的不能進行inBitmap
操作,最終會設定options.inBitmap
,其中快取的點陣圖從bitmapPool中獲取;
解析輸出流
decodeStream()
萬事具體只差東風,設定完BitmapFactory.Options之後,正在解析輸入流的程式碼就在decodeStream()
:
private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options,
DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException {
if (options.inJustDecodeBounds) {
is.mark(MARK_POSITION);
} else {
callbacks.onObtainBounds();
}
int sourceWidth = options.outWidth;
int sourceHeight = options.outHeight;
String outMimeType = options.outMimeType;
final Bitmap result;
TransformationUtils.getBitmapDrawableLock().lock();
try {
//真正的解析呼叫
result = BitmapFactory.decodeStream(is, null, options);
} catch (IllegalArgumentException e) {
if (options.inBitmap != null) {
try {
//發生異常要做inBitmap回收
is.reset();
bitmapPool.put(options.inBitmap);
options.inBitmap = null;//不適用inBitmap
return decodeStream(is, options, callbacks, bitmapPool);
} catch (IOException resetException) {
throw bitmapAssertionException;
}
}
throw bitmapAssertionException;
} finally {
TransformationUtils.getBitmapDrawableLock().unlock();
}
if (options.inJustDecodeBounds) {
is.reset();
}
return result;
}
複製程式碼
decodeStream()
核心的方法是呼叫BitmapFactory.decodeStream(is, null, options)
做解析工作,剩下的程式碼都是對異常解析,如果發現異常時inBitmap
不為空,設定inBitmap
為null
並重試一次;
總結
本文主要是對Glide下采樣做了簡單介紹,從程式碼流程上分析,可以分為對BitmapFactory.Options的配置、呼叫BitmapFactory解析輸入流以及對Bitmap最後的處理三個步驟,其中Options配置階段在計算縮放上最為重要,其中在縮放因子計算階段,涉及native
以及libjpeg
底層的邏輯尤為重要,值得學習;