Use LruCache for cover art and no cache in CoverView
This commit is contained in:
parent
623a1b2332
commit
5d086bb82d
@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
|
||||
*
|
||||
* 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 <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 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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<Bitmap> mBitmapCache = new Cache<Bitmap>(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;
|
||||
/**
|
||||
|
@ -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<Song> {
|
||||
};
|
||||
|
||||
/**
|
||||
* A cache of 8 covers.
|
||||
* A cache of 6 MiB of covers.
|
||||
*/
|
||||
private static final Cache<Bitmap> sCoverCache = new Cache<Bitmap>(8);
|
||||
private static class CoverCache extends LruCache<Long, Bitmap> {
|
||||
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<Song> {
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user