Skip to content

Commit

Permalink
Add a size based pool for api 19+
Browse files Browse the repository at this point in the history
Fixes #34
  • Loading branch information
sjudd committed Jan 21, 2014
1 parent e81c2c3 commit d5ddd93
Show file tree
Hide file tree
Showing 14 changed files with 891 additions and 359 deletions.
2 changes: 2 additions & 0 deletions library/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
tests/ant.properties
tests/local.properties
tests/gen/**/*
tests/bin
libs/volley.jar

1 change: 1 addition & 0 deletions library/lint.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
<issue id="AllowBackup" severity="ignore" />
<issue id="InlinedApi">
<ignore path="src/com/bumptech/glide/resize/cache/LruMemoryCache.java" />
<ignore path="src/com/bumptech/glide/resize/bitmap_recycle/LruBitmapPool.java" />
</issue>
</lint>
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.bumptech.glide.resize.bitmap_recycle;

import android.graphics.Bitmap;

class AttributeStrategy implements LruPoolStrategy {
private final KeyPool keyPool = new KeyPool();
private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<Key, Bitmap>();

public void put(Bitmap bitmap) {
final Key key = keyPool.get(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());

groupedMap.put(key, bitmap);
}

@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
final Key key = keyPool.get(width, height, config);

return groupedMap.get(key);
}

@Override
public Bitmap removeLast() {
return groupedMap.removeLast();
}

@Override
public String logBitmap(Bitmap bitmap) {
return getBitmapString(bitmap);
}

@Override
public String logBitmap(int width, int height, Bitmap.Config config) {
return getBitmapString(width, height, config);
}

@Override
public int getSize(Bitmap bitmap) {
return bitmap.getHeight() * bitmap.getRowBytes();
}

@Override
public String toString() {
return "AttributeStrategy:\n " + groupedMap;
}

private static String getBitmapString(Bitmap bitmap) {
return getBitmapString(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
}

private static String getBitmapString(int width, int height, Bitmap.Config config) {
return "[" + width + "x" + height + "], " + config;
}

private static class KeyPool extends BaseKeyPool<Key> {
public Key get(int width, int height, Bitmap.Config config) {
Key result = get();
result.init(width, height, config);
return result;
}

@Override
protected Key create() {
return new Key(this);
}
}

private static class Key implements Poolable {
private final KeyPool pool;
private int width;
private int height;
// Config can be null :(
private Bitmap.Config config;

public Key(KeyPool pool) {
this.pool = pool;
}

public void init(int width, int height, Bitmap.Config config) {
this.width = width;
this.height = height;
this.config = config;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Key key = (Key) o;

if (height != key.height) return false;
if (width != key.width) return false;
if (config != key.config) return false;

return true;
}

@Override
public int hashCode() {
int result = width;
result = 31 * result + height;
result = 31 * result + (config != null ? config.hashCode() : 0);
return result;
}

@Override
public String toString() {
return getBitmapString(width, height, config);
}

@Override
public void offer() {
pool.offer(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.bumptech.glide.resize.bitmap_recycle;

import android.os.Build;

import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.Queue;

abstract class BaseKeyPool<T extends Poolable> {
private static final int MAX_SIZE = 20;
private final Queue<T> keyPool;

public BaseKeyPool() {
if (Build.VERSION.SDK_INT >= 9) {
keyPool = new ArrayDeque<T>(MAX_SIZE);
} else {
keyPool = new LinkedList<T>();
}
}

protected T get() {
T result = keyPool.poll();
if (result == null) {
result = create();
}
return result;
}

public void offer(T key) {
if (keyPool.size() < MAX_SIZE) {
keyPool.offer(key);
}
}

protected abstract T create();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.bumptech.glide.resize.bitmap_recycle;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Similar to {@link java.util.LinkedHashMap} when access ordered except that it is access ordered on groups
* of bitmaps rather than individual objects. The idea is to be able to find the LRU bitmap size, rather than the
* LRU bitmap object. We can then remove bitmaps from the least recently used size of bitmap when we need to
* reduce our cache size.
*
* For the purposes of the LRU, we count gets for a particular size of bitmap as an access, even if no bitmaps
* of that size are present. We do not count addition or removal of bitmaps as an access.
*/
class GroupedLinkedMap<K extends Poolable, V> {
private final LinkedEntry<K, V> head = new LinkedEntry<K, V>();
private final Map<K, LinkedEntry<K, V>> keyToEntry = new HashMap<K, LinkedEntry<K, V>>();

public void put(K key, V value) {
LinkedEntry<K, V> entry = keyToEntry.get(key);

if (entry == null) {
entry = new LinkedEntry<K, V>(key);
makeTail(entry);
keyToEntry.put(key, entry);
} else {
key.offer();
}

entry.add(value);
}

public V get(K key) {
LinkedEntry<K, V> entry = keyToEntry.get(key);
if (entry == null) {
entry = new LinkedEntry<K, V>(key);
keyToEntry.put(key, entry);
} else {
key.offer();
}

makeHead(entry);

return entry.removeLast();
}

public V removeLast() {
LinkedEntry<K, V> last = head.prev;

while (last != head) {
V removed = last.removeLast();
if (removed != null) {
return removed;
} else {
// We will clean up empty lru entries since they are likely to have been one off or unusual sizes and
// are not likely to be requested again so the gc thrash should be minimal. Doing so will speed up our
// removeLast operation in the future and prevent our linked list from growing to arbitrarily large
// sizes.
removeEntry(last);
keyToEntry.remove(last.key);
last.key.offer();
}

last = last.prev;
}

return null;
}

@Override
public String toString() {
String result = "GroupedLinkedMap( ";
LinkedEntry<K, V> current = head.next;
boolean hadAtLeastOneItem = false;
while (current != head) {
hadAtLeastOneItem = true;
result += "{" + current.key + ":" + current.size() + "}, ";
current = current.next;
}
if (hadAtLeastOneItem) {
result = result.substring(0, result.length() - 2);
}
return result + " )";
}

// Make the entry the most recently used item.
private void makeHead(LinkedEntry<K, V> entry) {
removeEntry(entry);
entry.prev = head;
entry.next = head.next;
updateEntry(entry);
}

// Make the entry the least recently used item.
private void makeTail(LinkedEntry<K, V> entry) {
removeEntry(entry);
entry.prev = head.prev;
entry.next = head;
updateEntry(entry);
}

private static void updateEntry(LinkedEntry entry) {
entry.next.prev = entry;
entry.prev.next = entry;
}

private static void removeEntry(LinkedEntry entry) {
entry.prev.next = entry.next;
entry.next.prev = entry.prev;
}

private static class LinkedEntry<K, V> {
private final K key;
private List<V> values;
LinkedEntry<K, V> next;
LinkedEntry<K, V> prev;

// Used only for the first item in the list which we will treat specially and which will not contain a value.
public LinkedEntry() {
this(null);
}

public LinkedEntry(K key) {
next = prev = this;
this.key = key;
}

public V removeLast() {
final int valueSize = size();
return valueSize > 0 ? values.remove(valueSize - 1) : null;
}

public int size() {
return values != null ? values.size() : 0;
}

public void add(V value) {
if (values == null) {
values = new ArrayList<V>();
}
values.add(value);
}
}
}
Loading

1 comment on commit d5ddd93

@csobrinho
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sjudd nicely done!

Please sign in to comment.