大尺寸圖片志衣,into 參數(shù)是 SimpleTarget岳服,應(yīng)用崩潰。
測試
-
如果 Target 是 ImageView
- xml 中布局寬高自適應(yīng)吆视,且沒有配置 override 參數(shù)典挑,加載內(nèi)存增加也就 3M 左右。
- 如果 xml 中指定了更小的寬高或配置了 override 參數(shù)啦吧,那么內(nèi)存會更小您觉。
-
如果 Target 不是 ImageView,比如 SimpleTarget
SimpleTarget 中設(shè)置了參數(shù)或者設(shè)置了 override授滓,根據(jù)尺寸的不同琳水,內(nèi)存增大不一,但基本在可控范圍般堆,10M 以內(nèi)在孝。
-
未在構(gòu)造時(shí)傳入指定尺寸或者 override
Glide.with(getApplicationContext()) .load(url) .asBitmap() .into(new SimpleTarget<Bitmap>() { @Override public void onResourceReady(Bitmap bitmap, GlideAnimation<? super Bitmap> glideAnimation) { // imageView.setImageBitmap(bitmap); } });
首先 into 方法將圖片加載到內(nèi)存中,然后回調(diào) onResourceReady 這個方法淮摔,可見 Java 層內(nèi)存飆升了 96M 左右浑玛,主要解碼圖片的操作。
屏幕快照 2019-03-04 上午11.16.10.png而假如再執(zhí)行
imageView.setImageBitmap(bitmap)
上噩咪,Graphics 也出現(xiàn)一個峰值顾彰,增加了近 100M屏幕快照 2019-03-04 上午11.26.56.png
主要的區(qū)別就在于 Target。對于 View胃碾,一般來說涨享,尺寸最大也就屏幕分辨率,所占內(nèi)存終究有個限制仆百,而不是 View厕隧,一些第三方的服務(wù)中的圖片多大完全不知道。
原因
Target 尺寸計(jì)算
into()
方法會執(zhí)行到 GenericRequest 類的 begin()
public void begin() {
// ...
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
// ...
}
如果通過 override()
方法傳入尺寸俄周,會直接進(jìn)入 onSizeReady()
吁讨,若未設(shè)置,Target 是 View 的話峦朗,會去獲取 View 顯示出來的尺寸
public void getSize(SizeReadyCallback cb) {
int currentWidth = getViewWidthOrParam();
int currentHeight = getViewHeightOrParam();
if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
} else {
if (!cbs.contains(cb)) {
cbs.add(cb);
}
if (layoutListener == null) {
final ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
}
}
}
而如果是 SimpleTarget
public SimpleTarget() {
this(SIZE_ORIGINAL, SIZE_ORIGINAL);
}
public SimpleTarget(int width, int height) {
this.width = width;
this.height = height;
}
public final void getSize(SizeReadyCallback cb) {
if (!Util.isValidDimensions(width, height)) {
throw new IllegalArgumentException("Width and height must both be > 0 or Target#SIZE_ORIGINAL, but given"
+ " width: " + width + " and height: " + height + ", either provide dimensions in the constructor"
+ " or call override()");
}
cb.onSizeReady(width, height);
}
可見如果 SimpleTarget 構(gòu)造時(shí)沒有傳尺寸參數(shù)建丧,寬高就是 SIZE_ORIGINAL,即 Integer 的最小值波势。最后也會執(zhí)行到 onSizeReady()
翎朱。
采樣壓縮
GenericRequest$onSizeReady() -> EngineRunnable$run() --> EngineRunnable$decodeFromSource() --> DecodeJob$decodeFromSourceData() --> GifBitmapWrapperResourceDecoder$decode() --> GifBitmapWrapperResourceDecoder$decodeBitmapWrapper() --> ImageVideoBitmapDecoder$decode() --> StreamBitmapDecoder$decode() --> Downsampler$decode()
@Override
public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) {
// 圖片真實(shí)尺寸,會先 inJustDecodeBounds 設(shè)為 true 獲取再重置 false
final int[] inDimens = getDimensions(invalidatingStream, bufferedStream, options);
final int inWidth = inDimens[0];
final int inHeight = inDimens[1];
// 圖片旋轉(zhuǎn)角度
final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight);
// 生成 Bitmap
final Bitmap downsampled =
downsampleWithSize(invalidatingStream, bufferedStream, options, pool, inWidth, inHeight, sampleSize,
decodeFormat);
}
// 根據(jù)原圖尺寸計(jì)算采樣率
private int getRoundedSampleSize(int degreesToRotate, int inWidth, int inHeight, int outWidth, int outHeight) {
int targetHeight = outHeight == Target.SIZE_ORIGINAL ? inHeight : outHeight;
int targetWidth = outWidth == Target.SIZE_ORIGINAL ? inWidth : outWidth;
final int exactSampleSize;
if (degreesToRotate == 90 || degreesToRotate == 270) {
// 如果有角度旋轉(zhuǎn)尺铣,要轉(zhuǎn)換寬高值
exactSampleSize = getSampleSize(inHeight, inWidth, targetWidth, targetHeight);
} else {
exactSampleSize = getSampleSize(inWidth, inHeight, targetWidth, targetHeight);
}
final int powerOfTwoSampleSize = exactSampleSize == 0 ? 0 : Integer.highestOneBit(exactSampleSize);
// 如果實(shí)際圖片小于設(shè)定尺寸拴曲,powerOfTwoSampleSize 是 0,采樣比是 1
return Math.max(1, powerOfTwoSampleSize);
}
int targetHeight = outHeight == Target.SIZE_ORIGINAL ? inHeight : outHeight;
在 SimpleTarget 方式中凛忿,outHeight 就是 Target.SIZE_ORIGINAL澈灼,這樣 targetWidth,targetHeight 就是圖片原尺寸店溢。而假設(shè)外界設(shè)置寬高為 500x400叁熔,那么 targetWidth 為 500,targetHeight 為 400逞怨。
其中 getSampleSize()
是抽象方法者疤,內(nèi)部有個靜態(tài)實(shí)例 AT_LEAST,此時(shí)用的就是它(StreamBitmapDecoder 初始化時(shí)傳的叠赦,具體邏輯未看)
public static final Downsampler AT_LEAST = new Downsampler() {
@Override
protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) {
// min{8688/400, 5792/500}=11
return Math.min(inHeight / outHeight, inWidth / outWidth);
}
// ...
};
因?yàn)?inSampleSize 需要是 2 的指數(shù)驹马,所以執(zhí)行 Integer.highestOneBit(exactSampleSize);
將二進(jìn)制最高位后面的全變成 0,這樣 11 就變成了 8除秀。
private Bitmap downsampleWithSize(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream,
BitmapFactory.Options options, BitmapPool pool, int inWidth, int inHeight, int sampleSize,
DecodeFormat decodeFormat) {
Bitmap.Config config = getConfig(is, decodeFormat);
options.inSampleSize = sampleSize; // 采樣率是 8 了
options.inPreferredConfig = config;
if ((options.inSampleSize == 1 || Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) && shouldUsePool(is)) {
// inWidth 原圖寬 5792斥扛,sampleSize 8,所以最后生成的圖片寬 742
int targetWidth = (int) Math.ceil(inWidth / (double) sampleSize);
// 高 1086
int targetHeight = (int) Math.ceil(inHeight / (double) sampleSize);
setInBitmap(options, pool.getDirty(targetWidth, targetHeight, config));
}
return decodeStream(is, bufferedStream, options);
}
可見有三種情況:
- SimpleTarget 未設(shè)置寬高规揪,加載原圖尺寸
- 設(shè)置的寬高比原圖尺寸還要大度苔,加載原圖尺寸
- 設(shè)置的寬高比原圖尺寸小,用原圖尺寸除以設(shè)置寬高暂吉,取最小值取整再向下取 2 的指數(shù)胖秒。因此最終獲得的圖片尺寸可能會比設(shè)置尺寸稍大缎患。
結(jié)論
Using Target.SIZE_ORIGINAL can be very inefficient or cause OOMs if your image sizes are large enough. As an alternative, You can also pass in a size to your Target’s constructor and provide those dimensions to the callback——Custom Targets
在 Glide 4 中 SimpleTarget 被標(biāo)記為過時(shí)的,并且多了一些注釋:
Always try to provide a size when using this class. Use {@link SimpleTarget#SimpleTarget(int, int)} whenever possible with values that are <em>not</em> {@link Target#SIZE_ORIGINAL}. Using {@link Target#SIZE_ORIGINAL} is unsafe if you're loading large images or are running your application on older or memory constrained devices because it can cause Glide to load very large images into memory. In some cases those images may throw {@link OutOfMemoryError} and in others they may exceed the texture limit for the device, which will prevent them from being rendered.
- 所以在使用 SimpleTarget 的時(shí)候一定要先通過 override 設(shè)置尺寸阎肝,或者構(gòu)造時(shí)傳入尺寸挤渔。
- 雖然實(shí)際圖片尺寸可能比設(shè)置尺寸更大,但這樣終究會有一個限制风题,限制在一定范圍內(nèi)判导。
- 假設(shè)要顯示的控件尺寸 20x20,圖片尺寸 80x80沛硅,沒有設(shè)置尺寸雖然不太可能導(dǎo)致 OOM眼刃,但終究也是對內(nèi)存不必要的浪費(fèi)。
centerCrop 和 fitCenter 對尺寸的影響
圖片生成后會返回到 DecodeJob 的 decodeFromSource()
方法
public Resource<Z> decodeFromSource() throws Exception {
Resource<T> decoded = decodeSource();
return transformEncodeAndTranscode(decoded);
}
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
long startTime = LogTime.getLogTime();
Resource<T> transformed = transform(decoded);
// ...
}
private Resource<T> transform(Resource<T> decoded) {
Resource<T> transformed = transformation.transform(decoded, width, height);
// ...
}
Transformation 是一個接口摇肌,默認(rèn)的 transformation 是 UnitTransformation擂红,它的 transform 就是直接返回資源
@Override
public Resource<T> transform(Resource<T> resource, int outWidth, int outHeight) {
// 如果沒有設(shè)置 centerCrop 或 fitCenter,圖片的寬高比會保持原樣
return resource;
}
而如果配置了 centerCrop()
的話朦蕴,這個 transformation 是 GifBitmapWrapperTransformation 實(shí)例篮条,從它的 transform 進(jìn)而執(zhí)行到 BitmapTransformation 的 transform()
方法,然后會到 CenterCrop 類的 transform吩抓,區(qū)別主要在這里涉茧,尺寸會變成 500x400 的。