From 626e148e2b60a3e5eb5a2e861b8c23539d459082 Mon Sep 17 00:00:00 2001 From: Hristo Hristov Date: Thu, 13 Oct 2016 16:39:15 +0300 Subject: [PATCH] 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). --- .../org/nativescript/widgets/Image/Cache.java | 253 ++---------------- .../nativescript/widgets/Image/Fetcher.java | 73 +++-- .../nativescript/widgets/Image/Worker.java | 169 ++++++------ .../org/nativescript/widgets/ImageView.java | 19 +- 4 files changed, 162 insertions(+), 352 deletions(-) diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Cache.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Cache.java index 632ac7d36..17b8998d2 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Cache.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Cache.java @@ -19,10 +19,8 @@ package org.nativescript.widgets.image; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; import android.os.Build.VERSION_CODES; import android.os.Environment; import android.os.StatFs; @@ -30,11 +28,6 @@ import android.support.v4.util.LruCache; import android.util.Log; 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.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -56,32 +49,16 @@ public class Cache { // Default memory cache size in kilobytes 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 private static final boolean DEFAULT_MEM_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 DiskLruCache mDiskLruCache; - private LruCache mMemoryCache; + private LruCache mMemoryCache; private CacheParams mParams; - private final Object mDiskCacheLock = new Object(); - private boolean mDiskCacheStarting = true; private Set> mReusableBitmaps; - private Fetcher mFetcher; - /** * Create a new Cache object using the specified parameters. This should not be * called directly by other classes, instead use @@ -102,7 +79,6 @@ public class Cache { public static Cache getInstance(CacheParams cacheParams) { if (instance == null) { instance = new Cache(cacheParams); - instance.init(cacheParams); } else if (instance.mParams != cacheParams) { instance.init(cacheParams); @@ -119,7 +95,6 @@ public class Cache { private void init(CacheParams cacheParams) { mParams = cacheParams; - //BEGIN_INCLUDE(init_memory_cache) // Set up memory cache if (mParams.memoryCacheEnabled) { if (Worker.debuggable > 0) { @@ -140,18 +115,18 @@ public class Cache { Collections.synchronizedSet(new HashSet>()); } - mMemoryCache = new LruCache(mParams.memCacheSize) { + mMemoryCache = new LruCache(mParams.memCacheSize) { /** * Notify the removed entry that is no longer being cached */ @Override protected void entryRemoved(boolean evicted, String key, - BitmapDrawable oldValue, BitmapDrawable newValue) { + Bitmap oldValue, Bitmap newValue) { if (Utils.hasHoneycomb()) { // We're running on Honeycomb or later, so add the bitmap // to a SoftReference set for possible use with inBitmap later - mReusableBitmaps.add(new SoftReference(oldValue.getBitmap())); + mReusableBitmaps.add(new SoftReference(oldValue)); } } @@ -160,54 +135,12 @@ public class Cache { * for a bitmap cache */ @Override - protected int sizeOf(String key, BitmapDrawable value) { + protected int sizeOf(String key, Bitmap value) { final int bitmapSize = getBitmapSize(value) / 1024; 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 value The bitmap drawable to store */ - public void addBitmapToCache(String data, BitmapDrawable value, boolean useDiskCache) { - //BEGIN_INCLUDE(add_bitmap_to_cache) + public void addBitmapToCache(String data, Bitmap value) { if (data == null || value == null) { return; } // Add to memory cache if (mMemoryCache != null) { - 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) { - } - } + 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); } } - //END_INCLUDE(add_bitmap_to_cache) } /** * Get from memory cache. * * @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) { - //BEGIN_INCLUDE(get_bitmap_from_mem_cache) - BitmapDrawable memValue = null; + public Bitmap getBitmapFromMemCache(String data) { + Bitmap memValue = null; if (mMemoryCache != null) { memValue = mMemoryCache.get(data); @@ -286,59 +184,6 @@ public class Cache { } 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"); } } - - 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 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 diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED; @@ -470,7 +254,6 @@ public class Cache { * is sufficient. */ 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 * actual bitmap data byte count (in the case it was re-used). * - * @param value + * @param bitmap * @return size in bytes */ @TargetApi(VERSION_CODES.KITKAT) - public static int getBitmapSize(BitmapDrawable value) { - Bitmap bitmap = value.getBitmap(); - + public static int getBitmapSize(Bitmap bitmap) { // From KitKat onward use getAllocationByteCount() as allocated bytes can potentially be // larger than bitmap byte count. if (Utils.hasKitKat()) { diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java index 8dc8bdb37..98d525ab3 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java @@ -18,8 +18,6 @@ package org.nativescript.widgets.image; import android.content.Context; import android.graphics.Bitmap; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.os.Build; import android.util.Log; @@ -28,6 +26,7 @@ import java.io.BufferedOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; 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. */ 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 String HTTP_CACHE_DIR = "http"; private static final int IO_BUFFER_SIZE = 8 * 1024; - private DiskLruCache mHttpDiskCache; private File mHttpCacheDir; + private DiskLruCache mHttpDiskCache; private boolean mHttpDiskCacheStarting = true; private final Object mHttpDiskCacheLock = new Object(); private static final int DISK_CACHE_INDEX = 0; @@ -65,11 +62,6 @@ public class Fetcher extends Resizer { @Override protected void initDiskCacheInternal() { - super.initDiskCacheInternal(); - initHttpDiskCache(); - } - - private void initHttpDiskCache() { if (!mHttpCacheDir.exists()) { mHttpCacheDir.mkdirs(); } @@ -104,14 +96,12 @@ public class Fetcher extends Resizer { } mHttpDiskCache = null; mHttpDiskCacheStarting = true; -// initHttpDiskCache(); } } } @Override protected void flushCacheInternal() { - super.flushCacheInternal(); synchronized (mHttpDiskCacheLock) { if (mHttpDiskCache != null) { try { @@ -128,7 +118,6 @@ public class Fetcher extends Resizer { @Override protected void closeCacheInternal() { - super.closeCacheInternal(); synchronized (mHttpDiskCacheLock) { if (mHttpDiskCache != null) { 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 * @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) { - Log.v(TAG, "processBitmap - " + data); + Log.v(TAG, "processHttp - " + data); } final String key = Cache.hashKeyForDisk(data); @@ -212,7 +201,7 @@ public class Fetcher extends Resizer { Bitmap bitmap = null; if (fileDescriptor != null) { bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, decodeWidth, - decodeHeight, getImageCache()); + decodeHeight, getCache()); } if (fileInputStream != null) { try { @@ -223,34 +212,65 @@ public class Fetcher extends Resizer { 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 - protected Bitmap processBitmap(Object data, int decodeWidth, int decodeHeight) { + protected Bitmap processBitmap(Object data, int decodeWidth, int decodeHeight, boolean useCache) { if (data instanceof String) { String stringData = String.valueOf(data); if (stringData.startsWith(FILE_PREFIX)) { String filename = stringData.substring(FILE_PREFIX.length()); if (debuggable > 0) { - Log.v(TAG, "processBitmap - " + filename); + Log.v(TAG, "processFile - " + filename); } - return decodeSampledBitmapFromFile(filename, decodeWidth, - decodeHeight, getImageCache()); + return decodeSampledBitmapFromFile(filename, decodeWidth, decodeHeight, getCache()); } else if (stringData.startsWith(RESOURCE_PREFIX)) { String resPath = stringData.substring(RESOURCE_PREFIX.length()); int resId = mResources.getIdentifier(resPath, "drawable", mPackageName); if (resId > 0) { if (debuggable > 0) { - Log.v(TAG, "processBitmap - " + resId); + Log.v(TAG, "processResource - " + resId); } - return decodeSampledBitmapFromResource(mResources, resId, decodeWidth, - decodeHeight, getImageCache()); + return decodeSampledBitmapFromResource(mResources, resId, decodeWidth, decodeHeight, getCache()); } else { Log.v(TAG, "Missing ResourceID: " + stringData); } } else { - return processBitmap(stringData, decodeWidth, decodeHeight); + if (useCache) { + return processHttp(stringData, decodeWidth, decodeHeight); + } else { + return processHttpNoCache(stringData, decodeWidth, decodeHeight); + } } } else { - Log.v(TAG, "Invalid Value: " + String.valueOf(data) + ". Expecting String or Integer."); + Log.v(TAG, "Invalid Value: " + String.valueOf(data)); } return null; @@ -292,7 +312,8 @@ public class Fetcher extends Resizer { if (in != null) { in.close(); } - } catch (final IOException e) {} + } catch (final IOException e) { + } } return false; } diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Worker.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Worker.java index f21028457..d15cb1787 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Worker.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Worker.java @@ -23,9 +23,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; import android.util.Log; import android.widget.ImageView; import java.lang.ref.WeakReference; @@ -37,6 +35,9 @@ import java.lang.ref.WeakReference; */ public abstract class Worker { + protected static final String RESOURCE_PREFIX = "res://"; + protected static final String FILE_PREFIX = "file:///"; + static final String TAG = "JS"; 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 - * {@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 * {@link Worker#addImageCache(Cache)}. If the * image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask} @@ -93,8 +94,11 @@ public abstract class Worker { return; } - BitmapDrawable value = null; + Bitmap value = null; String dataString = String.valueOf(data); + if (debuggable > 0) { + Log.d(TAG, "loadImage on: " + imageView + " to: " + dataString); + } if (mCache != null && useCache) { value = mCache.getBitmapFromMemCache(dataString); @@ -102,21 +106,25 @@ public abstract class Worker { if (value == null && !async) { // Decode sync. - Bitmap bitmap = processBitmap(data, decodeWidth, decodeHeight); - if (bitmap != null) { - value = new BitmapDrawable(mResources, bitmap); + value = processBitmap(data, decodeWidth, decodeHeight, useCache); + if (value != null) { if (mCache != null && useCache) { - // Don't add Images loaded from Resources to disk cache. - boolean addToDiskCache = !(data instanceof Number); - mCache.addBitmapToCache(dataString, value, addToDiskCache); + if (debuggable > 0) { + Log.v(TAG, "loadImage.addBitmapToCache: " + imageView + ", src: " + dataString); + } + mCache.addBitmapToCache(dataString, value); } } } if (value != null) { // 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) { + Log.d(TAG, "OnImageLoadedListener on: " + imageView + " to: " + dataString); listener.onImageLoaded(true); } } 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. * * @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 */ - 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. */ - protected Cache getImageCache() { + protected Cache getCache() { return mCache; } @@ -246,7 +254,7 @@ public abstract class Worker { /** * The actual AsyncTask that will asynchronously process the image. */ - private class BitmapWorkerTask extends AsyncTask { + private class BitmapWorkerTask extends AsyncTask { private int mDecodeWidth; private int mDecodeHeight; private Object mData; @@ -271,14 +279,14 @@ public abstract class Worker { * Background processing. */ @Override - protected BitmapDrawable doInBackground(Void... params) { + protected Bitmap doInBackground(Void... params) { + final String dataString = String.valueOf(mData); 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; - BitmapDrawable drawable = null; // Wait here if work is paused and the task is not cancelled 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 // 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 // process method (as implemented by a subclass) if (bitmap == null && !isCancelled() && getAttachedImageView() != null && !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 @@ -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 // bitmap to our cache as it might be used again in the future if (bitmap != null) { - drawable = new BitmapDrawable(mResources, bitmap); if (mCache != null && mCacheImage) { - // Don't add Images loaded from Resources to disk cache. - boolean addToDiskCache = !(mData instanceof Number); - mCache.addBitmapToCache(dataString, drawable, addToDiskCache); + if (debuggable > 0) { + Log.v(TAG, "addBitmapToCache: " + imageViewReference.get() + ", src: " + dataString); + } + mCache.addBitmapToCache(dataString, bitmap); } } @@ -324,35 +323,48 @@ public abstract class Worker { Log.v(TAG, "doInBackground - finished work"); } - return drawable; + return bitmap; } /** * Once the image is processed, associates it to the imageView */ @Override - protected void onPostExecute(BitmapDrawable value) { + protected void onPostExecute(Bitmap value) { boolean success = false; // if cancel was called on this task or the "exit early" flag is set then we're done if (isCancelled() || mExitTasksEarly) { value = null; } - final ImageView imageView = getAttachedImageView(); - if (value != null && imageView != null) { - if (debuggable > 0) { - Log.v(TAG, "onPostExecute - setting bitmap"); - } - success = true; - setImageDrawable(imageView, value); + final String dataString = String.valueOf(mData); + if (debuggable > 0) { + 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; + if (debuggable > 0) { + Log.d(TAG, "Set ImageDrawable on: " + imageView + " to: " + dataString); + } + imageView.setImageBitmap(value); + } + if (mOnImageLoadedListener != null) { + if (debuggable > 0) { + Log.d(TAG, "OnImageLoadedListener on: " + imageView + " to: " + dataString); + } mOnImageLoadedListener.onImageLoaded(success); } } @Override - protected void onCancelled(BitmapDrawable value) { + protected void onCancelled(Bitmap value) { super.onCancelled(value); synchronized (mPauseWorkLock) { mPauseWorkLock.notifyAll(); @@ -408,31 +420,31 @@ public abstract class Worker { } } - /** - * Called when the processing is complete and the final drawable should be - * set on the ImageView. - * - * @param imageView - * @param drawable - */ - private void setImageDrawable(ImageView imageView, Drawable drawable) { - if (mFadeInBitmap) { - // Transition drawable with a transparent drawable and the final drawable - final TransitionDrawable td = - new TransitionDrawable(new Drawable[] { - new ColorDrawable(0), - drawable - }); - // Set background to loading bitmap - imageView.setBackgroundDrawable( - new BitmapDrawable(mResources, mLoadingBitmap)); - - imageView.setImageDrawable(td); - td.startTransition(FADE_IN_TIME); - } else { - imageView.setImageDrawable(drawable); - } - } +// /** +// * Called when the processing is complete and the final drawable should be +// * set on the ImageView. +// * +// * @param imageView +// * @param bitmap +// */ +// private void setImageDrawable(ImageView imageView, Bitmap bitmap) { +// if (mFadeInBitmap) { +// // Transition drawable with a transparent drawable and the final drawable +// final TransitionDrawable td = +// new TransitionDrawable(new Drawable[] { +// new ColorDrawable(0), +// new BitmapDrawable(bitmap) +// }); +// // Set background to loading bitmap +// imageView.setBackgroundDrawable( +// new BitmapDrawable(mResources, mLoadingBitmap)); +// +// imageView.setImageDrawable(td); +// td.startTransition(FADE_IN_TIME); +// } else { +// imageView.setImageBitmap(bitmap); +// } +// } /** * 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() { if (mCache != null) { mCache.clearCache(); } } - protected void flushCacheInternal() { - if (mCache != null) { - mCache.flush(); - } - } + protected abstract void initDiskCacheInternal(); - protected void closeCacheInternal() { - if (mCache != null) { - mCache.close(); - mCache = null; - } - } + protected abstract void flushCacheInternal(); + + protected abstract void closeCacheInternal(); public void initCache() { new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE); diff --git a/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java b/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java index d13353527..bccb40933 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java @@ -5,8 +5,8 @@ package org.nativescript.widgets; import android.content.Context; import android.graphics.*; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.util.Log; /** * @author hhristov @@ -29,6 +29,7 @@ public class ImageView extends android.widget.ImageView { public void setRotationAngle(float rotationAngle) { this.rotationAngle = rotationAngle; + invalidate(); } public ImageView(Context context) { @@ -140,6 +141,14 @@ public class ImageView extends android.widget.ImageView { this.pBitmap = bm; } + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + if (drawable instanceof BitmapDrawable) { + this.pBitmap = ((BitmapDrawable)drawable).getBitmap(); + } + } + @Override protected void onDraw(Canvas canvas) { 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 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 // clip if there is no inner border radius. if (innerRadius > 0) { @@ -215,9 +228,5 @@ public class ImageView extends android.widget.ImageView { else { super.onDraw(canvas); } - - if (background != null) { - background.draw(canvas); - } } } \ No newline at end of file