Refactor bitmap cache into a discrete class

This commit is contained in:
Christopher Eby 2010-06-30 23:56:32 -05:00
parent 4a120630b4
commit a85848c246
2 changed files with 191 additions and 47 deletions

View File

@ -0,0 +1,179 @@
/*
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
*
* This file is part of Vanilla Music Player.
*
* Vanilla Music Player is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Vanilla Music Player is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kreed.vanilla;
import java.util.Arrays;
/**
* A key/value map that discards old items. When the capacity of the cache is
* reached, the oldest item (by insertion time) will be discarded.
*
* Keys should be non-negative.
*
* @param <E> The type of the values. (Keys will be longs).
*/
public class Cache<E> {
/**
* The keys contained in the cache, stored in the order of insertion.
*/
private long[] mKeys;
/**
* The values contained in the cache, stored in a location corresponding
* to the keys in mKeys.
*/
private Object[] mValues;
/**
* Create a Cache.
*
* @param capacity The capacity of the cache. This is fixed and may not be
* changed after construction.
*/
public Cache(int capacity)
{
mKeys = new long[capacity];
mValues = new Object[capacity];
Arrays.fill(mKeys, -1);
}
/**
* Calculate the number of items in the cache.
*
* @return The number of items in the cache.
*/
public int count()
{
long[] keys = mKeys;
int count = keys.length;
while (--count != -1 && keys[count] == -1);
return count + 1;
}
/**
* Find the index of the given key.
*
* @param key The key to search for.
* @return The index, or -1 if the key was not found.
*/
private int indexOf(long key)
{
long[] keys = mKeys;
for (int i = keys.length; --i != -1; )
if (keys[i] == key)
return i;
return -1;
}
/**
* Retrieve the value with the given key.
*
* @param key The key to search with.
* @return The value, or null if the given key is not contained in this
* cache.
*/
@SuppressWarnings("unchecked")
public E get(long key)
{
if (key < 0)
throw new IllegalArgumentException("Keys must be non-negative.");
int i = indexOf(key);
return i == -1 ? null : (E)mValues[i];
}
/**
* Reset the age of the item with the given key so that it will be
* discarded last.
*
* @param key The key of the item to touch.
*/
public void touch(long key)
{
if (key < 0)
throw new IllegalArgumentException("Keys must be non-negative.");
long[] keys = mKeys;
Object[] values = mValues;
int oldPos = indexOf(key);
int newPos = count() - 1;
if (oldPos != newPos && oldPos != -1) {
Object value = values[oldPos];
System.arraycopy(keys, oldPos + 1, keys, oldPos, newPos - oldPos);
System.arraycopy(values, oldPos + 1, values, oldPos, newPos - oldPos);
keys[newPos] = key;
values[newPos] = value;
}
}
/**
* Discard the oldest item in the cache. Does nothing if the cache is not
* full.
*
* @return The item that was discarded, or null if the cache is not full.
*/
@SuppressWarnings("unchecked")
public E discardOldest()
{
int count = count();
// Cache is not full.
if (count != mKeys.length)
return null;
E removed = (E)mValues[0];
System.arraycopy(mKeys, 1, mKeys, 0, count - 1);
System.arraycopy(mValues, 1, mValues, 0, count - 1);
mKeys[mKeys.length - 1] = -1;
return removed;
}
/**
* Insert an item into the cache. If the cache is full, the oldest item
* will be discarded.
*
* @param key The key to place the item at. Must be a duplicate of an
* existing key in the cache.
* @param value The item.
* @return The discarded item, or null if no items were discarded.
*/
public E put(long key, E value)
{
if (key < 0)
throw new IllegalArgumentException("Keys must be non-negative.");
E discarded = discardOldest();
int count = count();
mKeys[count] = key;
mValues[count] = value;
return discarded;
}
/**
* Clear the keys and return the values to be cleared by the caller.
*
* @return The values in the cache, untouched.
*/
public Object[] clear()
{
Arrays.fill(mKeys, -1);
return mValues;
}
}

View File

@ -29,7 +29,6 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@ -62,12 +61,7 @@ public final class CoverView extends View implements Handler.Callback {
/**
* Cache of cover bitmaps generated for songs. The song ids are the keys.
*/
private SparseArray<Bitmap> mBitmapCache = new SparseArray<Bitmap>();
/**
* Stores the order of insertion of songs ids into the cache. This allows
* for old bitmaps to be discarded.
*/
private int[] mCacheTimeline = new int[8];
private Cache<Bitmap> mBitmapCache = new Cache<Bitmap>(8);
private int mTimelinePos;
private Scroller mScroller;
@ -147,7 +141,12 @@ public final class CoverView extends View implements Handler.Callback {
*/
private void regenerateBitmaps()
{
mBitmapCache.clear();
Object[] bitmaps = mBitmapCache.clear();
for (int i = bitmaps.length; --i != -1; ) {
if (bitmaps[i] != null)
((Bitmap)bitmaps[i]).recycle();
bitmaps[i] = null;
}
for (int i = STORE_SIZE; --i != -1; )
setSong(i, mSongs[i]);
}
@ -315,51 +314,17 @@ public final class CoverView extends View implements Handler.Callback {
*/
private void generateBitmap(Song song)
{
int id = (int)song.id;
boolean created = false;
if (mBitmapCache.get(id) == null) {
created = true;
Bitmap bitmap;
Bitmap bitmap = mBitmapCache.get(song.id);
if (bitmap == null) {
if (mSeparateInfo)
bitmap = CoverBitmap.createSeparatedBitmap(song, getWidth(), getHeight());
else
bitmap = CoverBitmap.createOverlappingBitmap(song, getWidth(), getHeight());
mBitmapCache.put(id, bitmap);
mBitmapCache.put(song.id, bitmap);
postInvalidate();
} else {
mBitmapCache.touch(song.id);
}
int[] timeline = mCacheTimeline;
int end = timeline.length;
while (end > 0 && timeline[end - 1] == 0)
--end;
if (!created) {
// If we already have a bitmap for the given song, erase the old
// id and make room for it at the end of the timeline.
int i = end;
while (--i != -1 && timeline[i] != id);
if (i != -1) {
System.arraycopy(timeline, i + 1, timeline, i, timeline.length - i - 1);
--end;
}
}
if (end == timeline.length) {
// If the timeline is full, erase the bitmap for the oldest song
// and make room for the new bitmap at the end of the timeline.
int toRemove = timeline[0];
System.arraycopy(timeline, 1, timeline, 0, timeline.length - 1);
Bitmap bitmap = mBitmapCache.get(toRemove);
mBitmapCache.remove(toRemove);
if (bitmap != null)
bitmap.recycle();
--end;
}
timeline[end] = id;
}
/**