Skip to content

Instantly share code, notes, and snippets.

@Anrimian
Created October 25, 2019 07:55
Show Gist options
  • Save Anrimian/b6cfd2feb20b0abd07118d8f69d117aa to your computer and use it in GitHub Desktop.
Save Anrimian/b6cfd2feb20b0abd07118d8f69d117aa to your computer and use it in GitHub Desktop.
Lightweight android image loader(with rx dependency)
public class BitmapLruCache<K> extends LruCache<K, Bitmap> {
/**
* @param maxSize for caches that do not override {@link #sizeOf}, this is
* the maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this cache.
*/
public BitmapLruCache(int maxSize) {
super(maxSize);
}
@Override
protected int sizeOf(K key, Bitmap value) {
return BitmapCompat.getAllocationByteCount(value);
}
}
class ImageCache<T> {
private final BitmapLruCache<T> bitmapCache;
ImageCache(int maxCacheSize) {
bitmapCache = new BitmapLruCache<>(maxCacheSize);
}
@Nullable
Bitmap getBitmap(T key) {
return bitmapCache.get(key);
}
void putBitmap(T key, Bitmap bitmap) {
bitmapCache.put(key, bitmap);
}
}
public class SimpleImageLoader<K, T> {
@DrawableRes
private final int loadingPlaceholder;
@DrawableRes
private final int errorPlaceholder;
private final int timeoutSeconds;
private final ImageFetcher<T> imageFetcher;
private final KeyFetcher<K, T> keyFetcher;
private final ImageCache<K> imageCache;
private final WeakHashMap<ImageView, Disposable> imageLoadingMap = new WeakHashMap<>();
public SimpleImageLoader(int loadingPlaceholder,
int errorPlaceholder,
int timeoutSeconds,
int maxCacheSize,
ImageFetcher<T> imageFetcher,
KeyFetcher<K, T> keyFetcher) {
this.loadingPlaceholder = loadingPlaceholder;
this.errorPlaceholder = errorPlaceholder;
this.timeoutSeconds = timeoutSeconds;
this.imageFetcher = imageFetcher;
this.keyFetcher = keyFetcher;
imageCache = new ImageCache<>(maxCacheSize);
}
public void displayImage(@NonNull ImageView imageView, @NonNull T data) {
displayImage(imageView, data, errorPlaceholder);
}
public void displayImage(@NonNull ImageView imageView,
@NonNull T data,
@DrawableRes int errorPlaceholder) {
Bitmap bitmap = imageCache.getBitmap(keyFetcher.getKey(data));
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
if (loadingPlaceholder == -1) {
imageView.setImageBitmap(null);
} else {
imageView.setImageResource(loadingPlaceholder);
}
Disposable disposable = imageLoadingMap.get(imageView);
if (disposable != null) {
disposable.dispose();
}
disposable = Single.fromCallable(() -> getDataOrThrow(data))
.timeout(timeoutSeconds, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(imageView::setImageBitmap,
t -> imageView.setImageResource(errorPlaceholder));
imageLoadingMap.put(imageView, disposable);
}
@Nullable
public Bitmap getImage(@Nonnull T data) {
return getData(data);
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@SuppressLint("CheckResult")
public void displayImage(@NonNull RemoteViews widgetView,
@IdRes int viewId,
@NonNull T data,
@NonNull BitmapTransformer bitmapTransformer) {
if (loadingPlaceholder == -1) {
widgetView.setImageViewBitmap(viewId, null);
} else {
widgetView.setImageViewResource(viewId, loadingPlaceholder);
}
Single.fromCallable(() -> getDataOrThrow(data))
.timeout(timeoutSeconds, TimeUnit.SECONDS)
.map(bitmapTransformer::transform)
.subscribe(bitmap -> widgetView.setImageViewBitmap(viewId, bitmap),
t -> widgetView.setImageViewResource(viewId, errorPlaceholder));
}
@Nonnull
private Bitmap getDataOrThrow(T data) {
if (data == null) {
throw new RuntimeException("data is null");
}
Bitmap bitmap = getData(data);
if (bitmap == null) {
throw new RuntimeException("bitmap is null");
}
return bitmap;
}
@Nullable
private Bitmap getData(T data) {
K key = keyFetcher.getKey(data);
Bitmap bitmap = imageCache.getBitmap(key);
if (bitmap == null) {
synchronized (this) {
bitmap = imageCache.getBitmap(key);
if (bitmap == null) {
bitmap = imageFetcher.loadImage(data);
if (bitmap != null) {
imageCache.putBitmap(key, bitmap);
}
}
}
}
return bitmap;
}
public interface ImageFetcher<T> {
Bitmap loadImage(T data);
}
public interface KeyFetcher<K, T> {
K getKey(T data);
}
public interface BitmapTransformer {
Bitmap transform(Bitmap bitmap);
}
}
open class SimpleImageLoader<K, T>(@field:DrawableRes
private val loadingPlaceholder: Int,
@field:DrawableRes
private val errorPlaceholder: Int,
private val timeoutSeconds: Long,
maxCacheSize: Int,
private val imageFetcher: (T) -> Bitmap,
private val keyFetcher: (T) -> K) {
private val imageCache: ImageCache<K> = ImageCache(maxCacheSize)
private val imageLoadingMap = WeakHashMap<ImageView, Disposable>()
@JvmOverloads
fun displayImage(imageView: ImageView,
data: T,
@DrawableRes errorPlaceholder: Int = this.errorPlaceholder) {
val bitmap = imageCache.getBitmap(keyFetcher(data))
if (bitmap != null) {
imageView.setImageBitmap(bitmap)
return
}
if (loadingPlaceholder == -1) {
imageView.setImageBitmap(null)
} else {
imageView.setImageResource(loadingPlaceholder)
}
var disposable = imageLoadingMap[imageView]
disposable?.dispose()
disposable = Single.fromCallable { getDataOrThrow(data) }
.timeout(timeoutSeconds, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(imageView::setImageBitmap
) { imageView.setImageResource(errorPlaceholder) }
imageLoadingMap[imageView] = disposable
}
fun getImage(element: T): Bitmap? {
return getData(element)
}
@SuppressLint("CheckResult")
fun displayImage(widgetView: RemoteViews,
@IdRes viewId: Int,
data: T,
bitmapTransformer: (Bitmap) -> Bitmap) {
if (loadingPlaceholder == -1) {
widgetView.setImageViewBitmap(viewId, null)
} else {
widgetView.setImageViewResource(viewId, loadingPlaceholder)
}
Single.fromCallable { getDataOrThrow(data) }
.timeout(timeoutSeconds, TimeUnit.SECONDS)
.map(bitmapTransformer::invoke)
.subscribe({ bitmap -> widgetView.setImageViewBitmap(viewId, bitmap) },
{ widgetView.setImageViewResource(viewId, errorPlaceholder) })
}
private fun getDataOrThrow(data: T?): Bitmap {
if (data == null) {
throw RuntimeException("data is null")
}
return getData(data) ?: throw RuntimeException("bitmap is null")
}
private fun getData(data: T): Bitmap? {
val key = keyFetcher(data)
var bitmap = imageCache.getBitmap(key)
if (bitmap == null) {
synchronized(this) {
bitmap = imageCache.getBitmap(key)
if (bitmap == null) {
bitmap = imageFetcher(data)
if (bitmap != null) {
imageCache.putBitmap(key, bitmap)
}
}
}
}
return bitmap
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment