1dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddpackage com.bumptech.glide.load.engine.prefill;
2dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
3dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport android.graphics.Bitmap;
4dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport android.os.Handler;
5dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport android.os.Looper;
6dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport android.os.SystemClock;
7dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport android.util.Log;
8f7a6d65cf7c1a41908dd48e0dab68ee5b881387eSam Judd
9dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport com.bumptech.glide.load.Key;
10dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
11dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport com.bumptech.glide.load.engine.cache.MemoryCache;
12dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport com.bumptech.glide.load.resource.bitmap.BitmapResource;
13dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport com.bumptech.glide.util.Util;
14dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
15dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport java.io.UnsupportedEncodingException;
16dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport java.security.MessageDigest;
17dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport java.util.HashSet;
18dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport java.util.Set;
19dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddimport java.util.concurrent.TimeUnit;
20dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
21dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd/**
22dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd * A class that allocates {@link android.graphics.Bitmap Bitmaps} to make sure that the
23dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd * {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} is pre-populated.
24dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd *
25dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd * <p>By posting to the main thread with backoffs, we try to avoid ANRs when the garbage collector gets into a state
26dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd * where a high percentage of {@link Bitmap} allocations trigger a stop the world GC. We try to detect whether or not a
27dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd * GC has occurred by only allowing our allocator to run for a limited number of milliseconds. Since the allocations
28dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd * themselves very fast, a GC is the most likely reason for a substantial delay. If we detect our allocator has run for
29dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd * more than our limit, we assume a GC has occurred, stop the current allocations, and try again after a delay.
30dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd */
31dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Juddfinal class BitmapPreFillRunner implements Runnable {
32dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private static final String TAG = "PreFillRunner";
33dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private static final Clock DEFAULT_CLOCK = new Clock();
34dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
35dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    /**
36dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     * The maximum number of millis we can run before posting. Set to match and detect the duration of non concurrent
37dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     * GCs.
38dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     */
39dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    static final long MAX_DURATION_MS = 32;
40dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
41dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    /**
42dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     * The amount of time in ms we wait before continuing to allocate after the first GC is detected.
43dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     */
44dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    static final long INITIAL_BACKOFF_MS = 40;
45dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
46dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    /**
47dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     * The amount by which the current backoff time is multiplied each time we detect a GC.
48dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     */
49dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    static final int BACKOFF_RATIO = 4;
50dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
51dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    /**
52dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     * The maximum amount of time in ms we wait before continuing to allocate.
53dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     */
54dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    static final long MAX_BACKOFF_MS = TimeUnit.SECONDS.toMillis(1);
55dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
56dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private final BitmapPool bitmapPool;
57dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private final MemoryCache memoryCache;
58dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private final PreFillQueue toPrefill;
59dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private final Clock clock;
60dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private final Set<PreFillType> seenTypes = new HashSet<PreFillType>();
61dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private final Handler handler;
62dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
63dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private long currentDelay = INITIAL_BACKOFF_MS;
64dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private boolean isCancelled;
65dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
66dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    public BitmapPreFillRunner(BitmapPool bitmapPool, MemoryCache memoryCache, PreFillQueue allocationOrder) {
67dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        this(bitmapPool, memoryCache, allocationOrder, DEFAULT_CLOCK, new Handler(Looper.getMainLooper()));
68dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
69dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
70dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    // Visible for testing.
71dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    BitmapPreFillRunner(BitmapPool bitmapPool, MemoryCache memoryCache, PreFillQueue allocationOrder, Clock clock,
72dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            Handler handler) {
73dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        this.bitmapPool = bitmapPool;
74dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        this.memoryCache = memoryCache;
75dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        this.toPrefill = allocationOrder;
76dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        this.clock = clock;
77dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        this.handler = handler;
78dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
79dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
80dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    public void cancel() {
81dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        isCancelled = true;
82dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
83dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
84dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    /**
85dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     * Attempts to allocate {@link android.graphics.Bitmap}s and returns {@code true} if there are more
86dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     * {@link android.graphics.Bitmap}s to allocate and {@code false} otherwise.
87dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd     */
88dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private boolean allocate() {
89dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        long start = clock.now();
90dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        while (!toPrefill.isEmpty() && !isGcDetected(start)) {
91dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            PreFillType toAllocate = toPrefill.remove();
92dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            Bitmap bitmap = Bitmap.createBitmap(toAllocate.getWidth(), toAllocate.getHeight(),
93dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd                    toAllocate.getConfig());
94dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
95dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            // Don't over fill the memory cache to avoid evicting useful resources, but make sure it's not empty so
96dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            // we use all available space.
97dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            if (getFreeMemoryCacheBytes() >= Util.getBitmapByteSize(bitmap)) {
98dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd                memoryCache.put(new UniqueKey(), BitmapResource.obtain(bitmap, bitmapPool));
99dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            } else {
100dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd                addToBitmapPool(toAllocate, bitmap);
101dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            }
102dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
103dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            if (Log.isLoggable(TAG, Log.DEBUG)) {
104dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd                Log.d(TAG, "allocated [" + toAllocate.getWidth() + "x" + toAllocate.getHeight() + "] "
105dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd                        + toAllocate.getConfig() + " size: " + Util.getBitmapByteSize(bitmap));
106dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            }
107dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        }
108dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
109dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        return !isCancelled && !toPrefill.isEmpty();
110dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
111dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
112dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private boolean isGcDetected(long startTimeMs) {
113dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        return clock.now() - startTimeMs >= MAX_DURATION_MS;
114dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
115dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
116dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private int getFreeMemoryCacheBytes() {
117dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        return memoryCache.getMaxSize() - memoryCache.getCurrentSize();
118dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
119dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
120dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private void addToBitmapPool(PreFillType toAllocate, Bitmap bitmap) {
121dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        // 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
122dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        // is at the front of the queue so that the Bitmap we're adding won't be evicted immediately.
123dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        if (seenTypes.add(toAllocate)) {
124dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd          Bitmap fromPool = bitmapPool.get(toAllocate.getWidth(), toAllocate.getHeight(),
125dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd              toAllocate.getConfig());
126dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            if (fromPool != null) {
127dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd                bitmapPool.put(fromPool);
128dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            }
129dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        }
130dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
131dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        bitmapPool.put(bitmap);
132dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
133dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
134dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    @Override
135dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    public void run() {
136dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        if (allocate()) {
137dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            handler.postDelayed(this, getNextDelay());
138dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        }
139dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
140dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
141dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private long getNextDelay() {
142dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        long result = currentDelay;
143dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        currentDelay = Math.min(currentDelay * BACKOFF_RATIO, MAX_BACKOFF_MS);
144dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        return result;
145dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
146dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
147dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    private static class UniqueKey implements Key {
148dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
149dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        @Override
150dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
151dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            // Do nothing.
152dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        }
153dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
154dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd
155dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    // Visible for testing.
156dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    static class Clock {
157dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        public long now() {
158dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd            return SystemClock.currentThreadTimeMillis();
159dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd        }
160dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd    }
161dd737542dc2f6d58cfb1367929c8d3ebc1eeebf4Sam Judd}
162