大尺寸图片,into 参数是 SimpleTarget,应用崩溃。
如果 Target 是 ImageView
如果 Target 不是 ImageView,比如 SimpleTarget
SimpleTarget 中设置了参数或者设置了 override,根据尺寸的不同,内存增大不一,但基本在可控范围,10M 以内。
未在构造时传入指定尺寸或者 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 方法将图片加载到内存中,然后回调 onResourceReady 这个方法,可见 Java 层内存飙升了 96M 左右,主要解码图片的操作。
// ...
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
// ...
如果通过 override()
方法传入尺寸,会直接进入 onSizeReady()
,若未设置,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 构造时没有传尺寸参数,宽高就是 SIZE_ORIGINAL,即 Integer 的最小值。最后也会执行到 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) {
// 图片真实尺寸,会先 inJustDecodeBounds 设为 true 获取再重置 false
final int[] inDimens = getDimensions(invalidatingStream, bufferedStream, options);
final int inWidth = inDimens[0];
final int inHeight = inDimens[1];
// 图片旋转角度
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);
// 根据原图尺寸计算采样率
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) {
// 如果有角度旋转,要转换宽高值
exactSampleSize = getSampleSize(inHeight, inWidth, targetWidth, targetHeight);
} else {
exactSampleSize = getSampleSize(inWidth, inHeight, targetWidth, targetHeight);
final int powerOfTwoSampleSize = exactSampleSize == 0 ? 0 : Integer.highestOneBit(exactSampleSize);
// 如果实际图片小于设定尺寸,powerOfTwoSampleSize 是 0,采样比是 1
return Math.max(1, powerOfTwoSampleSize);
int targetHeight = outHeight == Target.SIZE_ORIGINAL ? inHeight : outHeight;
在 SimpleTarget 方式中,outHeight 就是 Target.SIZE_ORIGINAL,这样 targetWidth,targetHeight 就是图片原尺寸。而假设外界设置宽高为 500x400,那么 targetWidth 为 500,targetHeight 为 400。
其中 getSampleSize()
是抽象方法,内部有个静态实例 AT_LEAST,此时用的就是它(StreamBitmapDecoder 初始化时传的,具体逻辑未看)
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);
// ...
因为 inSampleSize 需要是 2 的指数,所以执行 Integer.highestOneBit(exactSampleSize);
将二进制最高位后面的全变成 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 未设置宽高,加载原图尺寸
设置的宽高比原图尺寸还要大,加载原图尺寸
设置的宽高比原图尺寸小,用原图尺寸除以设置宽高,取最小值取整再向下取 2 的指数。因此最终获得的图片尺寸可能会比设置尺寸稍大。
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 被标记为过时的,并且多了一些注释:
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 的时候一定要先通过 override 设置尺寸,或者构造时传入尺寸。
虽然实际图片尺寸可能比设置尺寸更大,但这样终究会有一个限制,限制在一定范围内。
假设要显示的控件尺寸 20x20,图片尺寸 80x80,没有设置尺寸虽然不太可能导致 OOM,但终究也是对内存不必要的浪费。
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 是一个接口,默认的 transformation 是 UnitTransformation,它的 transform 就是直接返回资源
@Override
public Resource<T> transform(Resource<T> resource, int outWidth, int outHeight) {