提交 52079068 编写于 作者: S Sam Judd

Add a basic pre-filling api.

上级 98389d82
......@@ -147,6 +147,17 @@ public class LruBitmapPoolTest {
assertEquals(Math.round(MAX_SIZE * sizeMultiplier) - MAX_SIZE, strategy.numRemoves);
}
@Test
public void testCanGetCurrentMaxSize() {
assertEquals(MAX_SIZE, pool.getMaxSize());
}
@Test
public void testMaxSizeChangesAfterSizeMultiplier() {
pool.setSizeMultiplier(2);
assertEquals(2 * MAX_SIZE, pool.getMaxSize());
}
private void fillPool(LruBitmapPool pool, int fillCount) {
for (int i = 0; i < fillCount; i++) {
pool.put(createMutableBitmap());
......
......@@ -222,6 +222,50 @@ public class LruCacheTest {
verify(listener, never()).onItemRemoved(anyObject());
}
@Test
public void testGetMaxSizeReturnsCurrentMaxSizeOfCache() {
assertEquals(SIZE, cache.getMaxSize());
}
@Test
public void testGetMaxSizeChangesIfMaxSizeChanges() {
int multiplier = 2;
cache.setSizeMultiplier(multiplier);
assertEquals(SIZE * multiplier, cache.getMaxSize());
}
@Test
public void getCurrentSizeReturnsZeroForEmptyCache() {
assertEquals(0, cache.getCurrentSize());
}
@Test
public void testGetCurrentSizeIncreasesAsSizeIncreases() {
cache.put(getKey(), new Object());
assertEquals(1, cache.getCurrentSize());
cache.put(getKey(), new Object());
assertEquals(2, cache.getCurrentSize());
}
@Test
public void testGetCurrentSizeDoesNotChangeWhenSizeMultiplierChangesIfNoItemsAreEvicted() {
cache.put(getKey(), new Object());
assertEquals(1, cache.getCurrentSize());
cache.setSizeMultiplier(2);
assertEquals(1, cache.getCurrentSize());
}
@Test
public void testGetCurrentSizeChangesIfItemsAreEvictedWhenSizeMultiplierChanges() {
for (int i = 0; i < SIZE; i++) {
cache.put(getKey(), new Object());
}
assertEquals(SIZE, cache.getCurrentSize());
cache.setSizeMultiplier(0.5f);
assertEquals(SIZE / 2, cache.getCurrentSize());
}
private String getKey() {
currentKey += "1";
return currentKey;
......
package com.bumptech.glide.load.engine.prefill;
import android.graphics.Bitmap;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.engine.cache.MemoryCache;
import com.bumptech.glide.util.Util;
import org.hamcrest.core.CombinableMatcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
public class BitmapPreFillerAllocationTest {
private static final int DEFAULT_BITMAP_WIDTH = 100;
private static final int DEFAULT_BITMAP_HEIGHT = 50;
private static final Bitmap.Config DEFAULT_BITMAP_CONFIG = PreFillBitmapAttribute.DEFAULT_CONFIG;
private static final Bitmap DEFAULT_BITMAP =
Bitmap.createBitmap(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT, DEFAULT_BITMAP_CONFIG);
private static final int DEFAULT_BITMAP_SIZE = Util.getSize(DEFAULT_BITMAP);
private static final int DEFAULT_BITMAPS_IN_POOL = 10;
private static final int DEFAULT_BITMAPS_IN_CACHE = 10;
private static final int POOL_SIZE = DEFAULT_BITMAPS_IN_POOL * DEFAULT_BITMAP_SIZE;
private static final int CACHE_SIZE = DEFAULT_BITMAPS_IN_CACHE * DEFAULT_BITMAP_SIZE;
private BitmapPool pool;
private BitmapPreFiller bitmapPreFiller;
private MemoryCache cache;
@Before
public void setUp() {
pool = mock(BitmapPool.class);
when(pool.getMaxSize()).thenReturn(POOL_SIZE);
cache = mock(MemoryCache.class);
when(cache.getMaxSize()).thenReturn(CACHE_SIZE);
bitmapPreFiller = new BitmapPreFiller(cache, pool);
}
@Test
public void testAllocationOrderContainsEnoughSizesToFillPoolAndMemoryCache() {
PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(
new PreFillBitmapAttribute[]{
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT)
}
);
assertEquals(DEFAULT_BITMAPS_IN_POOL + DEFAULT_BITMAPS_IN_CACHE, allocationOrder.getSize());
}
@Test
public void testAllocationOrderThatDoesNotFitExactlyIntoGivenSizeRoundsDown() {
PreFillBitmapAttribute[] sizes = new PreFillBitmapAttribute[] {
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT),
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT),
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT / 2)
};
PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(sizes);
int byteSize = 0;
while (!allocationOrder.isEmpty()) {
PreFillBitmapAttribute current = allocationOrder.remove();
byteSize += Util.getBitmapPixelSize(current.getWidth(), current.getHeight(), current.getConfig());
}
int expectedSize = 0;
int maxSize = POOL_SIZE + CACHE_SIZE;
for (PreFillBitmapAttribute current : sizes) {
int currentSize = Util.getBitmapPixelSize(current.getWidth(), current.getHeight(), current.getConfig());
expectedSize += currentSize * (maxSize / (3 * currentSize));
}
assertEquals(expectedSize, byteSize);
}
@Test
public void testAllocationOrderDoesNotOverFillWithMultipleSizes() {
PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(
new PreFillBitmapAttribute[] {
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT),
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT),
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT / 2)
}
);
int byteSize = 0;
while (!allocationOrder.isEmpty()) {
PreFillBitmapAttribute current = allocationOrder.remove();
byteSize += Util.getBitmapPixelSize(current.getWidth(), current.getHeight(), current.getConfig());
}
assertThat(byteSize, lessThanOrEqualTo(POOL_SIZE + CACHE_SIZE));
}
@Test
public void testAllocationOrderDoesNotOverFillWithMultipleSizesAndWeights() {
PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(
new PreFillBitmapAttribute[]{
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT,
DEFAULT_BITMAP_CONFIG, 4),
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT),
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT / 3,
DEFAULT_BITMAP_CONFIG, 3)
}
);
int byteSize = 0;
while (!allocationOrder.isEmpty()) {
PreFillBitmapAttribute current = allocationOrder.remove();
byteSize += Util.getBitmapPixelSize(current.getWidth(), current.getHeight(), current.getConfig());
}
assertThat(byteSize, lessThanOrEqualTo(POOL_SIZE + CACHE_SIZE));
}
@Test
public void testAllocationOrderContainsSingleSizeIfSingleSizeIsProvided() {
PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(
new PreFillBitmapAttribute[] {
new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT)
}
);
while (!allocationOrder.isEmpty()) {
PreFillBitmapAttribute size = allocationOrder.remove();
assertEquals(DEFAULT_BITMAP_WIDTH, size.getWidth());
assertEquals(DEFAULT_BITMAP_HEIGHT, size.getHeight());
assertEquals(DEFAULT_BITMAP_CONFIG, size.getConfig());
}
}
@Test
public void testAllocationOrderSplitsEvenlyBetweenEqualSizesWithEqualWeights() {
PreFillBitmapAttribute smallWidth = new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT);
PreFillBitmapAttribute smallHeight = new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH,
DEFAULT_BITMAP_HEIGHT / 2);
PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(
new PreFillBitmapAttribute[] { smallWidth, smallHeight, }
);
int numSmallWidth = 0, numSmallHeight = 0;
while (!allocationOrder.isEmpty()) {
PreFillBitmapAttribute current = allocationOrder.remove();
if (smallWidth.equals(current)) {
numSmallWidth++;
} else if (smallHeight.equals(current)) {
numSmallHeight++;
} else {
fail("Unexpected size, size: " + current);
}
}
assertEquals(numSmallWidth, numSmallHeight);
}
@Test
public void testAllocationOrderSplitsByteSizeEvenlyBetweenUnEqualSizesWithEqualWeights() {
PreFillBitmapAttribute smallWidth = new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT);
PreFillBitmapAttribute normal = new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT);
PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(
new PreFillBitmapAttribute[] { smallWidth, normal }
);
int numSmallWidth = 0, numNormal = 0;
while (!allocationOrder.isEmpty()) {
PreFillBitmapAttribute current = allocationOrder.remove();
if (smallWidth.equals(current)) {
numSmallWidth++;
} else if (normal.equals(current)) {
numNormal++;
} else {
fail("Unexpected size, size: " + current);
}
}
assertEquals(2 * numNormal, numSmallWidth);
}
@Test
public void testAllocationOrderSplitsByteSizeUnevenlyBetweenEqualSizesWithUnequalWeights() {
PreFillBitmapAttribute doubleWeight = new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH / 2,
DEFAULT_BITMAP_HEIGHT, DEFAULT_BITMAP_CONFIG, 2);
PreFillBitmapAttribute normal = new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH, DEFAULT_BITMAP_HEIGHT / 2,
DEFAULT_BITMAP_CONFIG, 1);
PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(
new PreFillBitmapAttribute[] { doubleWeight, normal }
);
int numDoubleWeight = 0, numNormal = 0;
while (!allocationOrder.isEmpty()) {
PreFillBitmapAttribute current = allocationOrder.remove();
if (doubleWeight.equals(current)) {
numDoubleWeight++;
} else if (normal.equals(current)) {
numNormal++;
} else {
fail("Unexpected size, size: " + current);
}
}
assertEquals(2 * numNormal, numDoubleWeight);
}
@Test
public void testAllocationOrderRoundRobinsDifferentSizes() {
when(pool.getMaxSize()).thenReturn(DEFAULT_BITMAP_SIZE);
when(cache.getMaxSize()).thenReturn(DEFAULT_BITMAP_SIZE);
PreFillBitmapAttribute smallWidth = new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH / 2, DEFAULT_BITMAP_HEIGHT);
PreFillBitmapAttribute smallHeight = new PreFillBitmapAttribute(DEFAULT_BITMAP_WIDTH,
DEFAULT_BITMAP_HEIGHT / 2);
PreFillQueue allocationOrder = bitmapPreFiller.generateAllocationOrder(
new PreFillBitmapAttribute[] { smallWidth, smallHeight, }
);
List<PreFillBitmapAttribute> attributes = new ArrayList<PreFillBitmapAttribute>();
while (!allocationOrder.isEmpty()) {
attributes.add(allocationOrder.remove());
}
CombinableMatcher.CombinableEitherMatcher<Iterable<? extends PreFillBitmapAttribute>> either =
either(contains(smallWidth, smallHeight, smallWidth, smallHeight));
assertThat(attributes, either.or(contains(smallHeight, smallWidth, smallHeight, smallWidth)));
}
}
\ No newline at end of file
package com.bumptech.glide.load.engine.prefill;
import android.graphics.Bitmap;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.Resource;
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 org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
public class PreFillerHandlerTest {
private BitmapPreFillIdleHandler.Clock clock;
private BitmapPool pool;
private MemoryCache cache;
private List<Bitmap> addedBitmaps = new ArrayList<Bitmap>();
@Before
public void setUp() {
clock = mock(BitmapPreFillIdleHandler.Clock.class);
pool = mock(BitmapPool.class);
when(pool.put(any(Bitmap.class))).thenAnswer(new AddBitmapPoolAnswer(addedBitmaps));
cache = mock(MemoryCache.class);
when(cache.put(any(Key.class), any(Resource.class))).thenAnswer(new AddBitmapCacheAnswer(addedBitmaps));
}
private BitmapPreFillIdleHandler getHandler(Map<PreFillBitmapAttribute, Integer> allocationOrder) {
int total = 0;
for (Integer count : allocationOrder.values()) {
total += count;
}
return new BitmapPreFillIdleHandler(pool, cache, new PreFillQueue(allocationOrder), clock);
}
@Test
public void testAllocatesABitmapPerSizeInAllocationOrder() {
PreFillBitmapAttribute size = new PreFillBitmapAttribute(100, 100);
final int toAdd = 3;
Map<PreFillBitmapAttribute, Integer> allocationOrder = new HashMap<PreFillBitmapAttribute, Integer>();
allocationOrder.put(size, toAdd);
BitmapPreFillIdleHandler handler = getHandler(allocationOrder);
handler.queueIdle();
Bitmap expected = Bitmap.createBitmap(size.getWidth(), size.getHeight(), size.getConfig());
assertThat(addedBitmaps, contains(expected, expected, expected));
}
@Test
public void testAllocatesBitmapsInOrderGivenByAllocationOrder() {
PreFillBitmapAttribute smallWidth = new PreFillBitmapAttribute(50, 100);
PreFillBitmapAttribute smallHeight = new PreFillBitmapAttribute(100, 50);
PreFillBitmapAttribute[] expectedOrder = new PreFillBitmapAttribute[] {
smallWidth,
smallHeight,
smallWidth,
smallHeight,
};
HashMap<PreFillBitmapAttribute, Integer> allocationOrder = new HashMap<PreFillBitmapAttribute, Integer>();
allocationOrder.put(smallWidth, 2);
allocationOrder.put(smallHeight, 2);
BitmapPreFillIdleHandler handler = getHandler(allocationOrder);
handler.queueIdle();
Bitmap[] expectedBitmaps = new Bitmap[expectedOrder.length];
for (int i = 0; i < expectedBitmaps.length; i++) {
PreFillBitmapAttribute current = expectedOrder[i];
expectedBitmaps[i] = Bitmap.createBitmap(current.getWidth(), current.getHeight(), current.getConfig());
}
Bitmap current = addedBitmaps.get(0);
for (int i = 1; i < addedBitmaps.size(); i++) {
assertNotEquals(current, addedBitmaps.get(i));
current = addedBitmaps.get(i);
}
assertThat(addedBitmaps, hasSize(4));
}
@Test
public void testStopsAllocatingBitmapsUntilNextIdleCallIfAllocationsTakeLongerThanLimit() {
PreFillBitmapAttribute size = new PreFillBitmapAttribute(1, 1);
Map<PreFillBitmapAttribute, Integer> allocationOrder = new HashMap<PreFillBitmapAttribute, Integer>();
allocationOrder.put(size, 3);
when(clock.now()).thenReturn(0L).thenReturn(0L).thenReturn(BitmapPreFillIdleHandler.MAX_DURATION_MILLIS);
BitmapPreFillIdleHandler handler = getHandler(allocationOrder);
handler.queueIdle();
assertThat(addedBitmaps, hasSize(1));
handler.queueIdle();
assertThat(addedBitmaps, hasSize(3));
}
@Test
public void testPreFillHandlerReturnsFalseFromQueueIdleIfHasNoBitmapsToAllocate() {
BitmapPreFillIdleHandler handler = getHandler(new HashMap<PreFillBitmapAttribute, Integer>());
assertFalse(handler.queueIdle());
}
@Test
public void testPreFillHandlerReturnsTrueFromQueueIdleIfHasBitmapsToAllocate() {
PreFillBitmapAttribute size = new PreFillBitmapAttribute(1, 1);
Map<PreFillBitmapAttribute, Integer> allocationOrder = new HashMap<PreFillBitmapAttribute, Integer>();
allocationOrder.put(size, 2);
BitmapPreFillIdleHandler handler = getHandler(allocationOrder);
when(clock.now()).thenReturn(0L).thenReturn(0L).thenReturn(BitmapPreFillIdleHandler.MAX_DURATION_MILLIS);
assertTrue(handler.queueIdle());
}
@Test
public void testPreFillHandlerReturnsFalseFromQueueIdleIfHasBitmapsButIsCancelled() {
PreFillBitmapAttribute size = new PreFillBitmapAttribute(1, 1);
Map<PreFillBitmapAttribute, Integer> allocationOrder = new HashMap<PreFillBitmapAttribute, Integer>();
allocationOrder.put(size, 2);
BitmapPreFillIdleHandler handler = getHandler(allocationOrder);
when(clock.now()).thenReturn(0L).thenReturn(0L).thenReturn(BitmapPreFillIdleHandler.MAX_DURATION_MILLIS);
handler.cancel();
handler.queueIdle();
assertFalse(handler.queueIdle());
}
@Test
public void testAddsBitmapsToMemoryCacheIfMemoryCacheHasEnoughSpaceRemaining() {
Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
when(cache.getMaxSize()).thenReturn(Util.getSize(bitmap));
PreFillBitmapAttribute size = new PreFillBitmapAttribute(bitmap.getWidth(), bitmap.getHeight(),
bitmap.getConfig(), 1);
Map<PreFillBitmapAttribute, Integer> allocationOrder = new HashMap<PreFillBitmapAttribute, Integer>();
allocationOrder.put(size, 1);
BitmapPreFillIdleHandler handler = getHandler(allocationOrder);
handler.queueIdle();
verify(cache).put(any(Key.class), any(Resource.class));
verify(pool, never()).put(any(Bitmap.class));
assertThat(addedBitmaps, contains(bitmap));
}
@Test
public void testAddsBitmapsToBitmapPoolIfMemoryCacheIsFull() {
Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
when(cache.getMaxSize()).thenReturn(0);
PreFillBitmapAttribute size = new PreFillBitmapAttribute(bitmap.getWidth(), bitmap.getHeight(),
bitmap.getConfig(), 1);
Map<PreFillBitmapAttribute, Integer> allocationOrder = new HashMap<PreFillBitmapAttribute, Integer>();
allocationOrder.put(size, 1);
BitmapPreFillIdleHandler handler = getHandler(allocationOrder);
handler.queueIdle();
verify(cache, never()).put(any(Key.class), any(Resource.class));
verify(pool).put(eq(bitmap));
assertThat(addedBitmaps, contains(bitmap));
}
@Test
public void testAddsBitmapsToPoolIfMemoryCacheIsNotFullButCannotFitBitmap() {
Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
when(cache.getMaxSize()).thenReturn(Util.getSize(bitmap) / 2);
PreFillBitmapAttribute size = new PreFillBitmapAttribute(bitmap.getWidth(), bitmap.getHeight(),
bitmap.getConfig(), 1);
Map<PreFillBitmapAttribute, Integer> allocationOrder = new HashMap<PreFillBitmapAttribute, Integer>();
allocationOrder.put(size, 1);
BitmapPreFillIdleHandler handler = getHandler(allocationOrder);
handler.queueIdle();
verify(cache, never()).put(any(Key.class), any(Resource.class));
verify(pool).put(eq(bitmap));
assertThat(addedBitmaps, contains(bitmap));
}
private static class AddBitmapPoolAnswer implements Answer<Boolean> {
private List<Bitmap> bitmaps;
public AddBitmapPoolAnswer(List<Bitmap> bitmaps) {
this.bitmaps = bitmaps;
}
@Override
public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable {
Bitmap bitmap = (Bitmap) invocationOnMock.getArguments()[0];
bitmaps.add(bitmap);
return null;
}
}
private static class AddBitmapCacheAnswer implements Answer<Resource<?>> {
private List<Bitmap> bitmaps;
public AddBitmapCacheAnswer(List<Bitmap> bitmaps) {
this.bitmaps = bitmaps;
}
@Override
public Resource<?> answer(InvocationOnMock invocationOnMock) throws Throwable {
BitmapResource resource = (BitmapResource) invocationOnMock.getArguments()[1];
bitmaps.add(resource.get());
return null;
}
}
}
\ No newline at end of file
......@@ -15,9 +15,10 @@ import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import com.bumptech.glide.load.engine.Engine;
import com.bumptech.glide.load.engine.prefill.PreFillBitmapAttribute;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.engine.prefill.BitmapPreFiller;
import com.bumptech.glide.load.engine.cache.MemoryCache;
import com.bumptech.glide.load.model.GenericLoaderFactory;
import com.bumptech.glide.load.model.GlideUrl;
......@@ -91,6 +92,7 @@ public class Glide {
private final FitCenter bitmapFitCenter;
private final GifBitmapWrapperTransformation drawableFitCenter;
private final Handler mainHandler;
private final BitmapPreFiller bitmapPreFiller;
/**
* Returns a directory with a default name in the private cache directory of the application to use to store
......@@ -183,6 +185,7 @@ public class Glide {
this.bitmapPool = bitmapPool;
this.memoryCache = memoryCache;
mainHandler = new Handler(Looper.getMainLooper());
bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool);
dataLoadProviderRegistry = new DataLoadProviderRegistry();
......@@ -294,6 +297,36 @@ public class Glide {
return loaderFactory;
}
/**
* Pre-fills the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} using the given sizes.
*
* <p>
* Enough Bitmaps are added to completely fill the pool, so most or all of the Bitmaps currently in the pool will
* be evicted. Bitmaps are allocated according to the weights of the given sizes, where each size gets
* (weight / prefillWeightSum) percent of the pool to fill.
* </p>
*
* <p>
* Note - Pre-filling is done asynchronously using and {@link android.os.MessageQueue.IdleHandler}. Any
* currently running pre-fill will be cancelled and replaced by a call to this method.
* </p>
*
* <p>
* This method should be used with caution, overly aggressive pre-filling is substantially worse than not
* pre-filling at all. Pre-filling should only be started in onCreate to avoid constantly clearing and
* re-filling the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}. Rotation should be carefully
* considered as well. It may be worth calling this method only when no saved instance state exists so that
* pre-filling only happens when the Activity is first created, rather than on every rotation.
* </p>
*
* @param bitmapAttributes The list of {@link com.bumptech.glide.load.engine.prefill.PreFillBitmapAttribute}s
* representing individual sizes and configurations of {@link android.graphics.Bitmap}s to
* be pre-filled.
*/
public void preFillBitmapPool(PreFillBitmapAttribute... bitmapAttributes) {
bitmapPreFiller.preFill(bitmapAttributes);
}
/**
* Clears as much memory as possible.
*
......
......@@ -268,7 +268,7 @@ public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedLis
@Override
public boolean handleMessage(Message message) {
if (message.what == RECYCLE_RESOURCE) {
EngineResource resource = (EngineResource) message.obj;
Resource resource = (Resource) message.obj;
resource.recycle();
return true;
}
......@@ -279,8 +279,7 @@ public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedLis
private static class ResourceWeakReference extends WeakReference<EngineResource<?>> {
private final Key key;
public ResourceWeakReference(Key key, EngineResource<?> r,
ReferenceQueue<? super EngineResource<?>> q) {
public ResourceWeakReference(Key key, EngineResource<?> r, ReferenceQueue<? super EngineResource<?>> q) {
super(r, q);
this.key = key;
}
......@@ -292,7 +291,7 @@ public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedLis
private ReferenceQueue<EngineResource<?>> queue;
public RefQueueIdleHandler(Map<Key, WeakReference<EngineResource<?>>> activeResources,
ReferenceQueue<EngineResource<?>> queue) {
ReferenceQueue<EngineResource<?>> queue) {
this.activeResources = activeResources;
this.queue = queue;
}
......
......@@ -6,6 +6,12 @@ import android.graphics.Bitmap;
* An interface for a pool that allows users to reuse {@link android.graphics.Bitmap} objects.
*/
public interface BitmapPool {
/**
* Returns the current maximum size of the pool in bytes.
*/
public int getMaxSize();
/**
* Multiplies the initial size of the pool by the given multipler to dynamically and synchronously allow users to
* adjust the size of the pool.
......
......@@ -7,6 +7,11 @@ import android.graphics.Bitmap;
* {@link android.graphics.Bitmap Bitmap}s added to it and always returns {@code null} from get.
*/
public class BitmapPoolAdapter implements BitmapPool {
@Override
public int getMaxSize() {
return 0;
}
@Override
public void setSizeMultiplier(float sizeMultiplier) {
// Do nothing.
......
......@@ -48,6 +48,11 @@ public class LruBitmapPool implements BitmapPool {
this(maxSize, getDefaultStrategy());
}
@Override
public int getMaxSize() {
return maxSize;
}
@Override
public void setSizeMultiplier(float sizeMultiplier) {
maxSize = Math.round(initialMaxSize * sizeMultiplier);
......
......@@ -14,6 +14,16 @@ public interface MemoryCache {
public void onResourceRemoved(Resource<?> removed);
}
/**
* Returns the sum of the sizes of all the contents of the cache in bytes.
*/
public int getCurrentSize();
/**
* Returns the current maximum size in bytes of the cache.
*/
public int getMaxSize();
/**
* Adjust the maximum size of the cache by multiplying the original size of the cache by the given multiplier.
*
......
......@@ -10,6 +10,16 @@ public class MemoryCacheAdapter implements MemoryCache {
private ResourceRemovedListener listener;
@Override
public int getCurrentSize() {
return 0;
}
@Override
public int getMaxSize() {
return 0;
}
@Override
public void setSizeMultiplier(float multiplier) {
// Do nothing.
......
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;
/**
* 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 DefaultClock();
private final BitmapPool bitmapPool;
private final MemoryCache memoryCache;
private final PreFillQueue toPrefill;
private final Clock clock;
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) {
PreFillBitmapAttribute 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.getSize(bitmap)) {
memoryCache.put(new UniqueKey(), new BitmapResource(bitmap, bitmapPool));
} else {
bitmapPool.put(bitmap);
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "allocated [" + toAllocate.getWidth() + "x" + toAllocate.getHeight() + "] "
+ toAllocate.getConfig() + " size: " + Util.getSize(bitmap));
}
}
return !isCancelled && !toPrefill.isEmpty();
}
private static class UniqueKey implements Key {
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
// Do nothing.
}
}
// Visible for testing.
interface Clock {
public long now();
}
private static class DefaultClock implements Clock {
@Override
public long now() {
return SystemClock.currentThreadTimeMillis();
}
}
}
package com.bumptech.glide.load.engine.prefill;
import android.os.Looper;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.engine.cache.MemoryCache;
import com.bumptech.glide.util.Util;
import java.util.HashMap;
import java.util.Map;
/**
* A class for pre-filling {@link android.graphics.Bitmap Bitmaps} in a
* {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}.
*/
public final class BitmapPreFiller {
private final MemoryCache memoryCache;
private final BitmapPool bitmapPool;
private BitmapPreFillIdleHandler current;
public BitmapPreFiller(MemoryCache memoryCache, BitmapPool bitmapPool) {
this.memoryCache = memoryCache;
this.bitmapPool = bitmapPool;
}
public void preFill(PreFillBitmapAttribute... bitmapAttributes) {
if (current != null) {
current.cancel();
}
PreFillQueue allocationOrder = generateAllocationOrder(bitmapAttributes);
current = new BitmapPreFillIdleHandler(bitmapPool, memoryCache, allocationOrder);
Looper.myQueue().addIdleHandler(current);
}
// Visible for testing.
PreFillQueue generateAllocationOrder(PreFillBitmapAttribute[] preFillSizes) {
final int maxSize = (memoryCache.getMaxSize() - memoryCache.getCurrentSize()) + bitmapPool.getMaxSize();
int totalWeight = 0;
for (PreFillBitmapAttribute size : preFillSizes) {
totalWeight += size.getWeight();
}
final float bytesPerWeight = maxSize / (float) totalWeight;
Map<PreFillBitmapAttribute, Integer> attributeToCount = new HashMap<PreFillBitmapAttribute, Integer>();
for (PreFillBitmapAttribute size : preFillSizes) {
int bytesForSize = Math.round(bytesPerWeight * size.getWeight());
int bytesPerBitmap = getSizeInBytes(size);
int bitmapsForSize = bytesForSize / bytesPerBitmap;
attributeToCount.put(size, bitmapsForSize);
}
return new PreFillQueue(attributeToCount);
}
private static int getSizeInBytes(PreFillBitmapAttribute size) {
return Util.getBitmapPixelSize(size.getWidth(), size.getHeight(), size.getConfig());
}
}
package com.bumptech.glide.load.engine.prefill;
import android.graphics.Bitmap;
/**
* A container for a set of options used to pre-fill a {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}
* with {@link Bitmap Bitmaps} of a single size and configuration.
*/
public final class PreFillBitmapAttribute {
// Visible for testing.
static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.RGB_565;
private final int width;
private final int height;
private final Bitmap.Config config;
private final int weight;
/**
* Constructor for a single type of {@link android.graphics.Bitmap}.
*
* @see #PreFillBitmapAttribute(int, int, int)
* @see #PreFillBitmapAttribute(int, int, android.graphics.Bitmap.Config, int)
*
* @param width The width in pixels of the {@link android.graphics.Bitmap} to pre-fill.
* @param height The height in pixels of the {@link android.graphics.Bitmap} to pre-fill.
*/
public PreFillBitmapAttribute(int width, int height) {
this(width, height, 1);
}
/**
* Constructor for a single type of {@link android.graphics.Bitmap}.
*
* @see #PreFillBitmapAttribute(int, int)
* @see #PreFillBitmapAttribute(int, int, android.graphics.Bitmap.Config, int)
*
* @param width The width in pixels of the {@link android.graphics.Bitmap} to pre-fill.
* @param height The height in pixels of the {@link android.graphics.Bitmap} to pre-fill.
* @param weight An integer indicating how to balance pre-filling this size and configuration of
* {@link android.graphics.Bitmap} against any other sizes/configurations that may be being pre-filled.
*/
public PreFillBitmapAttribute(int width, int height, int weight) {
this(width, height, DEFAULT_CONFIG, weight);
}
/**
* Constructor for a single type of {@link android.graphics.Bitmap}.
*
* @see #PreFillBitmapAttribute(int, int)
* @see #PreFillBitmapAttribute(int, int, int)
*
* @param width The width in pixels of the {@link android.graphics.Bitmap Bitmaps} to
* pre-fill.
* @param height The height in pixels of the {@link android.graphics.Bitmap Bitmaps} to
* pre-fill.
* @param config The {@link android.graphics.Bitmap.Config} of the {@link android.graphics.Bitmap Bitmaps} to
* pre-fill.
* @param weight An integer indicating how to balance pre-filling this size and configuration of
* {@link android.graphics.Bitmap} against any other sizes/configurations that may be being pre-filled.
*/
public PreFillBitmapAttribute(int width, int height, Bitmap.Config config, int weight) {
this.width = width;
this.height = height;
this.config = config;
this.weight = weight;
}
/**
* Returns the width in pixels of the {@link android.graphics.Bitmap Bitmaps}.
*/
public int getWidth() {
return width;
}
/**
* Returns the height in pixels of the {@link android.graphics.Bitmap Bitmaps}.
*/
public int getHeight() {
return height;
}
/**
* Returns the {@link android.graphics.Bitmap.Config} of the {@link android.graphics.Bitmap Bitmaps}.
*/
public Bitmap.Config getConfig() {
return config;
}
/**
* Returns the weight of the {@link android.graphics.Bitmap Bitmaps} of this type.
*/
public int getWeight() {
return weight;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o == null || getClass() != o.getClass()) {
return false;
}
PreFillBitmapAttribute size = (PreFillBitmapAttribute) o;
return height == size.height
&& weight == size.weight
&& width == size.width
&& config == size.config;
}
@Override
public int hashCode() {
int result = width;
result = 31 * result + height;
result = 31 * result + config.hashCode();
result = 31 * result + weight;
return result;
}
@Override
public String toString() {
return "PreFillSize{"
+ "width=" + width
+ ", height=" + height
+ ", config=" + config
+ ", weight=" + weight
+ '}';
}
}
package com.bumptech.glide.load.engine.prefill;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
final class PreFillQueue {
private final Map<PreFillBitmapAttribute, Integer> bitmapsPerType;
private final List<PreFillBitmapAttribute> keyList;
private int bitmapsRemaining;
private int keyIndex;
public PreFillQueue(Map<PreFillBitmapAttribute, Integer> bitmapsPerType) {
this.bitmapsPerType = bitmapsPerType;
// We don't particularly care about the initial order.
keyList = new ArrayList<PreFillBitmapAttribute>(bitmapsPerType.keySet());
for (Integer count : bitmapsPerType.values()) {
bitmapsRemaining += count;
}
}
public PreFillBitmapAttribute remove() {
PreFillBitmapAttribute result = keyList.get(keyIndex);
Integer countForResult = bitmapsPerType.get(result);
if (countForResult == 1) {
bitmapsPerType.remove(result);
keyList.remove(keyIndex);
} else {
bitmapsPerType.put(result, countForResult - 1);
}
bitmapsRemaining--;
// Avoid divide by 0.
keyIndex = keyList.isEmpty() ? 0 : (keyIndex + 1) % keyList.size();
return result;
}
public int getSize() {
return bitmapsRemaining;
}
public boolean isEmpty() {
return bitmapsRemaining == 0;
}
}
......@@ -61,6 +61,13 @@ public class LruCache<T, Y> {
// optional override
}
/**
* Returns the current maximum size of the cache in bytes.
*/
public int getMaxSize() {
return maxSize;
}
/**
* Returns the sum of the sizes of all items in the cache.
*/
......
......@@ -2,6 +2,7 @@ package com.bumptech.glide.samples.flickr;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
......@@ -25,6 +26,7 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.prefill.PreFillBitmapAttribute;
import com.bumptech.glide.request.FutureTarget;
import com.bumptech.glide.samples.flickr.api.Api;
import com.bumptech.glide.samples.flickr.api.Photo;
......@@ -154,6 +156,23 @@ public class FlickrSearchActivity extends ActionBarActivity {
executeSearch(savedSearchString);
}
}
final Resources res = getResources();
int smallGridSize = res.getDimensionPixelSize(R.dimen.small_photo_side);
int mediumGridSize = res.getDimensionPixelSize(R.dimen.medium_photo_side);
int listHeightSize = res.getDimensionPixelSize(R.dimen.flickr_list_item_height);
int screenWidth = getScreenWidth();
// Weight values determined experimentally by measuring the number of incurred GCs while scrolling through
// the various photo grids/lists.
Glide.get(this).preFillBitmapPool(
new PreFillBitmapAttribute(smallGridSize, smallGridSize, 1),
new PreFillBitmapAttribute(mediumGridSize, mediumGridSize, 1),
new PreFillBitmapAttribute(screenWidth / 2, listHeightSize, 6));
}
private int getScreenWidth() {
return getResources().getDisplayMetrics().widthPixels;
}
@Override
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册