Use a field rather than a superclass for ContentCache's cache.

This makes it easier for cache experiments to swap out the
LinkedHashMap for another cache.

http://b/3184897
Change-Id: Iacdb266e41f5a98efd9bb30bc09ff8fff5a0a5a9
This commit is contained in:
Jesse Wilson 2011-01-25 17:13:22 -08:00
parent 1d62e10e4f
commit 3aee641aab
3 changed files with 42 additions and 41 deletions

View File

@ -70,9 +70,7 @@ import java.util.Set;
* All references to ContentCache that are external to the ContentCache class MUST synchronize on
* the ContentCache instance (e.g. CachedCursor.close())
*/
public final class ContentCache extends LinkedHashMap<String, Cursor> {
private static final long serialVersionUID = 1L;
public final class ContentCache {
private static final boolean DEBUG_CACHE = false; // DO NOT CHECK IN TRUE
private static final boolean DEBUG_TOKENS = false; // DO NOT CHECK IN TRUE
private static final boolean DEBUG_NOT_CACHEABLE = false; // DO NOT CHECK IN TRUE
@ -86,6 +84,24 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
// A map of queries that aren't cacheable (debug only)
private static final CounterMap<String> sNotCacheableMap = new CounterMap<String>();
private final Map<String, Cursor> mMap = new LinkedHashMap<String, Cursor>() {
@Override
public boolean removeEldestEntry(Map.Entry<String, Cursor> entry) {
synchronized (ContentCache.this) {
// If we're above the maximum size for this cache, remove the LRU cache entry
if (size() > mMaxSize) {
Cursor cursor = entry.getValue();
// Close this cursor if it's no longer being used
if (!sActiveCursors.contains(cursor)) {
cursor.close();
}
return true;
}
return false;
}
}
};
// All defined caches
private static final ArrayList<ContentCache> sContentCaches = new ArrayList<ContentCache>();
// A set of all unclosed, cached cursors; this will typically be a very small set, as cursors
@ -113,7 +129,6 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
* A synchronized reference counter for arbitrary objects
*/
/*package*/ static class CounterMap<T> {
private static final long serialVersionUID = 1L;
private HashMap<T, Integer> mMap;
/*package*/ CounterMap(int maxSize) {
@ -238,7 +253,6 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
* record.
*/
public static final class CacheToken {
private static final long serialVersionUID = 1L;
private final String mId;
private boolean mIsValid = READ_CACHE_ENABLED;
@ -304,7 +318,7 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
public void close() {
synchronized(mCache) {
int count = sActiveCursors.subtract(mCursor);
if ((count == 0) && !mCache.containsValue(mCursor)) {
if ((count == 0) && !mCache.mMap.containsValue(mCursor)) {
super.close();
}
}
@ -390,8 +404,6 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
* @param maxSize the maximum number of content cursors to cache
*/
public ContentCache(String name, String[] baseProjection, int maxSize) {
// Third argument true makes this LRU, rather than LRI
super(maxSize, .75F, true);
mName = name;
mMaxSize = maxSize;
mBaseProjection = baseProjection;
@ -426,21 +438,12 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
return token;
}
/* (non-Javadoc)
* @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
*/
@Override
public synchronized boolean removeEldestEntry(Map.Entry<String,Cursor> entry) {
// If we're above the maximum size for this cache, remove the LRU cache entry
if (size() > mMaxSize) {
Cursor cursor = entry.getValue();
// Close this cursor if it's no longer being used
if (!sActiveCursors.contains(cursor)) {
cursor.close();
}
return true;
}
return false;
public int size() {
return mMap.size();
}
private Cursor get(String id) {
return mMap.get(id);
}
/**
@ -479,7 +482,7 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
if (existingCursor != null) {
unlockImpl(id, null, false);
}
put(id, c);
mMap.put(id, c);
return new CachedCursor(c, this, id);
}
return c;
@ -623,12 +626,12 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
Log.d(mLogTag, "=========== Recaching with new values: " + id);
}
cursor.moveToFirst();
put(id, cursor);
mMap.put(id, cursor);
} else {
remove(id);
mMap.remove(id);
}
} else {
remove(id);
mMap.remove(id);
}
// If there are no cursors using the old cached cursor, close it
if (!sActiveCursors.contains(c)) {
@ -662,12 +665,12 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
}
mStats.mInvalidateCount++;
// Close all cached cursors that are no longer in use
for (Cursor c: values()) {
for (Cursor c: mMap.values()) {
if (!sActiveCursors.contains(c)) {
c.close();
}
}
clear();
mMap.clear();
// Invalidate all current tokens
mTokenList.invalidate();
}
@ -701,7 +704,7 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
}
}
private static class CacheCounter implements Comparable<Object> {
private static class CacheCounter implements Comparable<CacheCounter> {
String uri;
Integer count;
@ -711,9 +714,8 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
}
@Override
public int compareTo(Object another) {
CacheCounter x = (CacheCounter)another;
return x.count > count ? 1 : x.count == count ? 0 : -1;
public int compareTo(CacheCounter another) {
return another.count > count ? 1 : another.count == count ? 0 : -1;
}
}
@ -722,7 +724,7 @@ public final class ContentCache extends LinkedHashMap<String, Cursor> {
CacheCounter[] array = new CacheCounter[size];
int i = 0;
for (Entry<String, Integer> entry: sNotCacheableMap.entrySet()) {
for (Map.Entry<String, Integer> entry: sNotCacheableMap.entrySet()) {
array[i++] = new CacheCounter(entry.getKey(), entry.getValue());
}
Arrays.sort(array);

View File

@ -17,7 +17,6 @@
package com.android.email.data;
import com.android.email.DBTestHelper;
import com.android.email.provider.ContentCache;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.ProviderTestUtils;
@ -26,7 +25,7 @@ import android.content.Context;
import android.test.LoaderTestCase;
public class MailboxAccountLoaderTestCase extends LoaderTestCase {
// Isolted Context for providers.
// Isolated Context for providers.
private Context mProviderContext;
@Override

View File

@ -256,7 +256,7 @@ public class ContentCacheTests extends ProviderTestCase2<EmailProvider> {
assertFalse(cursor2.isClosed());
assertFalse(cursor3.isClosed());
}
public void testCloseCachedCursor() {
// Create a cache of size 2
ContentCache cache = new ContentCache("Name", SIMPLE_PROJECTION, 2);
@ -274,10 +274,10 @@ public class ContentCacheTests extends ProviderTestCase2<EmailProvider> {
assertEquals(0, ContentCache.sActiveCursors.getCount(underlyingCursor));
// Underlying cursor should be closed (no cached cursors open)
assertTrue(underlyingCursor.isClosed());
underlyingCursor = getOneRowCursor();
cache.put("2", underlyingCursor);
cachedCursor1 = new CachedCursor(underlyingCursor, cache, "2");
cachedCursor1 = cache.putCursor(
underlyingCursor, "2", SIMPLE_PROJECTION, cache.getCacheToken("2"));
cachedCursor2 = new CachedCursor(underlyingCursor, cache, "2");
assertEquals(2, ContentCache.sActiveCursors.getCount(underlyingCursor));
cachedCursor1.close();
@ -289,7 +289,7 @@ public class ContentCacheTests extends ProviderTestCase2<EmailProvider> {
cachedCursor2 = new CachedCursor(underlyingCursor, cache, "2");
assertEquals(1, ContentCache.sActiveCursors.getCount(underlyingCursor));
// Remove "2" from the cache and close the cursor
cache.remove("2");
cache.invalidate();
cachedCursor2.close();
// The underlying cursor should now be closed (not in the cache and no cached cursors)
assertEquals(0, ContentCache.sActiveCursors.getCount(underlyingCursor));