Commit 4c105a7f authored by Geoffrey Métais's avatar Geoffrey Métais

Bitmap recycling, SoftReferences in cache

parent a26c9b9e
......@@ -30,10 +30,12 @@ import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.preference.PreferenceManager;
import android.support.v4.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.SectionIndexer;
......
......@@ -20,6 +20,7 @@
package org.videolan.vlc.gui.audio;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
......@@ -29,18 +30,22 @@ import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.support.annotation.RequiresPermission;
import android.util.Log;
import android.widget.Toast;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.libvlc.util.VLCUtil;
import org.videolan.vlc.BuildConfig;
import org.videolan.vlc.MediaWrapper;
import org.videolan.vlc.R;
import org.videolan.vlc.VLCApplication;
import org.videolan.vlc.util.AndroidDevices;
import org.videolan.vlc.util.BitmapCache;
import org.videolan.vlc.util.BitmapUtil;
import org.videolan.vlc.util.MurmurHash;
import org.videolan.vlc.util.Util;
......@@ -103,7 +108,7 @@ public class AudioUtil {
context.getApplicationContext(),
RingtoneManager.TYPE_RINGTONE,
newUri
);
);
} catch(Exception e) {
Toast.makeText(context.getApplicationContext(),
context.getString(R.string.ringtone_error),
......@@ -167,11 +172,11 @@ public class AudioUtil {
return null;
ContentResolver contentResolver = context.getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, new String[] {
MediaStore.Audio.Albums.ALBUM,
MediaStore.Audio.Albums.ALBUM_ART },
MediaStore.Audio.Albums.ALBUM + " LIKE ?",
new String[] { album }, null);
Cursor cursor = contentResolver.query(uri, new String[]{
MediaStore.Audio.Albums.ALBUM,
MediaStore.Audio.Albums.ALBUM_ART},
MediaStore.Audio.Albums.ALBUM + " LIKE ?",
new String[]{album}, null);
if (cursor == null) {
// do nothing
} else if (!cursor.moveToFirst()) {
......@@ -197,8 +202,8 @@ public class AudioUtil {
/* Parse decoded attachment */
if( mArtist.length() == 0 || mAlbum.length() == 0 ||
mArtist.equals(VLCApplication.getAppContext().getString(R.string.unknown_artist)) ||
mAlbum.equals(VLCApplication.getAppContext().getString(R.string.unknown_album)) )
mArtist.equals(VLCApplication.getAppContext().getString(R.string.unknown_artist)) ||
mAlbum.equals(VLCApplication.getAppContext().getString(R.string.unknown_album)) )
{
/* If artist or album are missing, it was cached by title MD5 hash */
MessageDigest md = MessageDigest.getInstance("MD5");
......@@ -355,6 +360,8 @@ public class AudioUtil {
/* Get the resolution of the bitmap without allocating the memory */
options.inJustDecodeBounds = true;
options.inMutable = true;
BitmapUtil.setInBitmap(options);
BitmapFactory.decodeFile(path, options);
if (options.outWidth > 0 && options.outHeight > 0) {
......@@ -366,6 +373,7 @@ public class AudioUtil {
options.inSampleSize = options.inSampleSize * 2;
// Decode the file (with memory allocation this time)
BitmapUtil.setInBitmap(options);
cover = BitmapFactory.decodeFile(path, options);
}
......
......@@ -33,13 +33,21 @@ import android.view.View;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.vlc.VLCApplication;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
public class BitmapCache {
public final static String TAG = "VLC/BitmapCache";
private final static boolean LOG_ENABLED = false;
private static BitmapCache mInstance;
private final LruCache<String, Bitmap> mMemCache;
private final LruCache<String, SoftReference<Bitmap>> mMemCache;
Set<SoftReference<Bitmap>> mCachedBitmaps;
Set<SoftReference<Bitmap>> mReusableBitmaps;
public static BitmapCache getInstance() {
if (mInstance == null)
......@@ -59,33 +67,60 @@ public class BitmapCache {
// Use 1/5th of the available memory for this memory cache.
final int cacheSize = 1024 * 1024 * memClass / 5;
Log.d(TAG, "LRUCache size sets to " + cacheSize);
Log.i(TAG, "LRUCache size set to " + cacheSize);
mMemCache = new LruCache<String, Bitmap>(cacheSize) {
mMemCache = new LruCache<String, SoftReference<Bitmap>>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
protected int sizeOf(String key, SoftReference<Bitmap> value) {
if (value.get() == null)
return 0;
return value.get().getRowBytes() * value.get().getHeight();
}
@Override
protected void entryRemoved(boolean evicted, String key, SoftReference<Bitmap> oldValue, SoftReference<Bitmap> newValue) {
if (evicted) {
mCachedBitmaps.remove(oldValue);
if (oldValue.get() != null)
addReusableBitmapRef(oldValue);
}
}
};
if (AndroidUtil.isHoneycombOrLater())
mReusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
mCachedBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}
public Bitmap getBitmapFromMemCache(String key) {
final Bitmap b = mMemCache.get(key);
if (LOG_ENABLED)
Log.d(TAG, (b == null) ? "Cache miss" : "Cache found");
if (b != null && b.isRecycled()) {
/* A recycled bitmap cannot be used again */
final SoftReference<Bitmap> ref = mMemCache.get(key);
if (ref == null)
return null;
Bitmap b = ref.get();
if (b == null){
mMemCache.remove(key);
mCachedBitmaps.remove(ref);
return null;
}
if (b.isRecycled()) {
/* A recycled bitmap cannot be used again */
addReusableBitmapRef(ref);
mCachedBitmaps.remove(ref);
mMemCache.remove(key);
b = null;
}
if (LOG_ENABLED)
Log.d(TAG, (b == null) ? "Cache miss" : "Cache found");
return b;
}
public void addBitmapToMemCache(String key, Bitmap bitmap) {
if (key != null && bitmap != null && getBitmapFromMemCache(key) == null)
mMemCache.put(key, bitmap);
if (key != null && bitmap != null && getBitmapFromMemCache(key) == null) {
final SoftReference<Bitmap> ref =new SoftReference<Bitmap>(bitmap);
mMemCache.put(key, ref);
mCachedBitmaps.add(ref);
}
}
private Bitmap getBitmapFromMemCache(int resId) {
......@@ -98,15 +133,49 @@ public class BitmapCache {
public void clear() {
mMemCache.evictAll();
mCachedBitmaps.clear();
}
public static Bitmap getFromResource(View v, int resId) {
BitmapCache cache = BitmapCache.getInstance();
Bitmap bitmap = cache.getBitmapFromMemCache(resId);
if (bitmap == null) {
bitmap = BitmapFactory.decodeResource(v.getResources(), resId);
BitmapFactory.Options options = new BitmapFactory.Options();
BitmapUtil.setInBitmap(options);
options.inMutable = true;
bitmap = BitmapFactory.decodeResource(v.getResources(), resId, options);
cache.addBitmapToMemCache(resId, bitmap);
}
return bitmap;
}
public synchronized void addReusableBitmapRef(SoftReference<Bitmap> ref){
mReusableBitmaps.add(ref);
}
public synchronized Bitmap getReusableBitmap(BitmapFactory.Options targetOptions){
if (mReusableBitmaps == null || mReusableBitmaps.isEmpty())
return null;
Bitmap reusable = null;
LinkedList<SoftReference<Bitmap>> itemsToRemove = new LinkedList<SoftReference<Bitmap>>();
for(SoftReference<Bitmap> b : mReusableBitmaps){
reusable = b.get();
if (reusable == null) {
itemsToRemove.add(b);
continue;
}
// if (!reusable.isRecycled()) {
// Log.d(TAG, "not recycled");
// itemsToRemove.add(b);
// continue;
// }
if (BitmapUtil.canUseForInBitmap(reusable, targetOptions)) {
itemsToRemove.add(b);
return reusable;
}
}
if (!itemsToRemove.isEmpty())
mReusableBitmaps.removeAll(itemsToRemove);
return null;
}
}
......@@ -20,12 +20,15 @@
package org.videolan.vlc.util;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.vlc.MediaDatabase;
import org.videolan.vlc.MediaWrapper;
import org.videolan.vlc.R;
......@@ -139,4 +142,55 @@ public class BitmapUtil {
return cover;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
if (candidate == null)
return false;
if (AndroidUtil.isKitKatOrLater()) {
if (targetOptions.inSampleSize == 0)
return false;
// From Android 4.4 (KitKat) onward we can re-use if the byte size of
// the new bitmap is smaller than the reusable bitmap candidate
// allocation byte count.
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
// On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
/**
* A helper function to return the byte usage per pixel of a bitmap based on its configuration.
*/
static int getBytesPerPixel(Bitmap.Config config) {
if (config == Bitmap.Config.ARGB_8888) {
return 4;
} else if (config == Bitmap.Config.RGB_565) {
return 2;
} else if (config == Bitmap.Config.ARGB_4444) {
return 2;
} else if (config == Bitmap.Config.ALPHA_8) {
return 1;
}
return 1;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static void setInBitmap(BitmapFactory.Options options){
if (!AndroidUtil.isHoneycombOrLater())
return;
Bitmap inBitmap = BitmapCache.getInstance().getReusableBitmap(options);
if (inBitmap != null) {
options.inBitmap = inBitmap;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment