From 2007440c380df454db15f83b019a5b4c55ca4b72 Mon Sep 17 00:00:00 2001 From: Sam Judd Date: Mon, 3 Nov 2014 18:46:27 -0800 Subject: Add API to fix mark limit/buffer size. Fixes #225. --- .../load/engine/bitmap_recycle/LruBitmapPool.java | 4 ++++ .../glide/load/resource/bitmap/Downsampler.java | 20 ++++++++++------ .../bitmap/RecyclableBufferedInputStream.java | 27 ++++++++++++++++++---- .../glide/util/ExceptionCatchingInputStream.java | 12 +++++++--- 4 files changed, 48 insertions(+), 15 deletions(-) (limited to 'library/src/main/java/com') diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java index 8c5947bd..79d8c6c4 100644 --- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java +++ b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java @@ -63,6 +63,10 @@ public class LruBitmapPool implements BitmapPool { @Override public synchronized boolean put(Bitmap bitmap) { if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Reject bitmap from pool=" + strategy.logBitmap(bitmap) + " is mutable=" + + bitmap.isMutable()); + } return false; } diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java index f5e17e95..904a7c45 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java @@ -105,6 +105,7 @@ public abstract class Downsampler implements BitmapDecoder { final byte[] bytesForOptions = byteArrayPool.getBytes(); final byte[] bytesForStream = byteArrayPool.getBytes(); final BitmapFactory.Options options = getDefaultOptions(); + // TODO(#126): when the framework handles exceptions better, consider removing. final ExceptionCatchingInputStream stream = ExceptionCatchingInputStream.obtain(new RecyclableBufferedInputStream(is, bytesForStream)); @@ -137,9 +138,10 @@ public abstract class Downsampler implements BitmapDecoder { final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight); final Bitmap downsampled = - downsampleWithSize(stream, options, pool, inWidth, inHeight, sampleSize, decodeFormat); + downsampleWithSize(stream, options, pool, inWidth, inHeight, sampleSize, + decodeFormat); - // BitmapDecoder swallows exceptions during decodes and in some cases when inBitmap is non null, may catch + // BitmapFactory swallows exceptions during decodes and in some cases when inBitmap is non null, may catch // and log a stack trace but still return a non null bitmap. To avoid displaying partially decoded bitmaps, // we catch exceptions reading from the stream in our ExceptionCatchingInputStream and throw them here. final Exception streamException = stream.getException(); @@ -185,8 +187,8 @@ public abstract class Downsampler implements BitmapDecoder { return Math.max(1, powerOfTwoSampleSize); } - protected Bitmap downsampleWithSize(InputStream is, BitmapFactory.Options options, - BitmapPool pool, int inWidth, int inHeight, int sampleSize, DecodeFormat decodeFormat) { + private Bitmap downsampleWithSize(ExceptionCatchingInputStream is, 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. Bitmap.Config config = getConfig(is, decodeFormat); options.inSampleSize = sampleSize; @@ -280,15 +282,14 @@ public abstract class Downsampler implements BitmapDecoder { * android.graphics.BitmapFactory.Options)}. * @return an array containing the dimensions of the image in the form {width, height}. */ - public int[] getDimensions(InputStream is, BitmapFactory.Options options) { + public int[] getDimensions(ExceptionCatchingInputStream is, BitmapFactory.Options options) { options.inJustDecodeBounds = true; decodeStream(is, options); options.inJustDecodeBounds = false; return new int[] { options.outWidth, options.outHeight }; } - - private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options) { + private static Bitmap decodeStream(ExceptionCatchingInputStream is, BitmapFactory.Options options) { if (options.inJustDecodeBounds) { // This is large, but jpeg headers are not size bounded so we need something large enough to minimize // the possibility of not being able to fit enough of the header in the buffer to get the image size so @@ -296,6 +297,11 @@ public abstract class Downsampler implements BitmapDecoder { // original size each time we use up the buffer space without passing the mark so this is a maximum // bound on the buffer size, not a default. Most of the time we won't go past our pre-allocated 16kb. is.mark(MARK_POSITION); + } else { + // Once we've read the image header, we no longer need to allow the buffer to expand in size. To avoid + // unnecessary allocations reading image data, we fix the mark limit so that it is no larger than our + // current buffer size here. See issue #225. + is.fixMarkLimit(); } final Bitmap result = BitmapFactory.decodeStream(is, null, options); diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java index b0811526..8699022b 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java @@ -17,6 +17,8 @@ package com.bumptech.glide.load.resource.bitmap; * limitations under the License. */ +import android.util.Log; + import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; @@ -36,32 +38,33 @@ import java.io.InputStream; * */ public class RecyclableBufferedInputStream extends FilterInputStream { + private static final String TAG = "BufferedIs"; /** * The buffer containing the current bytes read from the target InputStream. */ - protected volatile byte[] buf; + private volatile byte[] buf; /** * The total number of bytes inside the byte array {@code buf}. */ - protected int count; + private int count; /** * The current limit, which when passed, invalidates the current mark. */ - protected int marklimit; + private int marklimit; /** * The currently marked position. -1 indicates no mark has been set or the * mark has been invalidated. */ - protected int markpos = -1; + private int markpos = -1; /** * The current position within the byte array {@code buf}. */ - protected int pos; + private int pos; public RecyclableBufferedInputStream(InputStream in, byte[] buffer) { super(in); @@ -94,6 +97,17 @@ public class RecyclableBufferedInputStream extends FilterInputStream { throw new IOException("BufferedInputStream is closed"); } + /** + * Reduces the mark limit to match the current buffer length to prevent the buffer from + * continuing to increase in size. + * + *

Subsequent calls to {@link #mark(int)} will be obeyed and may cause the buffer size + * to increase. + */ + public synchronized void fixMarkLimit() { + marklimit = buf.length; + } + /** * Closes this stream. The source stream is closed and any resources * associated with it are released. @@ -134,6 +148,9 @@ public class RecyclableBufferedInputStream extends FilterInputStream { if (newLength > marklimit) { newLength = marklimit; } + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "allocate buffer of length: " + newLength); + } byte[] newbuf = new byte[newLength]; System.arraycopy(localBuf, 0, newbuf, 0, localBuf.length); // Reassign buf, which will invalidate any local references diff --git a/library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java b/library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java index 12df71c3..344a2894 100644 --- a/library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java +++ b/library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java @@ -1,5 +1,7 @@ package com.bumptech.glide.util; +import com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream; + import java.io.IOException; import java.io.InputStream; import java.util.Queue; @@ -15,10 +17,10 @@ public class ExceptionCatchingInputStream extends InputStream { private static final Queue QUEUE = Util.createQueue(0); - private InputStream wrapped; + private RecyclableBufferedInputStream wrapped; private IOException exception; - public static ExceptionCatchingInputStream obtain(InputStream toWrap) { + public static ExceptionCatchingInputStream obtain(RecyclableBufferedInputStream toWrap) { ExceptionCatchingInputStream result; synchronized (QUEUE) { result = QUEUE.poll(); @@ -41,7 +43,7 @@ public class ExceptionCatchingInputStream extends InputStream { // Do nothing. } - void setInputStream(InputStream toWrap) { + void setInputStream(RecyclableBufferedInputStream toWrap) { wrapped = toWrap; } @@ -118,6 +120,10 @@ public class ExceptionCatchingInputStream extends InputStream { return result; } + public void fixMarkLimit() { + wrapped.fixMarkLimit(); + } + public IOException getException() { return exception; } -- cgit v1.2.3