diff --git a/library/src/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java b/library/src/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java index 1c22727f9..f59cbfee1 100644 --- a/library/src/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java +++ b/library/src/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java @@ -16,7 +16,12 @@ package com.nostra13.universalimageloader.core; import java.io.File; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import android.content.Context; @@ -32,6 +37,8 @@ import com.nostra13.universalimageloader.cache.memory.impl.FuzzyKeyMemoryCache; import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache; import com.nostra13.universalimageloader.core.assist.MemoryCacheUtil; +import com.nostra13.universalimageloader.core.assist.QueueProcessingType; +import com.nostra13.universalimageloader.core.assist.deque.LIFOLinkedBlockingDeque; import com.nostra13.universalimageloader.core.display.BitmapDisplayer; import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer; import com.nostra13.universalimageloader.core.download.BaseImageDownloader; @@ -46,6 +53,13 @@ */ public class DefaultConfigurationFactory { + /** Creates default implementation of task executor */ + public static Executor createExecutor(int threadPoolSize, int threadPriority, QueueProcessingType tasksProcessingType) { + boolean lifo = tasksProcessingType == QueueProcessingType.LIFO; + BlockingQueue taskQueue = lifo ? new LIFOLinkedBlockingDeque() : new LinkedBlockingQueue(); + return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue, createThreadFactory(threadPriority)); + } + /** Creates {@linkplain HashCodeFileNameGenerator default implementation} of FileNameGenerator */ public static FileNameGenerator createFileNameGenerator() { return new HashCodeFileNameGenerator(); @@ -94,7 +108,8 @@ public static BitmapDisplayer createBitmapDisplayer() { return new SimpleBitmapDisplayer(); } - public static ThreadFactory createThreadFactory(int threadPriority) { + /** Creates default implementation of {@linkplain ThreadFactory thread factory} for task executor */ + private static ThreadFactory createThreadFactory(int threadPriority) { return new DefaultThreadFactory(threadPriority); } diff --git a/library/src/com/nostra13/universalimageloader/core/ImageLoader.java b/library/src/com/nostra13/universalimageloader/core/ImageLoader.java index 73122d111..0c2013465 100644 --- a/library/src/com/nostra13/universalimageloader/core/ImageLoader.java +++ b/library/src/com/nostra13/universalimageloader/core/ImageLoader.java @@ -48,6 +48,8 @@ public class ImageLoader { public static final String TAG = ImageLoader.class.getSimpleName(); + static final String LOG_INIT_CONFIG = "Initialize ImageLoader with configuration"; + static final String LOG_DESTROY = "Destroy ImageLoader"; static final String LOG_WAITING_FOR_RESUME = "ImageLoader is paused. Waiting... [%s]"; static final String LOG_RESUME_AFTER_PAUSE = ".. Resume loading [%s]"; static final String LOG_DELAY_BEFORE_LOADING = "Delay %d ms before loading... [%s]"; @@ -68,6 +70,8 @@ public class ImageLoader { static final String LOG_TASK_INTERRUPTED = "Task was interrupted [%s]"; static final String LOG_CANT_DECODE_IMAGE = "Image can't be decoded [%s]"; + private static final String WARNING_RE_INIT_CONFIG = "Try to initialize ImageLoader which had already been initialized before. " + + "To re-init ImageLoader with new configuration call ImageLoader.destroy() at first."; private static final String ERROR_WRONG_ARGUMENTS = "Wrong arguments were passed to displayImage() method (ImageView reference must not be null)"; private static final String ERROR_NOT_INIT = "ImageLoader must be init with configuration before using"; private static final String ERROR_INIT_CONFIG_WITH_NULL = "ImageLoader configuration can not be initialized with null"; @@ -96,8 +100,9 @@ protected ImageLoader() { } /** - * Initializes ImageLoader's singleton instance with configuration. Method should be called once (each - * following call will have no effect)
+ * Initializes ImageLoader instance with configuration.
+ * If configurations was set before ( {@link #isInited()} == true) then this method does nothing.
+ * To force initialization with new configuration you should {@linkplain #destroy() destroy ImageLoader} at first. * * @param configuration {@linkplain ImageLoaderConfiguration ImageLoader configuration} * @throws IllegalArgumentException if configuration parameter is null @@ -107,8 +112,11 @@ public synchronized void init(ImageLoaderConfiguration configuration) { throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL); } if (this.configuration == null) { + if (configuration.loggingEnabled) L.d(LOG_INIT_CONFIG); engine = new ImageLoaderEngine(configuration); this.configuration = configuration; + } else { + L.w(WARNING_RE_INIT_CONFIG); } } @@ -444,9 +452,20 @@ public void resume() { engine.resume(); } - /** Stops all running display image tasks, discards all other scheduled tasks */ - public void stop() { - engine.stop(); + /** + * Clears current configuration. Stops all running display image tasks, discards all other scheduled tasks (true for + * built-in task executors, false - for + * {@linkplain ImageLoaderConfiguration.Builder#taskExecutor(java.util.concurrent.ExecutorService) custom task + * executors}).
+ *
+ * You can {@linkplain #init(ImageLoaderConfiguration) init} ImageLoader with new configuration after calling this + * method. + */ + public void destroy() { + if (configuration != null && configuration.loggingEnabled) L.d(LOG_DESTROY); + engine.destroy(); + engine = null; + configuration = null; } /** diff --git a/library/src/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java b/library/src/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java index 1324d1846..f984d5646 100644 --- a/library/src/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java +++ b/library/src/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java @@ -15,6 +15,8 @@ *******************************************************************************/ package com.nostra13.universalimageloader.core; +import java.util.concurrent.Executor; + import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; @@ -54,15 +56,20 @@ public final class ImageLoaderConfiguration { final CompressFormat imageCompressFormatForDiscCache; final int imageQualityForDiscCache; + final Executor taskExecutor; + final Executor taskExecutorForCachedImages; + final boolean customExecutor; + final boolean customExecutorForCachedImages; + final int threadPoolSize; - final boolean handleOutOfMemory; + final int threadPriority; final QueueProcessingType tasksProcessingType; + final boolean handleOutOfMemory; final MemoryCacheAware memoryCache; final DiscCacheAware discCache; final ImageDownloader downloader; final DisplayImageOptions defaultDisplayImageOptions; - final int threadPriority; final boolean loggingEnabled; final DiscCacheAware reserveDiscCache; @@ -77,15 +84,20 @@ private ImageLoaderConfiguration(final Builder builder) { maxImageHeightForDiscCache = builder.maxImageHeightForDiscCache; imageCompressFormatForDiscCache = builder.imageCompressFormatForDiscCache; imageQualityForDiscCache = builder.imageQualityForDiscCache; + taskExecutor = builder.taskExecutor; + taskExecutorForCachedImages = builder.taskExecutorForCachedImages; threadPoolSize = builder.threadPoolSize; + threadPriority = builder.threadPriority; + tasksProcessingType = builder.tasksProcessingType; handleOutOfMemory = builder.handleOutOfMemory; discCache = builder.discCache; memoryCache = builder.memoryCache; defaultDisplayImageOptions = builder.defaultDisplayImageOptions; loggingEnabled = builder.loggingEnabled; downloader = builder.downloader; - tasksProcessingType = builder.tasksProcessingType; - threadPriority = builder.threadPriority; + + customExecutor = builder.customExecutor; + customExecutorForCachedImages = builder.customExecutorForCachedImages; networkDeniedDownloader = new NetworkDeniedImageDownloader(downloader); slowNetworkDownloader = new SlowNetworkImageDownloader(downloader); @@ -125,12 +137,11 @@ public static ImageLoaderConfiguration createDefault(Context context) { */ public static class Builder { - private static final String WARNING_OVERLAP_MEMORY_CACHE_SIZE = "This method's call overlaps memoryCacheSize() method call"; - private static final String WARNING_MEMORY_CACHE_ALREADY_SET = "You already have set memory cache. This method call will make no effect."; - private static final String WARNING_OVERLAP_DISC_CACHE_SIZE = "This method's call overlaps discCacheSize() method call"; - private static final String WARNING_OVERLAP_DISC_CACHE_FILE_COUNT = "This method's call overlaps discCacheFileCount() method call"; - private static final String WARNING_OVERLAP_DISC_CACHE_FILE_NAME_GENERATOR = "This method's call overlaps discCacheFileNameGenerator() method call"; - private static final String WARNING_DISC_CACHE_ALREADY_SET = "You already have set disc cache. This method call will make no effect."; + private static final String WARNING_OVERLAP_DISC_CACHE_PARAMS = "discCache(), discCacheSize() and discCacheFileCount calls overlap each other"; + private static final String WARNING_OVERLAP_DISC_CACHE_NAME_GENERATOR = "discCache() and discCacheFileNameGenerator() calls overlap each other"; + private static final String WARNING_OVERLAP_MEMORY_CACHE = "memoryCache() and memoryCacheSize() calls overlap each other"; + private static final String WARNING_OVERLAP_EXECUTOR = "threadPoolSize(), threadPriority() and tasksProcessingOrder() calls " + + "can overlap taskExecutor() and taskExecutorForCachedImages() calls."; /** {@value} */ public static final int DEFAULT_THREAD_POOL_SIZE = 3; @@ -138,6 +149,7 @@ public static class Builder { public static final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY - 1; /** {@value} */ public static final int DEFAULT_MEMORY_CACHE_SIZE = 2 * 1024 * 1024; // bytes + public static final QueueProcessingType DEFAULT_TASK_PROCESSING_TYPE = QueueProcessingType.FIFO; private Context context; @@ -148,11 +160,16 @@ public static class Builder { private CompressFormat imageCompressFormatForDiscCache = null; private int imageQualityForDiscCache = 0; + private Executor taskExecutor = null; + private Executor taskExecutorForCachedImages = null; + private boolean customExecutor = false; + private boolean customExecutorForCachedImages = false; + private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE; private int threadPriority = DEFAULT_THREAD_PRIORITY; private boolean denyCacheImageMultipleSizesInMemory = false; private boolean handleOutOfMemory = true; - private QueueProcessingType tasksProcessingType = QueueProcessingType.FIFO; + private QueueProcessingType tasksProcessingType = DEFAULT_TASK_PROCESSING_TYPE; private int memoryCacheSize = DEFAULT_MEMORY_CACHE_SIZE; private int discCacheSize = 0; @@ -203,21 +220,78 @@ public Builder discCacheExtraOptions(int maxImageWidthForDiscCache, int maxImage return this; } + /** + * Sets custom {@linkplain Executor executor} for tasks of loading and displaying images.
+ *
+ * NOTE: If you set custom executor then following configuration options will not be considered for this + * executor: + *
    + *
  • {@link #threadPoolSize(int)}
  • + *
  • {@link #threadPriority(int)}
  • + *
  • {@link #tasksProcessingOrder(QueueProcessingType)}
  • + *
+ * + * @see #taskExecutorForCachedImages(Executor) + */ + public Builder taskExecutor(Executor executor) { + if (threadPoolSize != DEFAULT_THREAD_POOL_SIZE || threadPriority != DEFAULT_THREAD_PRIORITY || tasksProcessingType != DEFAULT_TASK_PROCESSING_TYPE) { + L.w(WARNING_OVERLAP_EXECUTOR); + } + + this.taskExecutor = executor; + return this; + } + + /** + * Sets custom {@linkplain Executor executor} for tasks of displaying cached on disc images (these tasks + * are executed quickly so UIL prefer to use separate executor for them).
+ *
+ * If you set the same executor for {@linkplain #taskExecutor(Executor) general tasks} and + * {@linkplain #taskExecutorForCachedImages(Executor) tasks about cached images} then these tasks will be in the + * same thread pool. So short-lived tasks can wait a long time for their turn.
+ *
+ * NOTE: If you set custom executor then following configuration options will not be considered for this + * executor: + *
    + *
  • {@link #threadPoolSize(int)}
  • + *
  • {@link #threadPriority(int)}
  • + *
  • {@link #tasksProcessingOrder(QueueProcessingType)}
  • + *
+ * + * @see #taskExecutor(Executor) + */ + public Builder taskExecutorForCachedImages(Executor executorForCachedImages) { + if (threadPoolSize != DEFAULT_THREAD_POOL_SIZE || threadPriority != DEFAULT_THREAD_PRIORITY || tasksProcessingType != DEFAULT_TASK_PROCESSING_TYPE) { + L.w(WARNING_OVERLAP_EXECUTOR); + } + + this.taskExecutorForCachedImages = executorForCachedImages; + return this; + } + /** * Sets thread pool size for image display tasks.
* Default value - {@link #DEFAULT_THREAD_POOL_SIZE this} * */ public Builder threadPoolSize(int threadPoolSize) { + if (taskExecutor != null || taskExecutorForCachedImages != null) { + L.w(WARNING_OVERLAP_EXECUTOR); + } + this.threadPoolSize = threadPoolSize; return this; } /** - * Sets the priority for image loading threads. Must be NOT greater than {@link Thread#MAX_PRIORITY} or + * Sets the priority for image loading threads. Should be NOT greater than {@link Thread#MAX_PRIORITY} or * less than {@link Thread#MIN_PRIORITY}
* Default value - {@link #DEFAULT_THREAD_PRIORITY this} * */ public Builder threadPriority(int threadPriority) { + if (taskExecutor != null || taskExecutorForCachedImages != null) { + L.w(WARNING_OVERLAP_EXECUTOR); + } + if (threadPriority < Thread.MIN_PRIORITY) { this.threadPriority = Thread.MIN_PRIORITY; } else { @@ -259,6 +333,10 @@ public Builder offOutOfMemoryHandling() { * Default value - {@link QueueProcessingType#FIFO} */ public Builder tasksProcessingOrder(QueueProcessingType tasksProcessingType) { + if (taskExecutor != null || taskExecutorForCachedImages != null) { + L.w(WARNING_OVERLAP_EXECUTOR); + } + this.tasksProcessingType = tasksProcessingType; return this; } @@ -273,7 +351,10 @@ public Builder tasksProcessingOrder(QueueProcessingType tasksProcessingType) { */ public Builder memoryCacheSize(int memoryCacheSize) { if (memoryCacheSize <= 0) throw new IllegalArgumentException("memoryCacheSize must be a positive number"); - if (memoryCache != null) L.w(WARNING_MEMORY_CACHE_ALREADY_SET); + + if (memoryCache != null) { + L.w(WARNING_OVERLAP_MEMORY_CACHE); + } this.memoryCacheSize = memoryCacheSize; return this; @@ -283,11 +364,16 @@ public Builder memoryCacheSize(int memoryCacheSize) { * Sets memory cache for {@link android.graphics.Bitmap bitmaps}.
* Default value - {@link com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache * UsingFreqLimitedCache} with limited memory cache size (size = {@link #DEFAULT_MEMORY_CACHE_SIZE this})
- * NOTE: You can use {@link #memoryCacheSize(int)} method instead of this method to simplify memory cache - * tuning. + *
+ * NOTE: If you set custom memory cache then following configuration option will not be considered: + *
    + *
  • {@link #memoryCacheSize(int)}
  • + *
*/ public Builder memoryCache(MemoryCacheAware memoryCache) { - if (memoryCacheSize != DEFAULT_MEMORY_CACHE_SIZE) L.w(WARNING_OVERLAP_MEMORY_CACHE_SIZE); + if (memoryCacheSize != DEFAULT_MEMORY_CACHE_SIZE) { + L.w(WARNING_OVERLAP_MEMORY_CACHE); + } this.memoryCache = memoryCache; return this; @@ -303,8 +389,10 @@ public Builder memoryCache(MemoryCacheAware memoryCache) { */ public Builder discCacheSize(int maxCacheSize) { if (maxCacheSize <= 0) throw new IllegalArgumentException("maxCacheSize must be a positive number"); - if (discCache != null) L.w(WARNING_DISC_CACHE_ALREADY_SET); - if (discCacheFileCount > 0) L.w(WARNING_OVERLAP_DISC_CACHE_FILE_COUNT); + + if (discCache != null || discCacheFileCount > 0) { + L.w(WARNING_OVERLAP_DISC_CACHE_PARAMS); + } this.discCacheSize = maxCacheSize; return this; @@ -320,8 +408,10 @@ public Builder discCacheSize(int maxCacheSize) { */ public Builder discCacheFileCount(int maxFileCount) { if (maxFileCount <= 0) throw new IllegalArgumentException("maxFileCount must be a positive number"); - if (discCache != null) L.w(WARNING_DISC_CACHE_ALREADY_SET); - if (discCacheSize > 0) L.w(WARNING_OVERLAP_DISC_CACHE_SIZE); + + if (discCache != null || discCacheSize > 0) { + L.w(WARNING_OVERLAP_DISC_CACHE_PARAMS); + } this.discCacheSize = 0; this.discCacheFileCount = maxFileCount; @@ -335,7 +425,9 @@ public Builder discCacheFileCount(int maxFileCount) { * DefaultConfigurationFactory.createFileNameGenerator()} */ public Builder discCacheFileNameGenerator(FileNameGenerator fileNameGenerator) { - if (discCache != null) L.w(WARNING_DISC_CACHE_ALREADY_SET); + if (discCache != null) { + L.w(WARNING_OVERLAP_DISC_CACHE_NAME_GENERATOR); + } this.discCacheFileNameGenerator = fileNameGenerator; return this; @@ -355,14 +447,24 @@ public Builder imageDownloader(ImageDownloader imageDownloader) { /** * Sets disc cache for images.
* Default value - {@link com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache - * UnlimitedDiscCache}. Cache directory is defined by + * UnlimitedDiscCache}. Cache directory is defined by * {@link com.nostra13.universalimageloader.utils.StorageUtils#getCacheDirectory(Context) * StorageUtils.getCacheDirectory(Context)}.
+ *
+ * NOTE: If you set custom disc cache then following configuration option will not be considered: + *
    + *
  • {@link #discCacheSize(int)}
  • + *
  • {@link #discCacheFileCount(int)}
  • + *
  • {@link #discCacheFileNameGenerator(FileNameGenerator)}
  • + *
*/ public Builder discCache(DiscCacheAware discCache) { - if (discCacheSize > 0) L.w(WARNING_OVERLAP_DISC_CACHE_SIZE); - if (discCacheFileCount > 0) L.w(WARNING_OVERLAP_DISC_CACHE_FILE_COUNT); - if (discCacheFileNameGenerator != null) L.w(WARNING_OVERLAP_DISC_CACHE_FILE_NAME_GENERATOR); + if (discCacheSize > 0 || discCacheFileCount > 0) { + L.w(WARNING_OVERLAP_DISC_CACHE_PARAMS); + } + if (discCacheFileNameGenerator != null) { + L.w(WARNING_OVERLAP_DISC_CACHE_NAME_GENERATOR); + } this.discCache = discCache; return this; @@ -392,6 +494,16 @@ public ImageLoaderConfiguration build() { } private void initEmptyFiledsWithDefaultValues() { + if (taskExecutor == null) { + taskExecutor = DefaultConfigurationFactory.createExecutor(threadPoolSize, threadPriority, tasksProcessingType); + } else { + customExecutor = true; + } + if (taskExecutorForCachedImages == null) { + taskExecutorForCachedImages = DefaultConfigurationFactory.createExecutor(threadPoolSize, threadPriority, tasksProcessingType); + } else { + customExecutorForCachedImages = true; + } if (discCache == null) { if (discCacheFileNameGenerator == null) { discCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator(); diff --git a/library/src/com/nostra13/universalimageloader/core/ImageLoaderEngine.java b/library/src/com/nostra13/universalimageloader/core/ImageLoaderEngine.java index 9d38facbd..57a2ccd80 100644 --- a/library/src/com/nostra13/universalimageloader/core/ImageLoaderEngine.java +++ b/library/src/com/nostra13/universalimageloader/core/ImageLoaderEngine.java @@ -19,12 +19,9 @@ import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; -import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; @@ -34,8 +31,6 @@ import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.assist.FlushedInputStream; import com.nostra13.universalimageloader.core.assist.ImageLoadingListener; -import com.nostra13.universalimageloader.core.assist.QueueProcessingType; -import com.nostra13.universalimageloader.core.assist.deque.LIFOLinkedBlockingDeque; /** * {@link ImageLoader} engine which responsible for {@linkplain LoadAndDisplayImageTask display task} execution. @@ -47,8 +42,8 @@ class ImageLoaderEngine { final ImageLoaderConfiguration configuration; - private ExecutorService imageLoadingExecutor; - private ExecutorService cachedImageLoadingExecutor; + private Executor taskExecutor; + private Executor taskExecutorForCachedImages; private ExecutorService taskDistributor; private final Map cacheKeysForImageViews = Collections.synchronizedMap(new HashMap()); @@ -60,19 +55,23 @@ class ImageLoaderEngine { ImageLoaderEngine(ImageLoaderConfiguration configuration) { this.configuration = configuration; + + taskExecutor = configuration.taskExecutor; + taskExecutorForCachedImages = configuration.taskExecutorForCachedImages; + + taskDistributor = Executors.newCachedThreadPool(); } /** Submits task to execution pool */ void submit(final LoadAndDisplayImageTask task) { - initExecutorsIfNeed(); - taskDistributor.submit(new Runnable() { + taskDistributor.execute(new Runnable() { @Override public void run() { boolean isImageCachedOnDisc = configuration.discCache.get(task.getLoadingUri()).exists(); if (isImageCachedOnDisc) { - cachedImageLoadingExecutor.submit(task); + taskExecutorForCachedImages.execute(task); } else { - imageLoadingExecutor.submit(task); + taskExecutor.execute(task); } } }); @@ -80,27 +79,7 @@ public void run() { /** Submits task to execution pool */ void submit(ProcessAndDisplayImageTask task) { - initExecutorsIfNeed(); - cachedImageLoadingExecutor.submit(task); - } - - private void initExecutorsIfNeed() { - if (imageLoadingExecutor == null || imageLoadingExecutor.isShutdown()) { - imageLoadingExecutor = createTaskExecutor(); - } - if (cachedImageLoadingExecutor == null || cachedImageLoadingExecutor.isShutdown()) { - cachedImageLoadingExecutor = createTaskExecutor(); - } - if (taskDistributor == null || taskDistributor.isShutdown()) { - taskDistributor = Executors.newCachedThreadPool(); - } - } - - private ExecutorService createTaskExecutor() { - boolean lifo = configuration.tasksProcessingType == QueueProcessingType.LIFO; - BlockingQueue taskQueue = lifo ? new LIFOLinkedBlockingDeque() : new LinkedBlockingQueue(); - return new ThreadPoolExecutor(configuration.threadPoolSize, configuration.threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue, - DefaultConfigurationFactory.createThreadFactory(configuration.threadPriority)); + taskExecutorForCachedImages.execute(task); } /** Returns URI of image which is loading at this moment into passed {@link ImageView} */ @@ -166,17 +145,24 @@ void resume() { } } - /** Stops all running display image tasks, discards all other scheduled tasks */ - void stop() { - if (imageLoadingExecutor != null) { - imageLoadingExecutor.shutdownNow(); + /** Stops all running display image tasks, discards all other scheduled tasks. Clears internal data. */ + void destroy() { + if (!configuration.customExecutor) { + ((ExecutorService) taskExecutor).shutdownNow(); } - if (cachedImageLoadingExecutor != null) { - cachedImageLoadingExecutor.shutdownNow(); + if (!configuration.customExecutorForCachedImages) { + ((ExecutorService) taskExecutorForCachedImages).shutdownNow(); } if (taskDistributor != null) { taskDistributor.shutdownNow(); } + + cacheKeysForImageViews.clear(); + uriLocks.clear(); + + taskExecutor = null; + taskExecutorForCachedImages = null; + taskDistributor = null; } ReentrantLock getLockForUri(String uri) {