aboutsummaryrefslogtreecommitdiff
path: root/library/src/main
diff options
context:
space:
mode:
authorSam Judd <judds@google.com>2014-10-30 18:45:59 -0700
committerSam Judd <judds@google.com>2014-11-02 14:07:31 -0800
commitdd737542dc2f6d58cfb1367929c8d3ebc1eeebf4 (patch)
tree8049827c020f3f9d59e73e8a8d08aaf4fb94ba86 /library/src/main
parentdeef4ae2607fcbd32caffc03e2490cbca9134643 (diff)
downloadglide-dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4.tar.gz
Add a back off to bitmap pool pre filling.
Diffstat (limited to 'library/src/main')
-rw-r--r--library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillIdleHandler.java102
-rw-r--r--library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillRunner.java160
-rw-r--r--library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFiller.java8
3 files changed, 165 insertions, 105 deletions
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillIdleHandler.java b/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillIdleHandler.java
deleted file mode 100644
index 3fef55c2..00000000
--- a/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillIdleHandler.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.bumptech.glide.load.engine.prefill;
-
-import android.graphics.Bitmap;
-import android.os.MessageQueue;
-import android.os.SystemClock;
-import android.util.Log;
-import com.bumptech.glide.load.Key;
-import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
-import com.bumptech.glide.load.engine.cache.MemoryCache;
-import com.bumptech.glide.load.resource.bitmap.BitmapResource;
-import com.bumptech.glide.util.Util;
-
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * A class that allocates {@link android.graphics.Bitmap Bitmaps} when the main thread runs out of messages so that the
- * {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} is pre-populated.
- */
-final class BitmapPreFillIdleHandler implements MessageQueue.IdleHandler {
- private static final String TAG = "PreFillIdleHandler";
- // Visisble for testing.
- static final long MAX_DURATION_MILLIS = 32;
-
- private static final Clock DEFAULT_CLOCK = new Clock();
-
- private final BitmapPool bitmapPool;
- private final MemoryCache memoryCache;
- private final PreFillQueue toPrefill;
- private final Clock clock;
- private final Set<PreFillType> seenAttributes =
- new HashSet<PreFillType>();
-
- private boolean isCancelled;
-
- public BitmapPreFillIdleHandler(BitmapPool bitmapPool, MemoryCache memoryCache,
- PreFillQueue allocationOrder) {
- this(bitmapPool, memoryCache, allocationOrder, DEFAULT_CLOCK);
- }
-
- // Visible for testing.
- BitmapPreFillIdleHandler(BitmapPool bitmapPool, MemoryCache memoryCache,
- PreFillQueue allocationOrder, Clock clock) {
- this.bitmapPool = bitmapPool;
- this.memoryCache = memoryCache;
- this.toPrefill = allocationOrder;
- this.clock = clock;
- }
-
- public void cancel() {
- isCancelled = true;
- }
-
- @Override
- public boolean queueIdle() {
- long start = clock.now();
- while (!toPrefill.isEmpty() && (clock.now() - start) < MAX_DURATION_MILLIS) {
- PreFillType toAllocate = toPrefill.remove();
- Bitmap bitmap = Bitmap.createBitmap(toAllocate.getWidth(), toAllocate.getHeight(),
- toAllocate.getConfig());
-
- // Don't over fill the memory cache to avoid evicting useful resources, but make sure it's not empty so
- // we use all available space.
- if ((memoryCache.getMaxSize() - memoryCache.getCurrentSize()) >= Util.getBitmapByteSize(bitmap)) {
- memoryCache.put(new UniqueKey(), BitmapResource.obtain(bitmap, bitmapPool));
- } else {
- if (seenAttributes.add(toAllocate)) {
- Bitmap fromPool = bitmapPool.get(toAllocate.getWidth(), toAllocate.getHeight(),
- toAllocate.getConfig());
- if (fromPool != null) {
- bitmapPool.put(fromPool);
- }
- }
- bitmapPool.put(bitmap);
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "allocated [" + toAllocate.getWidth() + "x" + toAllocate.getHeight() + "] "
- + toAllocate.getConfig() + " size: " + Util.getBitmapByteSize(bitmap));
- }
- }
-
- return !isCancelled && !toPrefill.isEmpty();
- }
-
- private static class UniqueKey implements Key {
-
- @Override
- public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
- // Do nothing.
- }
- }
-
- // Visible for testing.
- static class Clock {
- public long now() {
- return SystemClock.currentThreadTimeMillis();
- }
- }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillRunner.java b/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillRunner.java
new file mode 100644
index 00000000..20c71a55
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillRunner.java
@@ -0,0 +1,160 @@
+package com.bumptech.glide.load.engine.prefill;
+
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+import com.bumptech.glide.load.Key;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.engine.cache.MemoryCache;
+import com.bumptech.glide.load.resource.bitmap.BitmapResource;
+import com.bumptech.glide.util.Util;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class that allocates {@link android.graphics.Bitmap Bitmaps} to make sure that the
+ * {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} is pre-populated.
+ *
+ * <p>By posting to the main thread with backoffs, we try to avoid ANRs when the garbage collector gets into a state
+ * where a high percentage of {@link Bitmap} allocations trigger a stop the world GC. We try to detect whether or not a
+ * GC has occurred by only allowing our allocator to run for a limited number of milliseconds. Since the allocations
+ * themselves very fast, a GC is the most likely reason for a substantial delay. If we detect our allocator has run for
+ * more than our limit, we assume a GC has occurred, stop the current allocations, and try again after a delay.
+ */
+final class BitmapPreFillRunner implements Runnable {
+ private static final String TAG = "PreFillRunner";
+ private static final Clock DEFAULT_CLOCK = new Clock();
+
+ /**
+ * The maximum number of millis we can run before posting. Set to match and detect the duration of non concurrent
+ * GCs.
+ */
+ static final long MAX_DURATION_MS = 32;
+
+ /**
+ * The amount of time in ms we wait before continuing to allocate after the first GC is detected.
+ */
+ static final long INITIAL_BACKOFF_MS = 40;
+
+ /**
+ * The amount by which the current backoff time is multiplied each time we detect a GC.
+ */
+ static final int BACKOFF_RATIO = 4;
+
+ /**
+ * The maximum amount of time in ms we wait before continuing to allocate.
+ */
+ static final long MAX_BACKOFF_MS = TimeUnit.SECONDS.toMillis(1);
+
+ private final BitmapPool bitmapPool;
+ private final MemoryCache memoryCache;
+ private final PreFillQueue toPrefill;
+ private final Clock clock;
+ private final Set<PreFillType> seenTypes = new HashSet<PreFillType>();
+ private final Handler handler;
+
+ private long currentDelay = INITIAL_BACKOFF_MS;
+ private boolean isCancelled;
+
+ public BitmapPreFillRunner(BitmapPool bitmapPool, MemoryCache memoryCache, PreFillQueue allocationOrder) {
+ this(bitmapPool, memoryCache, allocationOrder, DEFAULT_CLOCK, new Handler(Looper.getMainLooper()));
+ }
+
+ // Visible for testing.
+ BitmapPreFillRunner(BitmapPool bitmapPool, MemoryCache memoryCache, PreFillQueue allocationOrder, Clock clock,
+ Handler handler) {
+ this.bitmapPool = bitmapPool;
+ this.memoryCache = memoryCache;
+ this.toPrefill = allocationOrder;
+ this.clock = clock;
+ this.handler = handler;
+ }
+
+ public void cancel() {
+ isCancelled = true;
+ }
+
+ /**
+ * Attempts to allocate {@link android.graphics.Bitmap}s and returns {@code true} if there are more
+ * {@link android.graphics.Bitmap}s to allocate and {@code false} otherwise.
+ */
+ private boolean allocate() {
+ long start = clock.now();
+ while (!toPrefill.isEmpty() && !isGcDetected(start)) {
+ PreFillType toAllocate = toPrefill.remove();
+ Bitmap bitmap = Bitmap.createBitmap(toAllocate.getWidth(), toAllocate.getHeight(),
+ toAllocate.getConfig());
+
+ // Don't over fill the memory cache to avoid evicting useful resources, but make sure it's not empty so
+ // we use all available space.
+ if (getFreeMemoryCacheBytes() >= Util.getBitmapByteSize(bitmap)) {
+ memoryCache.put(new UniqueKey(), BitmapResource.obtain(bitmap, bitmapPool));
+ } else {
+ addToBitmapPool(toAllocate, bitmap);
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "allocated [" + toAllocate.getWidth() + "x" + toAllocate.getHeight() + "] "
+ + toAllocate.getConfig() + " size: " + Util.getBitmapByteSize(bitmap));
+ }
+ }
+
+ return !isCancelled && !toPrefill.isEmpty();
+ }
+
+ private boolean isGcDetected(long startTimeMs) {
+ return clock.now() - startTimeMs >= MAX_DURATION_MS;
+ }
+
+ private int getFreeMemoryCacheBytes() {
+ return memoryCache.getMaxSize() - memoryCache.getCurrentSize();
+ }
+
+ private void addToBitmapPool(PreFillType toAllocate, Bitmap bitmap) {
+ // The pool may not move sizes to the front of the LRU on put. Do a get here to make sure the size we're adding
+ // is at the front of the queue so that the Bitmap we're adding won't be evicted immediately.
+ if (seenTypes.add(toAllocate)) {
+ Bitmap fromPool = bitmapPool.get(toAllocate.getWidth(), toAllocate.getHeight(),
+ toAllocate.getConfig());
+ if (fromPool != null) {
+ bitmapPool.put(fromPool);
+ }
+ }
+
+ bitmapPool.put(bitmap);
+ }
+
+ @Override
+ public void run() {
+ if (allocate()) {
+ handler.postDelayed(this, getNextDelay());
+ }
+ }
+
+ private long getNextDelay() {
+ long result = currentDelay;
+ currentDelay = Math.min(currentDelay * BACKOFF_RATIO, MAX_BACKOFF_MS);
+ return result;
+ }
+
+ private static class UniqueKey implements Key {
+
+ @Override
+ public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
+ // Do nothing.
+ }
+ }
+
+ // Visible for testing.
+ static class Clock {
+ public long now() {
+ return SystemClock.currentThreadTimeMillis();
+ }
+ }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFiller.java b/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFiller.java
index 4671a275..e4ca67df 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFiller.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFiller.java
@@ -1,6 +1,7 @@
package com.bumptech.glide.load.engine.prefill;
import android.graphics.Bitmap;
+import android.os.Handler;
import android.os.Looper;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
@@ -19,8 +20,9 @@ public final class BitmapPreFiller {
private final MemoryCache memoryCache;
private final BitmapPool bitmapPool;
private final DecodeFormat defaultFormat;
+ private final Handler handler = new Handler(Looper.getMainLooper());
- private BitmapPreFillIdleHandler current;
+ private BitmapPreFillRunner current;
public BitmapPreFiller(MemoryCache memoryCache, BitmapPool bitmapPool, DecodeFormat defaultFormat) {
this.memoryCache = memoryCache;
@@ -44,8 +46,8 @@ public final class BitmapPreFiller {
}
PreFillQueue allocationOrder = generateAllocationOrder(bitmapAttributes);
- current = new BitmapPreFillIdleHandler(bitmapPool, memoryCache, allocationOrder);
- Looper.myQueue().addIdleHandler(current);
+ current = new BitmapPreFillRunner(bitmapPool, memoryCache, allocationOrder);
+ handler.post(current);
}
// Visible for testing.