From 5d086bb82dba11708b87dc8974466ecb1bafd1bc Mon Sep 17 00:00:00 2001 From: Christopher Eby Date: Wed, 15 Feb 2012 22:37:04 -0600 Subject: [PATCH] Use LruCache for cover art and no cache in CoverView --- src/org/kreed/vanilla/Cache.java | 161 ------------------------- src/org/kreed/vanilla/CoverBitmap.java | 49 ++------ src/org/kreed/vanilla/CoverView.java | 80 ++++++------ src/org/kreed/vanilla/Song.java | 75 +++++++----- 4 files changed, 98 insertions(+), 267 deletions(-) delete mode 100644 src/org/kreed/vanilla/Cache.java diff --git a/src/org/kreed/vanilla/Cache.java b/src/org/kreed/vanilla/Cache.java deleted file mode 100644 index 79a4e229..00000000 --- a/src/org/kreed/vanilla/Cache.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2010 Christopher Eby - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.kreed.vanilla; - -import junit.framework.Assert; -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 final long[] mKeys; - /** - * The values contained in the cache, stored in a location corresponding - * to the keys in mKeys. - */ - private final 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. - */ - private 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) - { - 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 synchronized void touch(long key) - { - 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 synchronized 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, mKeys.length - 1); - System.arraycopy(mValues, 1, mValues, 0, mKeys.length - 1); - mKeys[mKeys.length - 1] = -1; - - return removed; - } - - /** - * Insert an item into the cache. Cache must not be full. - * - * @param key The key to place the item at. Must be a duplicate of an - * existing key in the cache. - * @param value The item. - */ - public synchronized void put(long key, E value) - { - int count = count(); - Assert.assertFalse(count == mKeys.length); // must not be full - mKeys[count] = key; - mValues[count] = value; - } -} diff --git a/src/org/kreed/vanilla/CoverBitmap.java b/src/org/kreed/vanilla/CoverBitmap.java index 4bd6330a..f8e58bb0 100644 --- a/src/org/kreed/vanilla/CoverBitmap.java +++ b/src/org/kreed/vanilla/CoverBitmap.java @@ -27,7 +27,6 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Rect; @@ -121,27 +120,24 @@ public final class CoverBitmap { * @param song Title and other data are taken from here for info modes. * @param width Maximum width of image * @param height Maximum height of image - * @param reuse A Bitmap to be drawn into. If null, a new Bitmap will be - * created. If the bitmap cannot be used, it will be recycled and a new - * Bitmap created. * @return The image, or null if the song was null, or width or height * were less than 1 */ - public static Bitmap createBitmap(Context context, int style, Bitmap coverArt, Song song, int width, int height, Bitmap reuse) + public static Bitmap createBitmap(Context context, int style, Bitmap coverArt, Song song, int width, int height) { switch (style) { case STYLE_OVERLAPPING_BOX: - return createOverlappingBitmap(context, coverArt, song, width, height, reuse); + return createOverlappingBitmap(context, coverArt, song, width, height); case STYLE_INFO_BELOW: - return createSeparatedBitmap(context, coverArt, song, width, height, reuse); + return createSeparatedBitmap(context, coverArt, song, width, height); case STYLE_NO_INFO: - return createScaledBitmap(coverArt, width, height, reuse); + return createScaledBitmap(coverArt, width, height); default: throw new IllegalArgumentException("Invalid bitmap type given: " + style); } } - private static Bitmap createOverlappingBitmap(Context context, Bitmap cover, Song song, int width, int height, Bitmap bitmap) + private static Bitmap createOverlappingBitmap(Context context, Bitmap cover, Song song, int width, int height) { if (TEXT_SIZE == -1) loadTextSizes(context); @@ -185,17 +181,7 @@ public final class CoverBitmap { int bitmapWidth = Math.max(coverWidth, boxWidth); int bitmapHeight = Math.max(coverHeight, boxHeight); - if (bitmap != null) { - if (bitmap.getHeight() != bitmapHeight || bitmap.getWidth() != bitmapWidth) { - bitmap.recycle(); - bitmap = null; - } else { - bitmap.eraseColor(Color.BLACK); - } - } - - if (bitmap == null) - bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.RGB_565); + Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); if (cover != null) { @@ -231,7 +217,7 @@ public final class CoverBitmap { return bitmap; } - private static Bitmap createSeparatedBitmap(Context context, Bitmap cover, Song song, int width, int height, Bitmap bitmap) + private static Bitmap createSeparatedBitmap(Context context, Bitmap cover, Song song, int width, int height) { if (TEXT_SIZE == -1) loadTextSizes(context); @@ -281,17 +267,7 @@ public final class CoverBitmap { int bitmapWidth = horizontal ? coverWidth + boxWidth : Math.max(coverWidth, boxWidth); int bitmapHeight = horizontal ? Math.max(coverHeight, boxHeight) : coverHeight + boxHeight; - if (bitmap != null) { - if (bitmap.getHeight() != bitmapHeight || bitmap.getWidth() != bitmapWidth) { - bitmap.recycle(); - bitmap = null; - } else { - bitmap.eraseColor(Color.BLACK); - } - } - - if (bitmap == null) - bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.RGB_565); + Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); if (cover != null) { @@ -334,18 +310,13 @@ public final class CoverBitmap { * preserved. At least one dimension of the result will match the provided * dimension exactly. * - * @param source The source bitmap. Will be recycled. + * @param source The bitmap to be scaled * @param width Maximum width of image * @param height Maximum height of image - * @param reuse A bitmap that will simply be recycled. (This method does not - * support reuse.) * @return The scaled bitmap. */ - private static Bitmap createScaledBitmap(Bitmap source, int width, int height, Bitmap reuse) + private static Bitmap createScaledBitmap(Bitmap source, int width, int height) { - if (reuse != null) - reuse.recycle(); - int sourceWidth = source.getWidth(); int sourceHeight = source.getHeight(); float scale = Math.min((float)width / sourceWidth, (float)height / sourceHeight); diff --git a/src/org/kreed/vanilla/CoverView.java b/src/org/kreed/vanilla/CoverView.java index 33ead718..618bbf3e 100644 --- a/src/org/kreed/vanilla/CoverView.java +++ b/src/org/kreed/vanilla/CoverView.java @@ -91,15 +91,11 @@ public final class CoverView extends View implements Handler.Callback { /** * The current set of songs: 0 = previous, 1 = current, and 2 = next. */ - private final Song[] mSongs = new Song[3]; + private Song[] mSongs = new Song[3]; /** * The covers for the current songs: 0 = previous, 1 = current, and 2 = next. */ - private final Bitmap[] mBitmaps = new Bitmap[3]; - /** - * Cache of cover bitmaps generated for songs. The song ids are the keys. - */ - private final Cache mBitmapCache = new Cache(8); + private Bitmap[] mBitmaps = new Bitmap[3]; /** * Cover art to use when a song has no cover art in no info display styles. */ @@ -331,38 +327,28 @@ public final class CoverView extends View implements Handler.Callback { } /** - * Generates a bitmap for the given song if the cache does not contain one - * for it, or moves the bitmap to the top of the cache if it does. + * Generates a bitmap for the given song. * * @param i The position of the song in mSongs. */ private void generateBitmap(int i) { Song song = mSongs[i]; - if (song == null || song.id == -1) - return; - - Bitmap reuse = mBitmapCache.discardOldest(); - if (reuse == mDefaultCover) - reuse = null; int style = mCoverStyle; Context context = getContext(); - Bitmap cover = song.getCover(context); - int width = getWidth(); - int height = getHeight(); + Bitmap cover = song == null ? null : song.getCover(context); - Bitmap bitmap; if (cover == null && style == CoverBitmap.STYLE_NO_INFO) { - if (mDefaultCover == null) - mDefaultCover = CoverBitmap.generateDefaultCover(width, height); - bitmap = mDefaultCover; + Bitmap def = mDefaultCover; + if (def == null) { + mDefaultCover = def = CoverBitmap.generateDefaultCover(getWidth(), getHeight()); + } + mBitmaps[i] = def; } else { - bitmap = CoverBitmap.createBitmap(context, style, cover, song, width, height, reuse); + mBitmaps[i] = CoverBitmap.createBitmap(context, style, cover, song, getWidth(), getHeight()); } - mBitmaps[i] = bitmap; - mBitmapCache.put(song.id, bitmap); postInvalidate(); } @@ -372,18 +358,13 @@ public final class CoverView extends View implements Handler.Callback { */ public void setSong(int i, Song song) { + if (song == mSongs[i]) + return; + mSongs[i] = song; - if (song == null) { - mBitmaps[i] = null; - } else { - Bitmap bitmap = mBitmapCache.get(song.id); - if (bitmap != null) { - mBitmaps[i] = bitmap; - mBitmapCache.touch(song.id); - } else { - mBitmaps[i] = null; - mHandler.sendMessage(mHandler.obtainMessage(MSG_GENERATE_BITMAP, i, 0)); - } + mBitmaps[i] = null; + if (song != null) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_GENERATE_BITMAP, i, 0)); } } @@ -400,9 +381,30 @@ public final class CoverView extends View implements Handler.Callback { } mHandler.removeMessages(MSG_GENERATE_BITMAP); - setSong(1, service.getSong(0)); - setSong(2, service.getSong(1)); - setSong(0, service.getSong(-1)); + + Song[] songs = mSongs; + Bitmap[] bitmaps = mBitmaps; + Song[] newSongs = { service.getSong(-1), service.getSong(0), service.getSong(1) }; + Bitmap[] newBitmaps = new Bitmap[3]; + mSongs = newSongs; + mBitmaps = newBitmaps; + + for (int i = 0; i != 3; ++i) { + if (newSongs[i] == null) + continue; + + for (int j = 0; j != 3; ++j) { + if (newSongs[i] == songs[j]) { + newBitmaps[i] = bitmaps[j]; + break; + } + } + + if (newBitmaps[i] == null) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_GENERATE_BITMAP, i, 0)); + } + } + resetScroll(); invalidate(); } @@ -410,7 +412,7 @@ public final class CoverView extends View implements Handler.Callback { /** * Call {@link CoverView#generateBitmap(int)} for the song at the given index. * - * obj must be the Song to generate a bitmap for. + * arg1 should be the index of the song. */ private static final int MSG_GENERATE_BITMAP = 0; /** diff --git a/src/org/kreed/vanilla/Song.java b/src/org/kreed/vanilla/Song.java index 40b4653c..d909f40f 100644 --- a/src/org/kreed/vanilla/Song.java +++ b/src/org/kreed/vanilla/Song.java @@ -30,7 +30,7 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.MediaStore; -import android.util.Log; +import android.support.v4.util.LruCache; import java.io.FileDescriptor; /** @@ -85,9 +85,47 @@ public class Song implements Comparable { }; /** - * A cache of 8 covers. + * A cache of 6 MiB of covers. */ - private static final Cache sCoverCache = new Cache(8); + private static class CoverCache extends LruCache { + private final Context mContext; + + public CoverCache(Context context) + { + super(6 * 1024 * 1024); + mContext = context; + } + + @Override + public Bitmap create(Long key) + { + Uri uri = Uri.parse("content://media/external/audio/media/" + key + "/albumart"); + ContentResolver res = mContext.getContentResolver(); + + try { + ParcelFileDescriptor parcelFileDescriptor = res.openFileDescriptor(uri, "r"); + if (parcelFileDescriptor != null) { + FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); + return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, BITMAP_OPTIONS); + } + } catch (Exception e) { + // no cover art found + } + + return null; + } + + @Override + protected int sizeOf(Long key, Bitmap value) + { + return value.getRowBytes() * value.getHeight(); + } + }; + + /** + * The cache instance. + */ + private static CoverCache sCoverCache = null; /** * If true, will not attempt to load any cover art in getCover() @@ -215,32 +253,13 @@ public class Song implements Comparable { if (mDisableCoverArt || id == -1 || (flags & FLAG_NO_COVER) != 0) return null; + if (sCoverCache == null) + sCoverCache = new CoverCache(context.getApplicationContext()); + Bitmap cover = sCoverCache.get(id); - if (cover != null) { - return cover; - } - - Uri uri = Uri.parse("content://media/external/audio/media/" + id + "/albumart"); - ContentResolver res = context.getContentResolver(); - try { - ParcelFileDescriptor parcelFileDescriptor = res.openFileDescriptor(uri, "r"); - if (parcelFileDescriptor != null) { - FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); - cover = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, BITMAP_OPTIONS); - if (cover != null) { - Bitmap discarded = sCoverCache.discardOldest(); - if (discarded != null) - discarded.recycle(); - sCoverCache.put(id, cover); - return cover; - } - } - } catch (Exception e) { - Log.d("VanillaMusic", "Failed to load cover art for " + path, e); - } - - flags |= FLAG_NO_COVER; - return null; + if (cover == null) + flags |= FLAG_NO_COVER; + return cover; } @Override