From a85848c2469034c753ee5a13baca71bf59b3643d Mon Sep 17 00:00:00 2001 From: Christopher Eby Date: Wed, 30 Jun 2010 23:56:32 -0500 Subject: [PATCH] Refactor bitmap cache into a discrete class --- src/org/kreed/vanilla/Cache.java | 179 +++++++++++++++++++++++++++ src/org/kreed/vanilla/CoverView.java | 59 ++------- 2 files changed, 191 insertions(+), 47 deletions(-) create mode 100644 src/org/kreed/vanilla/Cache.java diff --git a/src/org/kreed/vanilla/Cache.java b/src/org/kreed/vanilla/Cache.java new file mode 100644 index 00000000..1389e546 --- /dev/null +++ b/src/org/kreed/vanilla/Cache.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2010 Christopher Eby + * + * 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 . + */ + +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 The type of the values. (Keys will be longs). + */ +public class Cache { + /** + * 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; + } +} diff --git a/src/org/kreed/vanilla/CoverView.java b/src/org/kreed/vanilla/CoverView.java index f5221dbe..6ef75625 100644 --- a/src/org/kreed/vanilla/CoverView.java +++ b/src/org/kreed/vanilla/CoverView.java @@ -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 mBitmapCache = new SparseArray(); - /** - * 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 mBitmapCache = new Cache(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; } /**