Image background is drawn first not after bitmap. (#57)

Image file cache is used only for images downloaded through web.
Image cache is now caching Bitmap instead of BitmapDrawables (drawables are not immutable).
This commit is contained in:
Hristo Hristov
2016-10-13 16:39:15 +03:00
committed by GitHub
parent ace4777811
commit 626e148e2b
4 changed files with 162 additions and 352 deletions

View File

@@ -19,10 +19,8 @@ package org.nativescript.widgets.image;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config; import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build.VERSION_CODES; import android.os.Build.VERSION_CODES;
import android.os.Environment; import android.os.Environment;
import android.os.StatFs; import android.os.StatFs;
@@ -30,11 +28,6 @@ import android.support.v4.util.LruCache;
import android.util.Log; import android.util.Log;
import java.io.File; import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@@ -56,32 +49,16 @@ public class Cache {
// Default memory cache size in kilobytes // Default memory cache size in kilobytes
private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5MB private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5MB
// Default disk cache size in bytes
private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
// Compression settings when writing images to disk cache
private static final CompressFormat DEFAULT_COMPRESS_FORMAT = CompressFormat.JPEG;
private static final int DEFAULT_COMPRESS_QUALITY = 70;
private static final int DISK_CACHE_INDEX = 0;
// Constants to easily toggle various caches // Constants to easily toggle various caches
private static final boolean DEFAULT_MEM_CACHE_ENABLED = true; private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
private static final boolean DEFAULT_DISK_CACHE_ENABLED = true; private static final boolean DEFAULT_DISK_CACHE_ENABLED = true;
private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false;
private static final String IMAGE_CACHE_DIR = "images";
private static Cache instance; private static Cache instance;
private DiskLruCache mDiskLruCache; private LruCache<String, Bitmap> mMemoryCache;
private LruCache<String, BitmapDrawable> mMemoryCache;
private CacheParams mParams; private CacheParams mParams;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private Set<SoftReference<Bitmap>> mReusableBitmaps; private Set<SoftReference<Bitmap>> mReusableBitmaps;
private Fetcher mFetcher;
/** /**
* Create a new Cache object using the specified parameters. This should not be * Create a new Cache object using the specified parameters. This should not be
* called directly by other classes, instead use * called directly by other classes, instead use
@@ -102,7 +79,6 @@ public class Cache {
public static Cache getInstance(CacheParams cacheParams) { public static Cache getInstance(CacheParams cacheParams) {
if (instance == null) { if (instance == null) {
instance = new Cache(cacheParams); instance = new Cache(cacheParams);
instance.init(cacheParams);
} }
else if (instance.mParams != cacheParams) { else if (instance.mParams != cacheParams) {
instance.init(cacheParams); instance.init(cacheParams);
@@ -119,7 +95,6 @@ public class Cache {
private void init(CacheParams cacheParams) { private void init(CacheParams cacheParams) {
mParams = cacheParams; mParams = cacheParams;
//BEGIN_INCLUDE(init_memory_cache)
// Set up memory cache // Set up memory cache
if (mParams.memoryCacheEnabled) { if (mParams.memoryCacheEnabled) {
if (Worker.debuggable > 0) { if (Worker.debuggable > 0) {
@@ -140,18 +115,18 @@ public class Cache {
Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>()); Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
} }
mMemoryCache = new LruCache<String, BitmapDrawable>(mParams.memCacheSize) { mMemoryCache = new LruCache<String, Bitmap>(mParams.memCacheSize) {
/** /**
* Notify the removed entry that is no longer being cached * Notify the removed entry that is no longer being cached
*/ */
@Override @Override
protected void entryRemoved(boolean evicted, String key, protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) { Bitmap oldValue, Bitmap newValue) {
if (Utils.hasHoneycomb()) { if (Utils.hasHoneycomb()) {
// We're running on Honeycomb or later, so add the bitmap // We're running on Honeycomb or later, so add the bitmap
// to a SoftReference set for possible use with inBitmap later // to a SoftReference set for possible use with inBitmap later
mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap())); mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue));
} }
} }
@@ -160,54 +135,12 @@ public class Cache {
* for a bitmap cache * for a bitmap cache
*/ */
@Override @Override
protected int sizeOf(String key, BitmapDrawable value) { protected int sizeOf(String key, Bitmap value) {
final int bitmapSize = getBitmapSize(value) / 1024; final int bitmapSize = getBitmapSize(value) / 1024;
return bitmapSize == 0 ? 1 : bitmapSize; return bitmapSize == 0 ? 1 : bitmapSize;
} }
}; };
} }
//END_INCLUDE(init_memory_cache)
// // By default the disk cache is not initialized here as it should be initialized
// // on a separate thread due to disk access.
// if (cacheParams.initDiskCacheOnCreate) {
// // Set up disk cache
// initDiskCache();
// }
}
/**
* Initializes the disk cache. Note that this includes disk access so this should not be
* executed on the main/UI thread. By default an Cache does not initialize the disk
* cache when it is created, instead you should call initDiskCache() to initialize it on a
* background thread.
*/
public void initDiskCache() {
// Set up disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
File diskCacheDir = mParams.diskCacheDir;
if (mParams.diskCacheEnabled && diskCacheDir != null) {
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > mParams.diskCacheSize) {
try {
mDiskLruCache = DiskLruCache.open(
diskCacheDir, 1, 1, mParams.diskCacheSize);
if (Worker.debuggable > 0) {
Log.v(TAG, "Disk cache initialized");
}
} catch (final IOException e) {
mParams.diskCacheDir = null;
Log.e(TAG, "initDiskCache - " + e);
}
}
}
}
mDiskCacheStarting = false;
mDiskCacheLock.notifyAll();
}
} }
/** /**
@@ -216,66 +149,31 @@ public class Cache {
* @param data Unique identifier for the bitmap to store * @param data Unique identifier for the bitmap to store
* @param value The bitmap drawable to store * @param value The bitmap drawable to store
*/ */
public void addBitmapToCache(String data, BitmapDrawable value, boolean useDiskCache) { public void addBitmapToCache(String data, Bitmap value) {
//BEGIN_INCLUDE(add_bitmap_to_cache)
if (data == null || value == null) { if (data == null || value == null) {
return; return;
} }
// Add to memory cache // Add to memory cache
if (mMemoryCache != null) { if (mMemoryCache != null) {
Bitmap currentValue = mMemoryCache.get(data);
// NOTE: If we have existing we probably loaded it sync so we don't want to add the new one,
// because this will make the previous bitmap free for reuse but it is used somewhere.
// Probably won't happen often.
if (currentValue == null) {
mMemoryCache.put(data, value); mMemoryCache.put(data, value);
} }
if (!useDiskCache) {
return;
} }
synchronized (mDiskCacheLock) {
// Add to disk cache
if (mDiskLruCache != null) {
final String key = hashKeyForDisk(data);
OutputStream out = null;
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot == null) {
final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
out = editor.newOutputStream(DISK_CACHE_INDEX);
value.getBitmap().compress(
mParams.compressFormat, mParams.compressQuality, out);
editor.commit();
out.close();
}
} else {
snapshot.getInputStream(DISK_CACHE_INDEX).close();
}
} catch (final IOException e) {
Log.e(TAG, "addBitmapToCache - " + e);
} catch (Exception e) {
Log.e(TAG, "addBitmapToCache - " + e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
}
}
}
}
//END_INCLUDE(add_bitmap_to_cache)
} }
/** /**
* Get from memory cache. * Get from memory cache.
* *
* @param data Unique identifier for which item to get * @param data Unique identifier for which item to get
* @return The bitmap drawable if found in cache, null otherwise * @return The bitmap if found in cache, null otherwise
*/ */
public BitmapDrawable getBitmapFromMemCache(String data) { public Bitmap getBitmapFromMemCache(String data) {
//BEGIN_INCLUDE(get_bitmap_from_mem_cache) Bitmap memValue = null;
BitmapDrawable memValue = null;
if (mMemoryCache != null) { if (mMemoryCache != null) {
memValue = mMemoryCache.get(data); memValue = mMemoryCache.get(data);
@@ -286,59 +184,6 @@ public class Cache {
} }
return memValue; return memValue;
//END_INCLUDE(get_bitmap_from_mem_cache)
}
/**
* Get from disk cache.
*
* @param data Unique identifier for which item to get
* @return The bitmap if found in cache, null otherwise
*/
public Bitmap getBitmapFromDiskCache(String data) {
//BEGIN_INCLUDE(get_bitmap_from_disk_cache)
final String key = hashKeyForDisk(data);
Bitmap bitmap = null;
synchronized (mDiskCacheLock) {
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {
}
}
if (mDiskLruCache != null) {
InputStream inputStream = null;
try {
final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
if (Worker.debuggable > 0) {
Log.v(TAG, "Disk cache hit");
}
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
if (inputStream != null) {
FileDescriptor fd = ((FileInputStream) inputStream).getFD();
// Decode bitmap, but we don't want to sample so give
// MAX_VALUE as the target dimensions
bitmap = Resizer.decodeSampledBitmapFromDescriptor(
fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);
}
}
} catch (final IOException e) {
Log.e(TAG, "getBitmapFromDiskCache - " + e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
}
}
}
return bitmap;
}
//END_INCLUDE(get_bitmap_from_disk_cache)
} }
/** /**
@@ -389,63 +234,6 @@ public class Cache {
Log.v(TAG, "Memory cache cleared"); Log.v(TAG, "Memory cache cleared");
} }
} }
synchronized (mDiskCacheLock) {
mDiskCacheStarting = true;
if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
try {
mDiskLruCache.delete();
if (Worker.debuggable > 0) {
Log.v(TAG, "Disk cache cleared");
}
} catch (IOException e) {
Log.e(TAG, "clearCache - " + e);
}
mDiskLruCache = null;
// initDiskCache();
}
}
}
/**
* Flushes the disk cache associated with this Cache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public void flush() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
if (Worker.debuggable > 0) {
Log.v(TAG, "Disk cache flushed");
}
} catch (IOException e) {
Log.e(TAG, "flush - " + e);
}
}
}
}
/**
* Closes the disk cache associated with this Cache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public void close() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
try {
if (!mDiskLruCache.isClosed()) {
mDiskLruCache.close();
mDiskLruCache = null;
if (Worker.debuggable > 0) {
Log.v(TAG, "Disk cache closed");
}
}
} catch (IOException e) {
Log.e(TAG, "close - " + e);
}
}
}
} }
/** /**
@@ -453,10 +241,6 @@ public class Cache {
*/ */
public static class CacheParams { public static class CacheParams {
public int memCacheSize = DEFAULT_MEM_CACHE_SIZE; public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
public File diskCacheDir;
public CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
public int compressQuality = DEFAULT_COMPRESS_QUALITY;
public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED; public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED; public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
@@ -470,7 +254,6 @@ public class Cache {
* is sufficient. * is sufficient.
*/ */
public CacheParams(Context context, String diskCacheDirectoryName) { public CacheParams(Context context, String diskCacheDirectoryName) {
diskCacheDir = getDiskCacheDir(context, diskCacheDirectoryName);
} }
/** /**
@@ -592,13 +375,11 @@ public class Cache {
* onward this returns the allocated memory size of the bitmap which can be larger than the * onward this returns the allocated memory size of the bitmap which can be larger than the
* actual bitmap data byte count (in the case it was re-used). * actual bitmap data byte count (in the case it was re-used).
* *
* @param value * @param bitmap
* @return size in bytes * @return size in bytes
*/ */
@TargetApi(VERSION_CODES.KITKAT) @TargetApi(VERSION_CODES.KITKAT)
public static int getBitmapSize(BitmapDrawable value) { public static int getBitmapSize(Bitmap bitmap) {
Bitmap bitmap = value.getBitmap();
// From KitKat onward use getAllocationByteCount() as allocated bytes can potentially be // From KitKat onward use getAllocationByteCount() as allocated bytes can potentially be
// larger than bitmap byte count. // larger than bitmap byte count.
if (Utils.hasKitKat()) { if (Utils.hasKitKat()) {

View File

@@ -18,8 +18,6 @@ package org.nativescript.widgets.image;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
@@ -28,6 +26,7 @@ import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@@ -37,14 +36,12 @@ import java.net.URL;
* A simple subclass of {@link Resizer} that fetch and resize images from a file, resource or URL. * A simple subclass of {@link Resizer} that fetch and resize images from a file, resource or URL.
*/ */
public class Fetcher extends Resizer { public class Fetcher extends Resizer {
private static final String RESOURCE_PREFIX = "res://";
private static final String FILE_PREFIX = "file:///";
private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
private static final String HTTP_CACHE_DIR = "http"; private static final String HTTP_CACHE_DIR = "http";
private static final int IO_BUFFER_SIZE = 8 * 1024; private static final int IO_BUFFER_SIZE = 8 * 1024;
private DiskLruCache mHttpDiskCache;
private File mHttpCacheDir; private File mHttpCacheDir;
private DiskLruCache mHttpDiskCache;
private boolean mHttpDiskCacheStarting = true; private boolean mHttpDiskCacheStarting = true;
private final Object mHttpDiskCacheLock = new Object(); private final Object mHttpDiskCacheLock = new Object();
private static final int DISK_CACHE_INDEX = 0; private static final int DISK_CACHE_INDEX = 0;
@@ -65,11 +62,6 @@ public class Fetcher extends Resizer {
@Override @Override
protected void initDiskCacheInternal() { protected void initDiskCacheInternal() {
super.initDiskCacheInternal();
initHttpDiskCache();
}
private void initHttpDiskCache() {
if (!mHttpCacheDir.exists()) { if (!mHttpCacheDir.exists()) {
mHttpCacheDir.mkdirs(); mHttpCacheDir.mkdirs();
} }
@@ -104,14 +96,12 @@ public class Fetcher extends Resizer {
} }
mHttpDiskCache = null; mHttpDiskCache = null;
mHttpDiskCacheStarting = true; mHttpDiskCacheStarting = true;
// initHttpDiskCache();
} }
} }
} }
@Override @Override
protected void flushCacheInternal() { protected void flushCacheInternal() {
super.flushCacheInternal();
synchronized (mHttpDiskCacheLock) { synchronized (mHttpDiskCacheLock) {
if (mHttpDiskCache != null) { if (mHttpDiskCache != null) {
try { try {
@@ -128,7 +118,6 @@ public class Fetcher extends Resizer {
@Override @Override
protected void closeCacheInternal() { protected void closeCacheInternal() {
super.closeCacheInternal();
synchronized (mHttpDiskCacheLock) { synchronized (mHttpDiskCacheLock) {
if (mHttpDiskCache != null) { if (mHttpDiskCache != null) {
try { try {
@@ -153,9 +142,9 @@ public class Fetcher extends Resizer {
* @param data The data to load the bitmap, in this case, a regular http URL * @param data The data to load the bitmap, in this case, a regular http URL
* @return The downloaded and resized bitmap * @return The downloaded and resized bitmap
*/ */
private Bitmap processBitmap(String data, int decodeWidth, int decodeHeight) { private Bitmap processHttp(String data, int decodeWidth, int decodeHeight) {
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "processBitmap - " + data); Log.v(TAG, "processHttp - " + data);
} }
final String key = Cache.hashKeyForDisk(data); final String key = Cache.hashKeyForDisk(data);
@@ -212,7 +201,7 @@ public class Fetcher extends Resizer {
Bitmap bitmap = null; Bitmap bitmap = null;
if (fileDescriptor != null) { if (fileDescriptor != null) {
bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, decodeWidth, bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, decodeWidth,
decodeHeight, getImageCache()); decodeHeight, getCache());
} }
if (fileInputStream != null) { if (fileInputStream != null) {
try { try {
@@ -223,34 +212,65 @@ public class Fetcher extends Resizer {
return bitmap; return bitmap;
} }
private Bitmap processHttpNoCache(String data, int decodeWidth, int decodeHeight) {
if (debuggable > 0) {
Log.v(TAG, "processHttp - " + data);
}
final String key = Cache.hashKeyForDisk(data);
FileOutputStream outputStream = null;
Bitmap bitmap = null;
try {
outputStream = new FileOutputStream(key);
if (downloadUrlToStream(data, outputStream)) {
bitmap = decodeSampledBitmapFromDescriptor(outputStream.getFD(), decodeWidth, decodeHeight, getCache());
}
} catch (IOException e) {
Log.e(TAG, "processBitmap - " + e);
} catch (IllegalStateException e) {
Log.e(TAG, "processBitmap - " + e);
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
}
}
}
return bitmap;
}
@Override @Override
protected Bitmap processBitmap(Object data, int decodeWidth, int decodeHeight) { protected Bitmap processBitmap(Object data, int decodeWidth, int decodeHeight, boolean useCache) {
if (data instanceof String) { if (data instanceof String) {
String stringData = String.valueOf(data); String stringData = String.valueOf(data);
if (stringData.startsWith(FILE_PREFIX)) { if (stringData.startsWith(FILE_PREFIX)) {
String filename = stringData.substring(FILE_PREFIX.length()); String filename = stringData.substring(FILE_PREFIX.length());
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "processBitmap - " + filename); Log.v(TAG, "processFile - " + filename);
} }
return decodeSampledBitmapFromFile(filename, decodeWidth, return decodeSampledBitmapFromFile(filename, decodeWidth, decodeHeight, getCache());
decodeHeight, getImageCache());
} else if (stringData.startsWith(RESOURCE_PREFIX)) { } else if (stringData.startsWith(RESOURCE_PREFIX)) {
String resPath = stringData.substring(RESOURCE_PREFIX.length()); String resPath = stringData.substring(RESOURCE_PREFIX.length());
int resId = mResources.getIdentifier(resPath, "drawable", mPackageName); int resId = mResources.getIdentifier(resPath, "drawable", mPackageName);
if (resId > 0) { if (resId > 0) {
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "processBitmap - " + resId); Log.v(TAG, "processResource - " + resId);
} }
return decodeSampledBitmapFromResource(mResources, resId, decodeWidth, return decodeSampledBitmapFromResource(mResources, resId, decodeWidth, decodeHeight, getCache());
decodeHeight, getImageCache());
} else { } else {
Log.v(TAG, "Missing ResourceID: " + stringData); Log.v(TAG, "Missing ResourceID: " + stringData);
} }
} else { } else {
return processBitmap(stringData, decodeWidth, decodeHeight); if (useCache) {
return processHttp(stringData, decodeWidth, decodeHeight);
} else {
return processHttpNoCache(stringData, decodeWidth, decodeHeight);
}
} }
} else { } else {
Log.v(TAG, "Invalid Value: " + String.valueOf(data) + ". Expecting String or Integer."); Log.v(TAG, "Invalid Value: " + String.valueOf(data));
} }
return null; return null;
@@ -292,7 +312,8 @@ public class Fetcher extends Resizer {
if (in != null) { if (in != null) {
in.close(); in.close();
} }
} catch (final IOException e) {} } catch (final IOException e) {
}
} }
return false; return false;
} }

View File

@@ -23,9 +23,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.util.Log; import android.util.Log;
import android.widget.ImageView; import android.widget.ImageView;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@@ -37,6 +35,9 @@ import java.lang.ref.WeakReference;
*/ */
public abstract class Worker { public abstract class Worker {
protected static final String RESOURCE_PREFIX = "res://";
protected static final String FILE_PREFIX = "file:///";
static final String TAG = "JS"; static final String TAG = "JS";
private static final int FADE_IN_TIME = 200; private static final int FADE_IN_TIME = 200;
@@ -78,7 +79,7 @@ public abstract class Worker {
/** /**
* Load an image specified by the data parameter into an ImageView (override * Load an image specified by the data parameter into an ImageView (override
* {@link Worker#processBitmap(Object, int, int)} to define the processing logic). A memory and * {@link Worker#processBitmap(Object, int, int, boolean)} to define the processing logic). A memory and
* disk cache will be used if an {@link Cache} has been added using * disk cache will be used if an {@link Cache} has been added using
* {@link Worker#addImageCache(Cache)}. If the * {@link Worker#addImageCache(Cache)}. If the
* image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask} * image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask}
@@ -93,8 +94,11 @@ public abstract class Worker {
return; return;
} }
BitmapDrawable value = null; Bitmap value = null;
String dataString = String.valueOf(data); String dataString = String.valueOf(data);
if (debuggable > 0) {
Log.d(TAG, "loadImage on: " + imageView + " to: " + dataString);
}
if (mCache != null && useCache) { if (mCache != null && useCache) {
value = mCache.getBitmapFromMemCache(dataString); value = mCache.getBitmapFromMemCache(dataString);
@@ -102,21 +106,25 @@ public abstract class Worker {
if (value == null && !async) { if (value == null && !async) {
// Decode sync. // Decode sync.
Bitmap bitmap = processBitmap(data, decodeWidth, decodeHeight); value = processBitmap(data, decodeWidth, decodeHeight, useCache);
if (bitmap != null) { if (value != null) {
value = new BitmapDrawable(mResources, bitmap);
if (mCache != null && useCache) { if (mCache != null && useCache) {
// Don't add Images loaded from Resources to disk cache. if (debuggable > 0) {
boolean addToDiskCache = !(data instanceof Number); Log.v(TAG, "loadImage.addBitmapToCache: " + imageView + ", src: " + dataString);
mCache.addBitmapToCache(dataString, value, addToDiskCache); }
mCache.addBitmapToCache(dataString, value);
} }
} }
} }
if (value != null) { if (value != null) {
// Bitmap found in memory cache // Bitmap found in memory cache
imageView.setImageDrawable(value); if (debuggable > 0) {
Log.d(TAG, "Set ImageBitmap on: " + imageView + " to: " + dataString);
}
imageView.setImageBitmap(value);
if (listener != null) { if (listener != null) {
Log.d(TAG, "OnImageLoadedListener on: " + imageView + " to: " + dataString);
listener.onImageLoaded(true); listener.onImageLoaded(true);
} }
} else if (cancelPotentialWork(data, imageView)) { } else if (cancelPotentialWork(data, imageView)) {
@@ -176,15 +184,15 @@ public abstract class Worker {
* example, you could resize a large bitmap here, or pull down an image from the network. * example, you could resize a large bitmap here, or pull down an image from the network.
* *
* @param data The data to identify which image to process, as provided by * @param data The data to identify which image to process, as provided by
* {@link Worker#loadImage(Object, ImageView)} * {@link Worker#loadImage(Object, ImageView, int, int, boolean, boolean, OnImageLoadedListener)}
* @return The processed bitmap * @return The processed bitmap
*/ */
protected abstract Bitmap processBitmap(Object data, int decodeWidth, int decodeHeight); protected abstract Bitmap processBitmap(Object data, int decodeWidth, int decodeHeight, boolean useCache);
/** /**
* @return The {@link Cache} object currently being used by this Worker. * @return The {@link Cache} object currently being used by this Worker.
*/ */
protected Cache getImageCache() { protected Cache getCache() {
return mCache; return mCache;
} }
@@ -246,7 +254,7 @@ public abstract class Worker {
/** /**
* The actual AsyncTask that will asynchronously process the image. * The actual AsyncTask that will asynchronously process the image.
*/ */
private class BitmapWorkerTask extends AsyncTask<Void, Void, BitmapDrawable> { private class BitmapWorkerTask extends AsyncTask<Void, Void, Bitmap> {
private int mDecodeWidth; private int mDecodeWidth;
private int mDecodeHeight; private int mDecodeHeight;
private Object mData; private Object mData;
@@ -271,14 +279,14 @@ public abstract class Worker {
* Background processing. * Background processing.
*/ */
@Override @Override
protected BitmapDrawable doInBackground(Void... params) { protected Bitmap doInBackground(Void... params) {
final String dataString = String.valueOf(mData);
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "doInBackground - starting work"); Log.v(TAG, "doInBackground - starting work: " + imageViewReference.get() + ", on: " + dataString);
} }
final String dataString = String.valueOf(mData);
Bitmap bitmap = null; Bitmap bitmap = null;
BitmapDrawable drawable = null;
// Wait here if work is paused and the task is not cancelled // Wait here if work is paused and the task is not cancelled
synchronized (mPauseWorkLock) { synchronized (mPauseWorkLock) {
@@ -289,22 +297,13 @@ public abstract class Worker {
} }
} }
// If the image cache is available and this task has not been cancelled by another
// thread and the ImageView that was originally bound to this task is still bound back
// to this task and our "exit early" flag is not set then try and fetch the bitmap from
// the cache
if (mCache != null && !isCancelled() && getAttachedImageView() != null
&& !mExitTasksEarly && mCacheImage) {
bitmap = mCache.getBitmapFromDiskCache(dataString);
}
// If the bitmap was not found in the cache and this task has not been cancelled by // If the bitmap was not found in the cache and this task has not been cancelled by
// another thread and the ImageView that was originally bound to this task is still // another thread and the ImageView that was originally bound to this task is still
// bound back to this task and our "exit early" flag is not set, then call the main // bound back to this task and our "exit early" flag is not set, then call the main
// process method (as implemented by a subclass) // process method (as implemented by a subclass)
if (bitmap == null && !isCancelled() && getAttachedImageView() != null if (bitmap == null && !isCancelled() && getAttachedImageView() != null
&& !mExitTasksEarly) { && !mExitTasksEarly) {
bitmap = processBitmap(mData, mDecodeWidth, mDecodeHeight); bitmap = processBitmap(mData, mDecodeWidth, mDecodeHeight, mCacheImage);
} }
// If the bitmap was processed and the image cache is available, then add the processed // If the bitmap was processed and the image cache is available, then add the processed
@@ -312,11 +311,11 @@ public abstract class Worker {
// here, if it was, and the thread is still running, we may as well add the processed // here, if it was, and the thread is still running, we may as well add the processed
// bitmap to our cache as it might be used again in the future // bitmap to our cache as it might be used again in the future
if (bitmap != null) { if (bitmap != null) {
drawable = new BitmapDrawable(mResources, bitmap);
if (mCache != null && mCacheImage) { if (mCache != null && mCacheImage) {
// Don't add Images loaded from Resources to disk cache. if (debuggable > 0) {
boolean addToDiskCache = !(mData instanceof Number); Log.v(TAG, "addBitmapToCache: " + imageViewReference.get() + ", src: " + dataString);
mCache.addBitmapToCache(dataString, drawable, addToDiskCache); }
mCache.addBitmapToCache(dataString, bitmap);
} }
} }
@@ -324,35 +323,48 @@ public abstract class Worker {
Log.v(TAG, "doInBackground - finished work"); Log.v(TAG, "doInBackground - finished work");
} }
return drawable; return bitmap;
} }
/** /**
* Once the image is processed, associates it to the imageView * Once the image is processed, associates it to the imageView
*/ */
@Override @Override
protected void onPostExecute(BitmapDrawable value) { protected void onPostExecute(Bitmap value) {
boolean success = false; boolean success = false;
// if cancel was called on this task or the "exit early" flag is set then we're done // if cancel was called on this task or the "exit early" flag is set then we're done
if (isCancelled() || mExitTasksEarly) { if (isCancelled() || mExitTasksEarly) {
value = null; value = null;
} }
final ImageView imageView = getAttachedImageView(); final String dataString = String.valueOf(mData);
if (value != null && imageView != null) {
if (debuggable > 0) { if (debuggable > 0) {
Log.v(TAG, "onPostExecute - setting bitmap"); Log.v(TAG, "onPostExecute - setting bitmap for: " + imageViewReference.get() + " src: " + dataString);
} }
final ImageView imageView = getAttachedImageView();
if (debuggable > 0) {
Log.v(TAG, "onPostExecute - current ImageView: " + imageView);
}
if (value != null && imageView != null) {
success = true; success = true;
setImageDrawable(imageView, value); if (debuggable > 0) {
Log.d(TAG, "Set ImageDrawable on: " + imageView + " to: " + dataString);
} }
imageView.setImageBitmap(value);
}
if (mOnImageLoadedListener != null) { if (mOnImageLoadedListener != null) {
if (debuggable > 0) {
Log.d(TAG, "OnImageLoadedListener on: " + imageView + " to: " + dataString);
}
mOnImageLoadedListener.onImageLoaded(success); mOnImageLoadedListener.onImageLoaded(success);
} }
} }
@Override @Override
protected void onCancelled(BitmapDrawable value) { protected void onCancelled(Bitmap value) {
super.onCancelled(value); super.onCancelled(value);
synchronized (mPauseWorkLock) { synchronized (mPauseWorkLock) {
mPauseWorkLock.notifyAll(); mPauseWorkLock.notifyAll();
@@ -408,31 +420,31 @@ public abstract class Worker {
} }
} }
/** // /**
* Called when the processing is complete and the final drawable should be // * Called when the processing is complete and the final drawable should be
* set on the ImageView. // * set on the ImageView.
* // *
* @param imageView // * @param imageView
* @param drawable // * @param bitmap
*/ // */
private void setImageDrawable(ImageView imageView, Drawable drawable) { // private void setImageDrawable(ImageView imageView, Bitmap bitmap) {
if (mFadeInBitmap) { // if (mFadeInBitmap) {
// Transition drawable with a transparent drawable and the final drawable // // Transition drawable with a transparent drawable and the final drawable
final TransitionDrawable td = // final TransitionDrawable td =
new TransitionDrawable(new Drawable[] { // new TransitionDrawable(new Drawable[] {
new ColorDrawable(0), // new ColorDrawable(0),
drawable // new BitmapDrawable(bitmap)
}); // });
// Set background to loading bitmap // // Set background to loading bitmap
imageView.setBackgroundDrawable( // imageView.setBackgroundDrawable(
new BitmapDrawable(mResources, mLoadingBitmap)); // new BitmapDrawable(mResources, mLoadingBitmap));
//
imageView.setImageDrawable(td); // imageView.setImageDrawable(td);
td.startTransition(FADE_IN_TIME); // td.startTransition(FADE_IN_TIME);
} else { // } else {
imageView.setImageDrawable(drawable); // imageView.setImageBitmap(bitmap);
} // }
} // }
/** /**
* Pause any ongoing background work. This can be used as a temporary * Pause any ongoing background work. This can be used as a temporary
@@ -477,30 +489,17 @@ public abstract class Worker {
} }
} }
protected void initDiskCacheInternal() {
if (mCache != null) {
mCache.initDiskCache();
}
}
protected void clearCacheInternal() { protected void clearCacheInternal() {
if (mCache != null) { if (mCache != null) {
mCache.clearCache(); mCache.clearCache();
} }
} }
protected void flushCacheInternal() { protected abstract void initDiskCacheInternal();
if (mCache != null) {
mCache.flush();
}
}
protected void closeCacheInternal() { protected abstract void flushCacheInternal();
if (mCache != null) {
mCache.close(); protected abstract void closeCacheInternal();
mCache = null;
}
}
public void initCache() { public void initCache() {
new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE); new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);

View File

@@ -5,8 +5,8 @@ package org.nativescript.widgets;
import android.content.Context; import android.content.Context;
import android.graphics.*; import android.graphics.*;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.Log;
/** /**
* @author hhristov * @author hhristov
@@ -29,6 +29,7 @@ public class ImageView extends android.widget.ImageView {
public void setRotationAngle(float rotationAngle) { public void setRotationAngle(float rotationAngle) {
this.rotationAngle = rotationAngle; this.rotationAngle = rotationAngle;
invalidate();
} }
public ImageView(Context context) { public ImageView(Context context) {
@@ -140,6 +141,14 @@ public class ImageView extends android.widget.ImageView {
this.pBitmap = bm; this.pBitmap = bm;
} }
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
if (drawable instanceof BitmapDrawable) {
this.pBitmap = ((BitmapDrawable)drawable).getBitmap();
}
}
@Override @Override
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
BorderDrawable background = this.getBackground() instanceof BorderDrawable ? (BorderDrawable)this.getBackground() : null; BorderDrawable background = this.getBackground() instanceof BorderDrawable ? (BorderDrawable)this.getBackground() : null;
@@ -150,6 +159,10 @@ public class ImageView extends android.widget.ImageView {
float roundedBorderWidth = (float) Math.floor(uniformBorderWidth); float roundedBorderWidth = (float) Math.floor(uniformBorderWidth);
float innerRadius = Math.max(0, uniformBorderRadius - roundedBorderWidth); float innerRadius = Math.max(0, uniformBorderRadius - roundedBorderWidth);
if (background != null) {
background.draw(canvas);
}
// The border width is included in the padding so there is no need for // The border width is included in the padding so there is no need for
// clip if there is no inner border radius. // clip if there is no inner border radius.
if (innerRadius > 0) { if (innerRadius > 0) {
@@ -215,9 +228,5 @@ public class ImageView extends android.widget.ImageView {
else { else {
super.onDraw(canvas); super.onDraw(canvas);
} }
if (background != null) {
background.draw(canvas);
}
} }
} }