mirror of
https://github.com/AntennaPod/AntennaPod.git
synced 2025-01-31 19:04:52 +01:00
Prevent images from being loaded multiple times
(copied from AntennaPodSP)
This commit is contained in:
parent
dfa10a5989
commit
b9ab10253a
@ -1,14 +1,8 @@
|
|||||||
package de.danoeh.antennapod.asynctask;
|
package de.danoeh.antennapod.asynctask;
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.v4.util.LruCache;
|
import android.support.v4.util.LruCache;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -17,214 +11,228 @@ import de.danoeh.antennapod.AppConfig;
|
|||||||
import de.danoeh.antennapod.PodcastApp;
|
import de.danoeh.antennapod.PodcastApp;
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
|
|
||||||
/** Caches and loads FeedImage bitmaps in the background */
|
import java.io.InputStream;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches and loads FeedImage bitmaps in the background
|
||||||
|
*/
|
||||||
public class ImageLoader {
|
public class ImageLoader {
|
||||||
private static final String TAG = "ImageLoader";
|
private static final String TAG = "ImageLoader";
|
||||||
private static ImageLoader singleton;
|
private static ImageLoader singleton;
|
||||||
|
|
||||||
public static final int IMAGE_TYPE_THUMBNAIL = 0;
|
public static final int IMAGE_TYPE_THUMBNAIL = 0;
|
||||||
public static final int IMAGE_TYPE_COVER = 1;
|
public static final int IMAGE_TYPE_COVER = 1;
|
||||||
|
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
private ExecutorService executor;
|
private ExecutorService executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores references to loaded bitmaps. Bitmaps can be accessed by the id of
|
* Stores references to loaded bitmaps. Bitmaps can be accessed by the id of
|
||||||
* the FeedImage the bitmap belongs to.
|
* the FeedImage the bitmap belongs to.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
final int memClass = ((ActivityManager) PodcastApp.getInstance()
|
final int memClass = ((ActivityManager) PodcastApp.getInstance()
|
||||||
.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
|
.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
|
||||||
|
|
||||||
// Use 1/8th of the available memory for this memory cache.
|
// Use 1/8th of the available memory for this memory cache.
|
||||||
final int thumbnailCacheSize = 1024 * 1024 * memClass / 8;
|
final int thumbnailCacheSize = 1024 * 1024 * memClass / 8;
|
||||||
|
|
||||||
private LruCache<String, CachedBitmap> coverCache;
|
private LruCache<String, CachedBitmap> coverCache;
|
||||||
private LruCache<String, CachedBitmap> thumbnailCache;
|
private LruCache<String, CachedBitmap> thumbnailCache;
|
||||||
|
|
||||||
private ImageLoader() {
|
private ImageLoader() {
|
||||||
handler = new Handler();
|
handler = new Handler();
|
||||||
executor = createExecutor();
|
executor = createExecutor();
|
||||||
|
|
||||||
coverCache = new LruCache<String, CachedBitmap>(1);
|
coverCache = new LruCache<String, CachedBitmap>(1);
|
||||||
|
|
||||||
thumbnailCache = new LruCache<String, CachedBitmap>(thumbnailCacheSize) {
|
thumbnailCache = new LruCache<String, CachedBitmap>(thumbnailCacheSize) {
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
@Override
|
@Override
|
||||||
protected int sizeOf(String key, CachedBitmap value) {
|
protected int sizeOf(String key, CachedBitmap value) {
|
||||||
if (Integer.valueOf(android.os.Build.VERSION.SDK_INT) >= 12)
|
if (Integer.valueOf(android.os.Build.VERSION.SDK_INT) >= 12)
|
||||||
return value.getBitmap().getByteCount();
|
return value.getBitmap().getByteCount();
|
||||||
else
|
else
|
||||||
return (value.getBitmap().getRowBytes() * value.getBitmap()
|
return (value.getBitmap().getRowBytes() * value.getBitmap()
|
||||||
.getHeight());
|
.getHeight());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecutorService createExecutor() {
|
private ExecutorService createExecutor() {
|
||||||
return Executors.newFixedThreadPool(Runtime.getRuntime()
|
return Executors.newFixedThreadPool(Runtime.getRuntime()
|
||||||
.availableProcessors(), new ThreadFactory() {
|
.availableProcessors(), new ThreadFactory() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Thread newThread(Runnable r) {
|
public Thread newThread(Runnable r) {
|
||||||
Thread t = new Thread(r);
|
Thread t = new Thread(r);
|
||||||
t.setPriority(Thread.MIN_PRIORITY);
|
t.setPriority(Thread.MIN_PRIORITY);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized ImageLoader getInstance() {
|
public static synchronized ImageLoader getInstance() {
|
||||||
if (singleton == null) {
|
if (singleton == null) {
|
||||||
singleton = new ImageLoader();
|
singleton = new ImageLoader();
|
||||||
}
|
}
|
||||||
return singleton;
|
return singleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a bitmap from the cover cache. If the bitmap is not in the cache, it
|
* Load a bitmap from the cover cache. If the bitmap is not in the cache, it
|
||||||
* will be loaded from the disk. This method should either be called if the
|
* will be loaded from the disk. This method should either be called if the
|
||||||
* ImageView's size has already been set or inside a Runnable which is
|
* ImageView's size has already been set or inside a Runnable which is
|
||||||
* posted to the ImageView's message queue.
|
* posted to the ImageView's message queue.
|
||||||
*/
|
*/
|
||||||
public void loadCoverBitmap(ImageWorkerTaskResource source, ImageView target) {
|
public void loadCoverBitmap(ImageWorkerTaskResource source, ImageView target) {
|
||||||
loadCoverBitmap(source, target, target.getHeight());
|
loadCoverBitmap(source, target, target.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a bitmap from the cover cache. If the bitmap is not in the cache, it
|
* Load a bitmap from the cover cache. If the bitmap is not in the cache, it
|
||||||
* will be loaded from the disk. This method should either be called if the
|
* will be loaded from the disk. This method should either be called if the
|
||||||
* ImageView's size has already been set or inside a Runnable which is
|
* ImageView's size has already been set or inside a Runnable which is
|
||||||
* posted to the ImageView's message queue.
|
* posted to the ImageView's message queue.
|
||||||
*/
|
*/
|
||||||
public void loadCoverBitmap(ImageWorkerTaskResource source,
|
public void loadCoverBitmap(ImageWorkerTaskResource source,
|
||||||
ImageView target, int length) {
|
ImageView target, int length) {
|
||||||
final int defaultCoverResource = getDefaultCoverResource(target
|
final int defaultCoverResource = getDefaultCoverResource(target
|
||||||
.getContext());
|
.getContext());
|
||||||
|
final String cacheKey;
|
||||||
|
if (source != null && (cacheKey = source.getImageLoaderCacheKey()) != null) {
|
||||||
|
final Object currentTag = target.getTag(R.id.imageloader_key);
|
||||||
|
if (currentTag == null || !cacheKey.equals(currentTag)) {
|
||||||
|
target.setTag(R.id.imageloader_key, cacheKey);
|
||||||
|
CachedBitmap cBitmap = getBitmapFromCoverCache(cacheKey);
|
||||||
|
if (cBitmap != null && cBitmap.getLength() >= length) {
|
||||||
|
target.setImageBitmap(cBitmap.getBitmap());
|
||||||
|
} else {
|
||||||
|
target.setImageResource(defaultCoverResource);
|
||||||
|
BitmapDecodeWorkerTask worker = new BitmapDecodeWorkerTask(
|
||||||
|
handler, target, source, length, IMAGE_TYPE_COVER);
|
||||||
|
executor.submit(worker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target.setImageResource(defaultCoverResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (source != null && source.getImageLoaderCacheKey() != null) {
|
/**
|
||||||
target.setTag(R.id.imageloader_key, source.getImageLoaderCacheKey());
|
* Load a bitmap from the thumbnail cache. If the bitmap is not in the
|
||||||
CachedBitmap cBitmap = getBitmapFromCoverCache(source.getImageLoaderCacheKey());
|
* cache, it will be loaded from the disk. This method should either be
|
||||||
if (cBitmap != null && cBitmap.getLength() >= length) {
|
* called if the ImageView's size has already been set or inside a Runnable
|
||||||
target.setImageBitmap(cBitmap.getBitmap());
|
* which is posted to the ImageView's message queue.
|
||||||
} else {
|
*/
|
||||||
target.setImageResource(defaultCoverResource);
|
public void loadThumbnailBitmap(ImageWorkerTaskResource source,
|
||||||
BitmapDecodeWorkerTask worker = new BitmapDecodeWorkerTask(
|
ImageView target) {
|
||||||
handler, target, source, length, IMAGE_TYPE_COVER);
|
loadThumbnailBitmap(source, target, target.getHeight());
|
||||||
executor.submit(worker);
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
target.setImageResource(defaultCoverResource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a bitmap from the thumbnail cache. If the bitmap is not in the
|
* Load a bitmap from the thumbnail cache. If the bitmap is not in the
|
||||||
* cache, it will be loaded from the disk. This method should either be
|
* cache, it will be loaded from the disk. This method should either be
|
||||||
* called if the ImageView's size has already been set or inside a Runnable
|
* called if the ImageView's size has already been set or inside a Runnable
|
||||||
* which is posted to the ImageView's message queue.
|
* which is posted to the ImageView's message queue.
|
||||||
*/
|
*/
|
||||||
public void loadThumbnailBitmap(ImageWorkerTaskResource source,
|
public void loadThumbnailBitmap(ImageWorkerTaskResource source,
|
||||||
ImageView target) {
|
ImageView target, int length) {
|
||||||
loadThumbnailBitmap(source, target, target.getHeight());
|
final int defaultCoverResource = getDefaultCoverResource(target
|
||||||
}
|
.getContext());
|
||||||
|
final String cacheKey;
|
||||||
|
if (source != null && (cacheKey = source.getImageLoaderCacheKey()) != null) {
|
||||||
|
final Object currentTag = target.getTag(R.id.imageloader_key);
|
||||||
|
if (currentTag == null || !cacheKey.equals(currentTag)) {
|
||||||
|
target.setTag(R.id.imageloader_key, cacheKey);
|
||||||
|
CachedBitmap cBitmap = getBitmapFromThumbnailCache(cacheKey);
|
||||||
|
if (cBitmap != null && cBitmap.getLength() >= length) {
|
||||||
|
target.setImageBitmap(cBitmap.getBitmap());
|
||||||
|
} else {
|
||||||
|
target.setImageResource(defaultCoverResource);
|
||||||
|
BitmapDecodeWorkerTask worker = new BitmapDecodeWorkerTask(
|
||||||
|
handler, target, source, length, IMAGE_TYPE_THUMBNAIL);
|
||||||
|
executor.submit(worker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target.setImageResource(defaultCoverResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
public void clearExecutorQueue() {
|
||||||
* Load a bitmap from the thumbnail cache. If the bitmap is not in the
|
executor.shutdownNow();
|
||||||
* cache, it will be loaded from the disk. This method should either be
|
if (AppConfig.DEBUG)
|
||||||
* called if the ImageView's size has already been set or inside a Runnable
|
Log.d(TAG, "Executor was shut down.");
|
||||||
* which is posted to the ImageView's message queue.
|
executor = createExecutor();
|
||||||
*/
|
|
||||||
public void loadThumbnailBitmap(ImageWorkerTaskResource source,
|
|
||||||
ImageView target, int length) {
|
|
||||||
final int defaultCoverResource = getDefaultCoverResource(target
|
|
||||||
.getContext());
|
|
||||||
|
|
||||||
if (source != null && source.getImageLoaderCacheKey() != null) {
|
}
|
||||||
target.setTag(R.id.imageloader_key, source.getImageLoaderCacheKey());
|
|
||||||
CachedBitmap cBitmap = getBitmapFromThumbnailCache(source.getImageLoaderCacheKey());
|
|
||||||
if (cBitmap != null && cBitmap.getLength() >= length) {
|
|
||||||
target.setImageBitmap(cBitmap.getBitmap());
|
|
||||||
} else {
|
|
||||||
target.setImageResource(defaultCoverResource);
|
|
||||||
BitmapDecodeWorkerTask worker = new BitmapDecodeWorkerTask(
|
|
||||||
handler, target, source, length, IMAGE_TYPE_THUMBNAIL);
|
|
||||||
executor.submit(worker);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
target.setImageResource(defaultCoverResource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearExecutorQueue() {
|
public void wipeImageCache() {
|
||||||
executor.shutdownNow();
|
coverCache.evictAll();
|
||||||
if (AppConfig.DEBUG)
|
thumbnailCache.evictAll();
|
||||||
Log.d(TAG, "Executor was shut down.");
|
}
|
||||||
executor = createExecutor();
|
|
||||||
|
|
||||||
}
|
public boolean isInThumbnailCache(String fileUrl) {
|
||||||
|
return thumbnailCache.get(fileUrl) != null;
|
||||||
|
}
|
||||||
|
|
||||||
public void wipeImageCache() {
|
private CachedBitmap getBitmapFromThumbnailCache(String key) {
|
||||||
coverCache.evictAll();
|
return thumbnailCache.get(key);
|
||||||
thumbnailCache.evictAll();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInThumbnailCache(String fileUrl) {
|
public void addBitmapToThumbnailCache(String key, CachedBitmap bitmap) {
|
||||||
return thumbnailCache.get(fileUrl) != null;
|
thumbnailCache.put(key, bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CachedBitmap getBitmapFromThumbnailCache(String key) {
|
public boolean isInCoverCache(String fileUrl) {
|
||||||
return thumbnailCache.get(key);
|
return coverCache.get(fileUrl) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addBitmapToThumbnailCache(String key, CachedBitmap bitmap) {
|
private CachedBitmap getBitmapFromCoverCache(String key) {
|
||||||
thumbnailCache.put(key, bitmap);
|
return coverCache.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInCoverCache(String fileUrl) {
|
public void addBitmapToCoverCache(String key, CachedBitmap bitmap) {
|
||||||
return coverCache.get(fileUrl) != null;
|
coverCache.put(key, bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CachedBitmap getBitmapFromCoverCache(String key) {
|
private int getDefaultCoverResource(Context context) {
|
||||||
return coverCache.get(key);
|
return android.R.color.transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addBitmapToCoverCache(String key, CachedBitmap bitmap) {
|
/**
|
||||||
coverCache.put(key, bitmap);
|
* Used by the BitmapDecodeWorker task to retrieve the source of the bitmap.
|
||||||
}
|
*/
|
||||||
|
public interface ImageWorkerTaskResource {
|
||||||
|
/**
|
||||||
|
* Opens a new InputStream that can be decoded as a bitmap by the
|
||||||
|
* BitmapFactory.
|
||||||
|
*/
|
||||||
|
public InputStream openImageInputStream();
|
||||||
|
|
||||||
private int getDefaultCoverResource(Context context) {
|
/**
|
||||||
return android.R.color.transparent;
|
* Returns an InputStream that points to the beginning of the image
|
||||||
}
|
* resource. Implementations can either create a new InputStream or
|
||||||
|
* reset the existing one, depending on their implementation of
|
||||||
|
* openInputStream. If a new InputStream is returned, the one given as a
|
||||||
|
* parameter MUST be closed.
|
||||||
|
*
|
||||||
|
* @param input The input stream that was returned by openImageInputStream()
|
||||||
|
*/
|
||||||
|
public InputStream reopenImageInputStream(InputStream input);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by the BitmapDecodeWorker task to retrieve the source of the bitmap.
|
* Returns a string that identifies the image resource. Example: file
|
||||||
*/
|
* path of an image
|
||||||
public interface ImageWorkerTaskResource {
|
*/
|
||||||
/**
|
public String getImageLoaderCacheKey();
|
||||||
* Opens a new InputStream that can be decoded as a bitmap by the
|
}
|
||||||
* BitmapFactory.
|
|
||||||
*/
|
|
||||||
public InputStream openImageInputStream();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an InputStream that points to the beginning of the image
|
|
||||||
* resource. Implementations can either create a new InputStream or
|
|
||||||
* reset the existing one, depending on their implementation of
|
|
||||||
* openInputStream. If a new InputStream is returned, the one given as a
|
|
||||||
* parameter MUST be closed.
|
|
||||||
* @param input The input stream that was returned by openImageInputStream()
|
|
||||||
* */
|
|
||||||
public InputStream reopenImageInputStream(InputStream input);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string that identifies the image resource. Example: file
|
|
||||||
* path of an image
|
|
||||||
*/
|
|
||||||
public String getImageLoaderCacheKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user