Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 169 additions & 102 deletions src/main/java/de/blau/android/layer/tiles/util/LRUMapTileCache.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Created by plusminus on 22:13:10 - 28.09.2008
package de.blau.android.layer.tiles.util;

import static de.blau.android.contract.Constants.LOG_TAG_LEN;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import android.graphics.Bitmap;
import android.util.Log;
Expand All @@ -26,15 +29,12 @@
*/
public class LRUMapTileCache<T> {

private static final String DEBUG_TAG = LRUMapTileCache.class.getSimpleName().substring(0, Math.min(23, LRUMapTileCache.class.getSimpleName().length()));

// ===========================================================
// Constants
// ===========================================================
private static final int TAG_LEN = Math.min(LOG_TAG_LEN, LRUMapTileCache.class.getSimpleName().length());
private static final String DEBUG_TAG = LRUMapTileCache.class.getSimpleName().substring(0, TAG_LEN);

// ===========================================================
// Fields
// ===========================================================
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
private ReentrantReadWriteLock.ReadLock readLock = lock.readLock();

Map<String, CacheElement<T>> cache;

Expand Down Expand Up @@ -86,10 +86,6 @@ void init(@Nullable String key, @Nullable B blob, boolean recycleable, long owne
}
}

// ===========================================================
// Constructors
// ===========================================================

/**
* Constructs a new LRU cache instance.
*
Expand All @@ -103,27 +99,24 @@ public LRUMapTileCache(final long maxCacheSize) {
reuseList = new ArrayList<>();
}

// ===========================================================
// Getter & Setter
// ===========================================================

// ===========================================================
// Methods from SuperClass/Interfaces
// ===========================================================

/**
* Empty all data structures
*/
public synchronized void clear() {
for (CacheElement<T> ce : cache.values()) {
T b = ce.blob;
if (b instanceof Bitmap && ce.recycleable) {
((Bitmap) b).recycle();
public void clear() {
lockWrites();
try {
for (CacheElement<T> ce : cache.values()) {
T b = ce.blob;
if (b instanceof Bitmap && ce.recycleable) {
((Bitmap) b).recycle();
}
}
cache.clear();
list.clear();
cacheSize = 0;
} finally {
unlockWrites();
}
cache.clear();
list.clear();
cacheSize = 0;
}

/**
Expand All @@ -134,34 +127,39 @@ public synchronized void clear() {
* @param owner a long indicating who added the element to the cache
* @return true if the limit was successfully applied
*/
private synchronized boolean applyCacheLimit(long extra, long owner) {
long limit = maxCacheSize - extra;
if (limit < 0) {
limit = 0;
}
while (cacheSize > limit && !list.isEmpty()) {
CacheElement<T> ce = list.remove(list.size() - 1);
if (ce.owner == owner && owner != 0) {
// cache is being thrashed because it is too small, fail
Log.e(DEBUG_TAG, "cache too small, failing");
return false;
private boolean applyCacheLimit(long extra, long owner) {
lockWrites();
try {
long limit = maxCacheSize - extra;
if (limit < 0) {
limit = 0;
}
if (cache.remove(ce.key) == null) {
throw new IllegalStateException("can't remove " + ce.key + " from cache");
}
reuseList.add(ce);
T b = ce.blob;
if (b instanceof Bitmap && !((Bitmap) b).isRecycled()) {
Bitmap bitmap = (Bitmap) b;
cacheSize -= (long) bitmap.getRowBytes() * bitmap.getHeight();
if (ce.recycleable) {
bitmap.recycle();
while (cacheSize > limit && !list.isEmpty()) {
CacheElement<T> ce = list.remove(list.size() - 1);
if (ce.owner == owner && owner != 0) {
// cache is being thrashed because it is too small, fail
Log.e(DEBUG_TAG, "cache too small, failing");
return false;
}
if (cache.remove(ce.key) == null) {
throw new IllegalStateException("can't remove " + ce.key + " from cache");
}
reuseList.add(ce);
T b = ce.blob;
if (b instanceof Bitmap && !((Bitmap) b).isRecycled()) {
Bitmap bitmap = (Bitmap) b;
cacheSize -= (long) bitmap.getRowBytes() * bitmap.getHeight();
if (ce.recycleable) {
bitmap.recycle();
}
} else {
cacheSize -= 1;
}
} else {
cacheSize -= 1;
}
return true; // success
} finally {
unlockWrites();
}
return true; // success
}

/**
Expand All @@ -170,15 +168,25 @@ private synchronized boolean applyCacheLimit(long extra, long owner) {
* @return count
*/
public int size() {
return cache.size();
lockReads();
try {
return cache.size();
} finally {
unlockReads();
}
}

/**
* Reduces memory use by halving the cache size.
*/
public void onLowMemory() {
maxCacheSize /= 2;
applyCacheLimit(0, 0);
lockWrites();
try {
maxCacheSize /= 2;
applyCacheLimit(0, 0);
} finally {
unlockWrites();
}
}

/**
Expand All @@ -187,8 +195,13 @@ public void onLowMemory() {
* @param key the key
* @return true if present
*/
public synchronized boolean containsKey(@NonNull String key) {
return cache.containsKey(key);
public boolean containsKey(@NonNull String key) {
lockReads();
try {
return cache.containsKey(key);
} finally {
unlockReads();
}
}

/**
Expand All @@ -197,7 +210,12 @@ public synchronized boolean containsKey(@NonNull String key) {
* @return The number of bytes used by the cache.
*/
public long cacheSizeBytes() {
return cacheSize;
lockReads();
try {
return cacheSize;
} finally {
unlockReads();
}
}

/**
Expand All @@ -221,46 +239,51 @@ public long getMaxCacheSize() {
* <code>null</code> return can also indicate that the cache previously associated <code>null</code> with
* the specified key
*/
public synchronized T put(@NonNull final String key, @NonNull final T value, boolean recycleable, long owner) {
public T put(@NonNull final String key, @NonNull final T value, boolean recycleable, long owner) {
if (maxCacheSize == 0) {
return null;
}

CacheElement<T> prev = cache.get(key);
// if the key isn't in the cache and the cache is full...
if (prev != null) {
update(prev);
return value;
}
long sizeInc = 1;
if (value instanceof Bitmap) {
Bitmap bitmap = (Bitmap) value;
sizeInc = (long) bitmap.getRowBytes() * bitmap.getHeight();
if (!applyCacheLimit(sizeInc * 2, owner)) {
// failed: cache is to small to handle all tiles necessary for one draw cycle
// see if we can expand by 50%
if (maxCacheSize < (Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory()) && (maxCacheSize / 2 > sizeInc)) {
Log.w(DEBUG_TAG, "expanding memory tile cache from " + maxCacheSize + " to " + (maxCacheSize + maxCacheSize / 2));
maxCacheSize = maxCacheSize + maxCacheSize / 2;
} else {
throw new StorageException(StorageException.OOM); // can't expand any more
lockWrites();
try {
CacheElement<T> prev = cache.get(key);
// if the key isn't in the cache and the cache is full...
if (prev != null) {
update(prev);
return value;
}
long sizeInc = 1;
if (value instanceof Bitmap) {
Bitmap bitmap = (Bitmap) value;
sizeInc = (long) bitmap.getRowBytes() * bitmap.getHeight();
if (!applyCacheLimit(sizeInc * 2, owner)) {
// failed: cache is to small to handle all tiles necessary for one draw cycle
// see if we can expand by 50%
if (maxCacheSize < (Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory()) && (maxCacheSize / 2 > sizeInc)) {
Log.w(DEBUG_TAG, "expanding memory tile cache from " + maxCacheSize + " to " + (maxCacheSize + maxCacheSize / 2));
maxCacheSize = maxCacheSize + maxCacheSize / 2;
} else {
throw new StorageException(StorageException.OOM); // can't expand any more
}
}
} else {
applyCacheLimit(2, owner);
}
} else {
applyCacheLimit(2, owner);
}
// avoid creating new objects
CacheElement<T> ce = null;
if (!reuseList.isEmpty()) {
ce = reuseList.remove(0);
ce.init(key, value, recycleable, owner);
} else {
ce = new CacheElement<>(key, value, recycleable, owner);
// avoid creating new objects
CacheElement<T> ce = null;
if (!reuseList.isEmpty()) {
ce = reuseList.remove(0);
ce.init(key, value, recycleable, owner);
} else {
ce = new CacheElement<>(key, value, recycleable, owner);
}
list.add(0, ce);
cache.put(key, ce);
cacheSize += sizeInc;
return value;
} finally {
unlockWrites();
}
list.add(0, ce);
cache.put(key, ce);
cacheSize += sizeInc;
return value;
}

/**
Expand All @@ -270,31 +293,75 @@ public synchronized T put(@NonNull final String key, @NonNull final T value, boo
* @return the value to which the cache maps the specified key, or <code>null</code> if the map contains no mapping
* for this key
*/
public synchronized T get(final String key) {
final CacheElement<T> value = cache.get(key);
if (value != null) {
public T get(@NonNull final String key) {
CacheElement<T> value = lockedGet(key);
if (value == null) {
return null;
}
lockWrites();
try {
update(value);
return value.blob;
} finally {
unlockWrites();
}
}

/**
* Acquire the read lock and get the value for key
*
* @param key the key
* @return the value or null
*/
@Nullable
private CacheElement<T> lockedGet(@NonNull final String key) {
lockReads();
try {
return cache.get(key);
} finally {
unlockReads();
}
return null;
}

/**
* Moves the specified value to the top of the LRU list (the bottom of the list is where least recently used items
* live).
*
* Assume a write lock has been acquired
*
* @param value to move to the top of the list
*/
private synchronized void update(final CacheElement<T> value) {
private void update(@NonNull final CacheElement<T> value) {
list.remove(value);
list.add(0, value);
}

// ===========================================================
// Methods
// ===========================================================
/**
*
*/
private void unlockWrites() {
writeLock.unlock();
}

/**
*
*/
private void lockWrites() {
writeLock.lock();
}

// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
/**
*
*/
private void unlockReads() {
readLock.unlock();
}

/**
*
*/
private void lockReads() {
System.out.println("locking reads");
readLock.lock();
}
}
Loading
Loading