getMemoryCache() {
- checkConfiguration();
- return configuration.memoryCache;
- }
-
- /**
- * Clears memory cache
- *
- * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
- */
- public void clearMemoryCache() {
- checkConfiguration();
- configuration.memoryCache.clear();
- }
-
- /**
- * Returns disc cache
- *
- * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
- */
- public DiscCacheAware getDiscCache() {
- checkConfiguration();
- return configuration.discCache;
- }
-
- /**
- * Clears disc cache.
- *
- * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
- */
- public void clearDiscCache() {
- checkConfiguration();
- configuration.discCache.clear();
- }
-
- /** Returns URI of image which is loading at this moment into passed {@link ImageView} */
- public String getLoadingUriForView(ImageView imageView) {
- return engine.getLoadingUriForView(imageView);
- }
-
- /**
- * Cancel the task of loading and displaying image for passed {@link ImageView}.
- *
- * @param imageView {@link ImageView} for which display task will be cancelled
- */
- public void cancelDisplayTask(ImageView imageView) {
- engine.cancelDisplayTaskFor(imageView);
- }
-
- /**
- * Denies or allows ImageLoader to download images from the network.
- *
- * If downloads are denied and if image isn't cached then
- * {@link ImageLoadingListener#onLoadingFailed(String, View, FailReason)} callback will be fired with
- * {@link FailReason.FailType#NETWORK_DENIED}
- *
- * @param denyNetworkDownloads pass true - to deny engine to download images from the network; false -
- * to allow engine to download images from network.
- */
- public void denyNetworkDownloads(boolean denyNetworkDownloads) {
- engine.denyNetworkDownloads(denyNetworkDownloads);
- }
-
- /**
- * Sets option whether ImageLoader will use {@link FlushedInputStream} for network downloads to handle this known problem or not.
- *
- * @param handleSlowNetwork pass true - to use {@link FlushedInputStream} for network downloads; false
- * - otherwise.
- */
- public void handleSlowNetwork(boolean handleSlowNetwork) {
- engine.handleSlowNetwork(handleSlowNetwork);
- }
-
- /**
- * Pause ImageLoader. All new "load&display" tasks won't be executed until ImageLoader is {@link #resume() resumed}.
- * Already running tasks are not paused.
- */
- public void pause() {
- engine.pause();
- }
-
- /** Resumes waiting "load&display" tasks */
- public void resume() {
- engine.resume();
- }
-
- /**
- * Cancels all running and scheduled display image tasks.
- * ImageLoader still can be used after calling this method.
- */
- public void stop() {
- engine.stop();
- }
-
- /**
- * {@linkplain #stop() Stops ImageLoader} and clears current configuration.
- * You can {@linkplain #init(ImageLoaderConfiguration) init} ImageLoader with new configuration after calling this
- * method.
- */
- public void destroy() {
- if (configuration != null && configuration.writeLogs) L.d(LOG_DESTROY);
- stop();
- engine = null;
- configuration = null;
- }
-}
diff --git a/library/src/com/nostra13/universalimageloader/core/LoadAndDisplayImageTask.java b/library/src/com/nostra13/universalimageloader/core/LoadAndDisplayImageTask.java
deleted file mode 100644
index 4d91e5212..000000000
--- a/library/src/com/nostra13/universalimageloader/core/LoadAndDisplayImageTask.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *******************************************************************************/
-package com.nostra13.universalimageloader.core;
-
-import android.graphics.Bitmap;
-import android.os.Handler;
-import android.widget.ImageView;
-import com.nostra13.universalimageloader.cache.disc.DiscCacheAware;
-import com.nostra13.universalimageloader.core.assist.*;
-import com.nostra13.universalimageloader.core.assist.FailReason.FailType;
-import com.nostra13.universalimageloader.core.decode.ImageDecoder;
-import com.nostra13.universalimageloader.core.decode.ImageDecodingInfo;
-import com.nostra13.universalimageloader.core.download.ImageDownloader;
-import com.nostra13.universalimageloader.core.download.ImageDownloader.Scheme;
-import com.nostra13.universalimageloader.utils.IoUtils;
-import com.nostra13.universalimageloader.utils.L;
-
-import java.io.*;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Presents load'n'display image task. Used to load image from Internet or file system, decode it to {@link Bitmap}, and
- * display it in {@link ImageView} using {@link DisplayBitmapTask}.
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @see ImageLoaderConfiguration
- * @see ImageLoadingInfo
- * @since 1.3.1
- */
-final class LoadAndDisplayImageTask implements Runnable {
-
- private static final String LOG_WAITING_FOR_RESUME = "ImageLoader is paused. Waiting... [%s]";
- private static final String LOG_RESUME_AFTER_PAUSE = ".. Resume loading [%s]";
- private static final String LOG_DELAY_BEFORE_LOADING = "Delay %d ms before loading... [%s]";
- private static final String LOG_START_DISPLAY_IMAGE_TASK = "Start display image task [%s]";
- private static final String LOG_WAITING_FOR_IMAGE_LOADED = "Image already is loading. Waiting... [%s]";
- private static final String LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING = "...Get cached bitmap from memory after waiting. [%s]";
- private static final String LOG_LOAD_IMAGE_FROM_NETWORK = "Load image from network [%s]";
- private static final String LOG_LOAD_IMAGE_FROM_DISC_CACHE = "Load image from disc cache [%s]";
- private static final String LOG_PREPROCESS_IMAGE = "PreProcess image before caching in memory [%s]";
- private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]";
- private static final String LOG_CACHE_IMAGE_IN_MEMORY = "Cache image in memory [%s]";
- private static final String LOG_CACHE_IMAGE_ON_DISC = "Cache image on disc [%s]";
- private static final String LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISC = "Process image before cache on disc [%s]";
- private static final String LOG_TASK_CANCELLED = "ImageView is reused for another image. Task is cancelled. [%s]";
- private static final String LOG_TASK_INTERRUPTED = "Task was interrupted [%s]";
-
- private static final String ERROR_PRE_PROCESSOR_NULL = "Pre-processor returned null [%s]";
- private static final String ERROR_POST_PROCESSOR_NULL = "Pre-processor returned null [%s]";
- private static final String ERROR_PROCESSOR_FOR_DISC_CACHE_NULL = "Bitmap processor for disc cache returned null [%s]";
-
- private static final int BUFFER_SIZE = 32 * 1024; // 32 Kb
-
- private final ImageLoaderEngine engine;
- private final ImageLoadingInfo imageLoadingInfo;
- private final Handler handler;
-
- // Helper references
- private final ImageLoaderConfiguration configuration;
- private final ImageDownloader downloader;
- private final ImageDownloader networkDeniedDownloader;
- private final ImageDownloader slowNetworkDownloader;
- private final ImageDecoder decoder;
- private final boolean writeLogs;
- final String uri;
- private final String memoryCacheKey;
- final ImageView imageView;
- private final ImageSize targetSize;
- final DisplayImageOptions options;
- final ImageLoadingListener listener;
-
- private LoadedFrom loadedFrom = LoadedFrom.NETWORK;
-
- public LoadAndDisplayImageTask(ImageLoaderEngine engine, ImageLoadingInfo imageLoadingInfo, Handler handler) {
- this.engine = engine;
- this.imageLoadingInfo = imageLoadingInfo;
- this.handler = handler;
-
- configuration = engine.configuration;
- downloader = configuration.downloader;
- networkDeniedDownloader = configuration.networkDeniedDownloader;
- slowNetworkDownloader = configuration.slowNetworkDownloader;
- decoder = configuration.decoder;
- writeLogs = configuration.writeLogs;
- uri = imageLoadingInfo.uri;
- memoryCacheKey = imageLoadingInfo.memoryCacheKey;
- imageView = imageLoadingInfo.imageView;
- targetSize = imageLoadingInfo.targetSize;
- options = imageLoadingInfo.options;
- listener = imageLoadingInfo.listener;
- }
-
- @Override
- public void run() {
- if (waitIfPaused()) return;
- if (delayIfNeed()) return;
-
- ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
- log(LOG_START_DISPLAY_IMAGE_TASK);
- if (loadFromUriLock.isLocked()) {
- log(LOG_WAITING_FOR_IMAGE_LOADED);
- }
-
- loadFromUriLock.lock();
- Bitmap bmp;
- try {
- if (checkTaskIsNotActual()) return;
-
- bmp = configuration.memoryCache.get(memoryCacheKey);
- if (bmp == null) {
- bmp = tryLoadBitmap();
- if (bmp == null) return; // listener callback already was fired
-
- if (checkTaskIsNotActual() || checkTaskIsInterrupted()) return;
-
- if (options.shouldPreProcess()) {
- log(LOG_PREPROCESS_IMAGE);
- bmp = options.getPreProcessor().process(bmp);
- if (bmp == null) {
- L.e(ERROR_PRE_PROCESSOR_NULL);
- }
- }
-
- if (bmp != null && options.isCacheInMemory()) {
- log(LOG_CACHE_IMAGE_IN_MEMORY);
- configuration.memoryCache.put(memoryCacheKey, bmp);
- }
- } else {
- loadedFrom = LoadedFrom.MEMORY_CACHE;
- log(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING);
- }
-
- if (bmp != null && options.shouldPostProcess()) {
- log(LOG_POSTPROCESS_IMAGE);
- bmp = options.getPostProcessor().process(bmp);
- if (bmp == null) {
- L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
- }
- }
- } finally {
- loadFromUriLock.unlock();
- }
-
- if (checkTaskIsNotActual() || checkTaskIsInterrupted()) return;
-
- DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
- displayBitmapTask.setLoggingEnabled(writeLogs);
- handler.post(displayBitmapTask);
- }
-
- /** @return true - if task should be interrupted; false - otherwise */
- private boolean waitIfPaused() {
- AtomicBoolean pause = engine.getPause();
- if (pause.get()) {
- synchronized (pause) {
- log(LOG_WAITING_FOR_RESUME);
- try {
- pause.wait();
- } catch (InterruptedException e) {
- L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
- return true;
- }
- log(LOG_RESUME_AFTER_PAUSE);
- }
- }
- return checkTaskIsNotActual();
- }
-
- /** @return true - if task should be interrupted; false - otherwise */
- private boolean delayIfNeed() {
- if (options.shouldDelayBeforeLoading()) {
- log(LOG_DELAY_BEFORE_LOADING, options.getDelayBeforeLoading(), memoryCacheKey);
- try {
- Thread.sleep(options.getDelayBeforeLoading());
- } catch (InterruptedException e) {
- L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
- return true;
- }
- return checkTaskIsNotActual();
- }
- return false;
- }
-
- /**
- * Check whether the image URI of this task matches to image URI which is actual for current ImageView at this
- * moment and fire {@link ImageLoadingListener#onLoadingCancelled(String, android.view.View)}} event if it doesn't.
- */
- private boolean checkTaskIsNotActual() {
- String currentCacheKey = engine.getLoadingUriForView(imageView);
- // Check whether memory cache key (image URI) for current ImageView is actual.
- // If ImageView is reused for another task then current task should be cancelled.
- boolean imageViewWasReused = !memoryCacheKey.equals(currentCacheKey);
- if (imageViewWasReused) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- listener.onLoadingCancelled(uri, imageView);
- }
- });
- log(LOG_TASK_CANCELLED);
- }
- return imageViewWasReused;
- }
-
- /** Check whether the current task was interrupted */
- private boolean checkTaskIsInterrupted() {
- boolean interrupted = Thread.interrupted();
- if (interrupted) log(LOG_TASK_INTERRUPTED);
- return interrupted;
- }
-
- private Bitmap tryLoadBitmap() {
- File imageFile = getImageFileInDiscCache();
-
- Bitmap bitmap = null;
- try {
- if (imageFile.exists()) {
- log(LOG_LOAD_IMAGE_FROM_DISC_CACHE);
-
- loadedFrom = LoadedFrom.DISC_CACHE;
- bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
- }
- if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
- log(LOG_LOAD_IMAGE_FROM_NETWORK);
-
- loadedFrom = LoadedFrom.NETWORK;
- String imageUriForDecoding = options.isCacheOnDisc() ? tryCacheImageOnDisc(imageFile) : uri;
- if (!checkTaskIsNotActual()) {
- bitmap = decodeImage(imageUriForDecoding);
- if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
- fireImageLoadingFailedEvent(FailType.DECODING_ERROR, null);
- }
- }
- }
- } catch (IllegalStateException e) {
- fireImageLoadingFailedEvent(FailType.NETWORK_DENIED, null);
- } catch (IOException e) {
- L.e(e);
- fireImageLoadingFailedEvent(FailType.IO_ERROR, e);
- if (imageFile.exists()) {
- imageFile.delete();
- }
- } catch (OutOfMemoryError e) {
- L.e(e);
- fireImageLoadingFailedEvent(FailType.OUT_OF_MEMORY, e);
- } catch (Throwable e) {
- L.e(e);
- fireImageLoadingFailedEvent(FailType.UNKNOWN, e);
- }
- return bitmap;
- }
-
- private File getImageFileInDiscCache() {
- DiscCacheAware discCache = configuration.discCache;
- File imageFile = discCache.get(uri);
- File cacheDir = imageFile.getParentFile();
- if (cacheDir == null || (!cacheDir.exists() && !cacheDir.mkdirs())) {
- imageFile = configuration.reserveDiscCache.get(uri);
- cacheDir = imageFile.getParentFile();
- if (cacheDir != null && !cacheDir.exists()) {
- cacheDir.mkdirs();
- }
- }
- return imageFile;
- }
-
- private Bitmap decodeImage(String imageUri) throws IOException {
- ViewScaleType viewScaleType = ViewScaleType.fromImageView(imageView);
- ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, targetSize, viewScaleType, getDownloader(), options);
- return decoder.decode(decodingInfo);
- }
-
- /** @return Cached image URI; or original image URI if caching failed */
- private String tryCacheImageOnDisc(File targetFile) {
- log(LOG_CACHE_IMAGE_ON_DISC);
-
- try {
- int width = configuration.maxImageWidthForDiscCache;
- int height = configuration.maxImageHeightForDiscCache;
- boolean saved = false;
- if (width > 0 || height > 0) {
- saved = downloadSizedImage(targetFile, width, height);
- }
- if (!saved) {
- downloadImage(targetFile);
- }
-
- configuration.discCache.put(uri, targetFile);
- return Scheme.FILE.wrap(targetFile.getAbsolutePath());
- } catch (IOException e) {
- L.e(e);
- return uri;
- }
- }
-
- private boolean downloadSizedImage(File targetFile, int maxWidth, int maxHeight) throws IOException {
- // Download, decode, compress and save image
- ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
- DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options).imageScaleType(ImageScaleType.IN_SAMPLE_INT).build();
- ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, uri, targetImageSize, ViewScaleType.FIT_INSIDE, getDownloader(), specialOptions);
- Bitmap bmp = decoder.decode(decodingInfo);
- if (bmp == null) return false;
-
- if (configuration.processorForDiscCache != null) {
- log(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISC);
- bmp = configuration.processorForDiscCache.process(bmp);
- if (bmp == null) {
- L.e(ERROR_PROCESSOR_FOR_DISC_CACHE_NULL, memoryCacheKey);
- return false;
- }
- }
-
- OutputStream os = new BufferedOutputStream(new FileOutputStream(targetFile), BUFFER_SIZE);
- boolean savedSuccessfully;
- try {
- savedSuccessfully = bmp.compress(configuration.imageCompressFormatForDiscCache, configuration.imageQualityForDiscCache, os);
- } finally {
- IoUtils.closeSilently(os);
- }
- bmp.recycle();
- return savedSuccessfully;
- }
-
- private void downloadImage(File targetFile) throws IOException {
- InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
- try {
- OutputStream os = new BufferedOutputStream(new FileOutputStream(targetFile), BUFFER_SIZE);
- try {
- IoUtils.copyStream(is, os);
- } finally {
- IoUtils.closeSilently(os);
- }
- } finally {
- IoUtils.closeSilently(is);
- }
- }
-
- private void fireImageLoadingFailedEvent(final FailType failType, final Throwable failCause) {
- if (!Thread.interrupted()) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- if (options.shouldShowImageOnFail()) {
- imageView.setImageResource(options.getImageOnFail());
- }
- listener.onLoadingFailed(uri, imageView, new FailReason(failType, failCause));
- }
- });
- }
- }
-
- private ImageDownloader getDownloader() {
- ImageDownloader d;
- if (engine.isNetworkDenied()) {
- d = networkDeniedDownloader;
- } else if (engine.isSlowNetwork()) {
- d = slowNetworkDownloader;
- } else {
- d = downloader;
- }
- return d;
- }
-
- String getLoadingUri() {
- return uri;
- }
-
- private void log(String message) {
- if (writeLogs) L.d(message, memoryCacheKey);
- }
-
- private void log(String message, Object... args) {
- if (writeLogs) L.d(message, args);
- }
-}
diff --git a/library/src/com/nostra13/universalimageloader/core/display/FadeInBitmapDisplayer.java b/library/src/com/nostra13/universalimageloader/core/display/FadeInBitmapDisplayer.java
deleted file mode 100644
index e3ecd979f..000000000
--- a/library/src/com/nostra13/universalimageloader/core/display/FadeInBitmapDisplayer.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *******************************************************************************/
-package com.nostra13.universalimageloader.core.display;
-
-import android.graphics.Bitmap;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.ImageView;
-import com.nostra13.universalimageloader.core.assist.LoadedFrom;
-
-/**
- * Displays image with "fade in" animation
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @since 1.6.4
- */
-public class FadeInBitmapDisplayer implements BitmapDisplayer {
-
- private final int durationMillis;
-
- public FadeInBitmapDisplayer(int durationMillis) {
- this.durationMillis = durationMillis;
- }
-
- @Override
- public Bitmap display(Bitmap bitmap, ImageView imageView, LoadedFrom loadedFrom) {
- imageView.setImageBitmap(bitmap);
-
- animate(imageView, durationMillis);
-
- return bitmap;
- }
-
- /**
- * Animates {@link ImageView} with "fade-in" effect
- *
- * @param imageView {@link ImageView} which display image in
- * @param durationMillis The length of the animation in milliseconds
- */
- public static void animate(ImageView imageView, int durationMillis) {
- AlphaAnimation fadeImage = new AlphaAnimation(0, 1);
- fadeImage.setDuration(durationMillis);
- fadeImage.setInterpolator(new DecelerateInterpolator());
- imageView.startAnimation(fadeImage);
- }
-}
diff --git a/library/src/com/nostra13/universalimageloader/core/display/FakeBitmapDisplayer.java b/library/src/com/nostra13/universalimageloader/core/display/FakeBitmapDisplayer.java
deleted file mode 100644
index dffc7d065..000000000
--- a/library/src/com/nostra13/universalimageloader/core/display/FakeBitmapDisplayer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *******************************************************************************/
-package com.nostra13.universalimageloader.core.display;
-
-import android.graphics.Bitmap;
-import android.widget.ImageView;
-import com.nostra13.universalimageloader.core.DisplayImageOptions;
-import com.nostra13.universalimageloader.core.ImageLoader;
-import com.nostra13.universalimageloader.core.assist.LoadedFrom;
-
-/**
- * Fake displayer which doesn't display Bitmap in ImageView. Should be used in {@linkplain DisplayImageOptions display
- * options} for
- * {@link ImageLoader#loadImage(String, com.nostra13.universalimageloader.core.assist.ImageSize, com.nostra13.universalimageloader.core.DisplayImageOptions, com.nostra13.universalimageloader.core.assist.ImageLoadingListener)}
- * ImageLoader.loadImage()}
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @since 1.6.0
- */
-public final class FakeBitmapDisplayer implements BitmapDisplayer {
- @Override
- public Bitmap display(Bitmap bitmap, ImageView imageView, LoadedFrom loadedFrom) {
- // Do nothing
- return bitmap;
- }
-}
diff --git a/library/src/com/nostra13/universalimageloader/core/download/NetworkDeniedImageDownloader.java b/library/src/com/nostra13/universalimageloader/core/download/NetworkDeniedImageDownloader.java
deleted file mode 100644
index 373397f79..000000000
--- a/library/src/com/nostra13/universalimageloader/core/download/NetworkDeniedImageDownloader.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *******************************************************************************/
-package com.nostra13.universalimageloader.core.download;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Decorator. Prevents downloads from network (throws {@link IllegalStateException exception}).
- * In most cases this downloader shouldn't be used directly.
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @since 1.8.0
- */
-public class NetworkDeniedImageDownloader implements ImageDownloader {
-
- private final ImageDownloader wrappedDownloader;
-
- public NetworkDeniedImageDownloader(ImageDownloader wrappedDownloader) {
- this.wrappedDownloader = wrappedDownloader;
- }
-
- @Override
- public InputStream getStream(String imageUri, Object extra) throws IOException {
- switch (Scheme.ofUri(imageUri)) {
- case HTTP:
- case HTTPS:
- throw new IllegalStateException();
- default:
- return wrappedDownloader.getStream(imageUri, extra);
- }
- }
-}
diff --git a/library/src/com/nostra13/universalimageloader/core/download/SlowNetworkImageDownloader.java b/library/src/com/nostra13/universalimageloader/core/download/SlowNetworkImageDownloader.java
deleted file mode 100644
index 2cab9f38b..000000000
--- a/library/src/com/nostra13/universalimageloader/core/download/SlowNetworkImageDownloader.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*******************************************************************************
- * Copyright 2013 Sergey Tarasevich
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *******************************************************************************/
-package com.nostra13.universalimageloader.core.download;
-
-import com.nostra13.universalimageloader.core.assist.FlushedInputStream;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Decorator. Handles this problem on slow networks
- * using {@link FlushedInputStream}.
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @since 1.8.1
- */
-public class SlowNetworkImageDownloader implements ImageDownloader {
-
- private final ImageDownloader wrappedDownloader;
-
- public SlowNetworkImageDownloader(ImageDownloader wrappedDownloader) {
- this.wrappedDownloader = wrappedDownloader;
- }
-
- @Override
- public InputStream getStream(String imageUri, Object extra) throws IOException {
- InputStream imageStream = wrappedDownloader.getStream(imageUri, extra);
- switch (Scheme.ofUri(imageUri)) {
- case HTTP:
- case HTTPS:
- return new FlushedInputStream(imageStream);
- default:
- return imageStream;
- }
- }
-}
diff --git a/library/src/com/nostra13/universalimageloader/utils/ImageSizeUtils.java b/library/src/com/nostra13/universalimageloader/utils/ImageSizeUtils.java
deleted file mode 100644
index e77b2fef3..000000000
--- a/library/src/com/nostra13/universalimageloader/utils/ImageSizeUtils.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*******************************************************************************
- * Copyright 2013 Sergey Tarasevich
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *******************************************************************************/
-package com.nostra13.universalimageloader.utils;
-
-import android.graphics.BitmapFactory;
-import android.util.DisplayMetrics;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.ImageView;
-import com.nostra13.universalimageloader.core.assist.ImageSize;
-import com.nostra13.universalimageloader.core.assist.ViewScaleType;
-
-import java.lang.reflect.Field;
-
-/**
- * Provides calculations with image sizes, scales
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @since 1.8.3
- */
-public final class ImageSizeUtils {
-
- private ImageSizeUtils() {
- }
-
- /**
- * Defines target size for image. Size is defined by target {@link ImageView view} parameters, configuration
- * parameters or device display dimensions.
- * Size computing algorithm:
- * 1) Get the actual drawn getWidth() and getHeight() of the View. If view haven't drawn yet then go
- * to step #2.
- * 2) Get layout_width and layout_height. If both of them haven't exact value then go to step #3.
- * 3) Get maxWidth and maxHeight. If both of them are not set then go to step #4.
- * 4) Get maxImageWidth param (maxImageWidthForMemoryCache) and maxImageHeight param
- * (maxImageHeightForMemoryCache). If both of them are not set (equal 0) then go to step #5.
- * 5) Get device screen dimensions.
- */
- public static ImageSize defineTargetSizeForView(ImageView imageView, int maxImageWidth, int maxImageHeight) {
- final DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
-
- final LayoutParams params = imageView.getLayoutParams();
- int width = (params != null && params.width == LayoutParams.WRAP_CONTENT) ? 0 : imageView.getWidth(); // Get actual image width
- if (width <= 0 && params != null) width = params.width; // Get layout width parameter
- if (width <= 0) width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check maxWidth parameter
- if (width <= 0) width = maxImageWidth;
- if (width <= 0) width = displayMetrics.widthPixels;
-
- int height = (params != null && params.height == LayoutParams.WRAP_CONTENT) ? 0 : imageView.getHeight(); // Get actual image height
- if (height <= 0 && params != null) height = params.height; // Get layout height parameter
- if (height <= 0) height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check maxHeight parameter
- if (height <= 0) height = maxImageHeight;
- if (height <= 0) height = displayMetrics.heightPixels;
-
- return new ImageSize(width, height);
- }
-
- private static int getImageViewFieldValue(Object object, String fieldName) {
- int value = 0;
- try {
- Field field = ImageView.class.getDeclaredField(fieldName);
- field.setAccessible(true);
- int fieldValue = (Integer) field.get(object);
- if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
- value = fieldValue;
- }
- } catch (Exception e) {
- L.e(e);
- }
- return value;
- }
-
- /**
- * Computes sample size for downscaling image size (srcSize) to view size (targetSize). This sample
- * size is used during
- * {@linkplain BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options)
- * decoding image} to bitmap.
- *
- * Examples:
- *
- *
- * srcSize(100x100), targetSize(10x10), powerOf2Scale = true -> sampleSize = 8
- * srcSize(100x100), targetSize(10x10), powerOf2Scale = false -> sampleSize = 10
- *
- * srcSize(100x100), targetSize(20x40), viewScaleType = FIT_INSIDE -> sampleSize = 5
- * srcSize(100x100), targetSize(20x40), viewScaleType = CROP -> sampleSize = 2
- *
- *
- *
- * The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded
- * bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16
- * the number of pixels. Any value <= 1 is treated the same as 1.
- *
- * @param srcSize Original (image) size
- * @param targetSize Target (view) size
- * @param viewScaleType {@linkplain ViewScaleType Scale type} for placing image in view
- * @param powerOf2Scale true - if sample size be a power of 2 (1, 2, 4, 8, ...)
- * @return Computed sample size
- */
- public static int computeImageSampleSize(ImageSize srcSize, ImageSize targetSize, ViewScaleType viewScaleType, boolean powerOf2Scale) {
- int srcWidth = srcSize.getWidth();
- int srcHeight = srcSize.getHeight();
- int targetWidth = targetSize.getWidth();
- int targetHeight = targetSize.getHeight();
-
- int scale = 1;
-
- int widthScale = srcWidth / targetWidth;
- int heightScale = srcHeight / targetHeight;
-
- switch (viewScaleType) {
- case FIT_INSIDE:
- if (powerOf2Scale) {
- while (srcWidth / 2 >= targetWidth || srcHeight / 2 >= targetHeight) { // ||
- srcWidth /= 2;
- srcHeight /= 2;
- scale *= 2;
- }
- } else {
- scale = Math.max(widthScale, heightScale); // max
- }
- break;
- case CROP:
- if (powerOf2Scale) {
- while (srcWidth / 2 >= targetWidth && srcHeight / 2 >= targetHeight) { // &&
- srcWidth /= 2;
- srcHeight /= 2;
- scale *= 2;
- }
- } else {
- scale = Math.min(widthScale, heightScale); // min
- }
- break;
- }
-
- if (scale < 1) {
- scale = 1;
- }
-
- return scale;
- }
-
- /**
- * Computes scale of target size (targetSize) to source size (srcSize).
- *
- * Examples:
- *
- *
- * srcSize(40x40), targetSize(10x10) -> scale = 0.25
- *
- * srcSize(10x10), targetSize(20x20), stretch = false -> scale = 1
- * srcSize(10x10), targetSize(20x20), stretch = true -> scale = 2
- *
- * srcSize(100x100), targetSize(20x40), viewScaleType = FIT_INSIDE -> scale = 0.2
- * srcSize(100x100), targetSize(20x40), viewScaleType = CROP -> scale = 0.4
- *
- *
- * @param srcSize Source (image) size
- * @param targetSize Target (view) size
- * @param viewScaleType {@linkplain ViewScaleType Scale type} for placing image in view
- * @param stretch Whether source size should be stretched if target size is larger than source size. If false
- * then result scale value can't be greater than 1.
- * @return Computed scale
- */
- public static float computeImageScale(ImageSize srcSize, ImageSize targetSize, ViewScaleType viewScaleType, boolean stretch) {
- int srcWidth = srcSize.getWidth();
- int srcHeight = srcSize.getHeight();
- int targetWidth = targetSize.getWidth();
- int targetHeight = targetSize.getHeight();
-
- float widthScale = (float) srcWidth / targetWidth;
- float heightScale = (float) srcHeight / targetHeight;
-
- int destWidth;
- int destHeight;
- if ((viewScaleType == ViewScaleType.FIT_INSIDE && widthScale >= heightScale) || (viewScaleType == ViewScaleType.CROP && widthScale < heightScale)) {
- destWidth = targetWidth;
- destHeight = (int) (srcHeight / widthScale);
- } else {
- destWidth = (int) (srcWidth / heightScale);
- destHeight = targetHeight;
- }
-
- float scale = 1;
- if ((!stretch && destWidth < srcWidth && destHeight < srcHeight) || (stretch && destWidth != srcWidth && destHeight != srcHeight)) {
- scale = (float) destWidth / srcWidth;
- }
-
- return scale;
- }
-}
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..6aecc3f83
--- /dev/null
+++ b/library/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/library/src/main/java/com/nostra13/universalimageloader/cache/disc/DiskCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/DiskCache.java
new file mode 100644
index 000000000..7258b9ceb
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/DiskCache.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright 2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.cache.disc;
+
+import android.graphics.Bitmap;
+import com.nostra13.universalimageloader.utils.IoUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Interface for disk cache
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.9.2
+ */
+public interface DiskCache {
+ /**
+ * Returns root directory of disk cache
+ *
+ * @return Root directory of disk cache
+ */
+ File getDirectory();
+
+ /**
+ * Returns file of cached image
+ *
+ * @param imageUri Original image URI
+ * @return File of cached image or null if image wasn't cached
+ */
+ File get(String imageUri);
+
+ /**
+ * Saves image stream in disk cache.
+ * Incoming image stream shouldn't be closed in this method.
+ *
+ * @param imageUri Original image URI
+ * @param imageStream Input stream of image (shouldn't be closed in this method)
+ * @param listener Listener for saving progress, can be ignored if you don't use
+ * {@linkplain com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener
+ * progress listener} in ImageLoader calls
+ * @return true - if image was saved successfully; false - if image wasn't saved in disk cache.
+ * @throws java.io.IOException
+ */
+ boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;
+
+ /**
+ * Saves image bitmap in disk cache.
+ *
+ * @param imageUri Original image URI
+ * @param bitmap Image bitmap
+ * @return true - if bitmap was saved successfully; false - if bitmap wasn't saved in disk cache.
+ * @throws IOException
+ */
+ boolean save(String imageUri, Bitmap bitmap) throws IOException;
+
+ /**
+ * Removes image file associated with incoming URI
+ *
+ * @param imageUri Image URI
+ * @return true - if image file is deleted successfully; false - if image file doesn't exist for
+ * incoming URI or image file can't be deleted.
+ */
+ boolean remove(String imageUri);
+
+ /** Closes disk cache, releases resources. */
+ void close();
+
+ /** Clears disk cache. */
+ void clear();
+}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/BaseDiskCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/BaseDiskCache.java
new file mode 100644
index 000000000..80069c675
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/BaseDiskCache.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * Copyright 2011-2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.cache.disc.impl;
+
+import android.graphics.Bitmap;
+import com.nostra13.universalimageloader.cache.disc.DiskCache;
+import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
+import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;
+import com.nostra13.universalimageloader.utils.IoUtils;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Base disk cache.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @see FileNameGenerator
+ * @since 1.0.0
+ */
+public abstract class BaseDiskCache implements DiskCache {
+ /** {@value} */
+ public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 Kb
+ /** {@value} */
+ public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG;
+ /** {@value} */
+ public static final int DEFAULT_COMPRESS_QUALITY = 100;
+
+ private static final String ERROR_ARG_NULL = " argument must be not null";
+ private static final String TEMP_IMAGE_POSTFIX = ".tmp";
+
+ protected final File cacheDir;
+ protected final File reserveCacheDir;
+
+ protected final FileNameGenerator fileNameGenerator;
+
+ protected int bufferSize = DEFAULT_BUFFER_SIZE;
+
+ protected Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
+ protected int compressQuality = DEFAULT_COMPRESS_QUALITY;
+
+ /** @param cacheDir Directory for file caching */
+ public BaseDiskCache(File cacheDir) {
+ this(cacheDir, null);
+ }
+
+ /**
+ * @param cacheDir Directory for file caching
+ * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
+ */
+ public BaseDiskCache(File cacheDir, File reserveCacheDir) {
+ this(cacheDir, reserveCacheDir, DefaultConfigurationFactory.createFileNameGenerator());
+ }
+
+ /**
+ * @param cacheDir Directory for file caching
+ * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
+ * @param fileNameGenerator {@linkplain com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator
+ * Name generator} for cached files
+ */
+ public BaseDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
+ if (cacheDir == null) {
+ throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL);
+ }
+ if (fileNameGenerator == null) {
+ throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL);
+ }
+
+ this.cacheDir = cacheDir;
+ this.reserveCacheDir = reserveCacheDir;
+ this.fileNameGenerator = fileNameGenerator;
+ }
+
+ @Override
+ public File getDirectory() {
+ return cacheDir;
+ }
+
+ @Override
+ public File get(String imageUri) {
+ return getFile(imageUri);
+ }
+
+ @Override
+ public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
+ File imageFile = getFile(imageUri);
+ File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
+ boolean loaded = false;
+ try {
+ OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
+ try {
+ loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);
+ } finally {
+ IoUtils.closeSilently(os);
+ }
+ } finally {
+ if (loaded && !tmpFile.renameTo(imageFile)) {
+ loaded = false;
+ }
+ if (!loaded) {
+ tmpFile.delete();
+ }
+ }
+ return loaded;
+ }
+
+ @Override
+ public boolean save(String imageUri, Bitmap bitmap) throws IOException {
+ File imageFile = getFile(imageUri);
+ File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
+ OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
+ boolean savedSuccessfully = false;
+ try {
+ savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
+ } finally {
+ IoUtils.closeSilently(os);
+ if (savedSuccessfully && !tmpFile.renameTo(imageFile)) {
+ savedSuccessfully = false;
+ }
+ if (!savedSuccessfully) {
+ tmpFile.delete();
+ }
+ }
+ bitmap.recycle();
+ return savedSuccessfully;
+ }
+
+ @Override
+ public boolean remove(String imageUri) {
+ return getFile(imageUri).delete();
+ }
+
+ @Override
+ public void close() {
+ // Nothing to do
+ }
+
+ @Override
+ public void clear() {
+ File[] files = cacheDir.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ f.delete();
+ }
+ }
+ }
+
+ /** Returns file object (not null) for incoming image URI. File object can reference to non-existing file. */
+ protected File getFile(String imageUri) {
+ String fileName = fileNameGenerator.generate(imageUri);
+ File dir = cacheDir;
+ if (!cacheDir.exists() && !cacheDir.mkdirs()) {
+ if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
+ dir = reserveCacheDir;
+ }
+ }
+ return new File(dir, fileName);
+ }
+
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+ public void setCompressFormat(Bitmap.CompressFormat compressFormat) {
+ this.compressFormat = compressFormat;
+ }
+
+ public void setCompressQuality(int compressQuality) {
+ this.compressQuality = compressQuality;
+ }
+}
\ No newline at end of file
diff --git a/library/src/com/nostra13/universalimageloader/cache/disc/impl/LimitedAgeDiscCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/LimitedAgeDiskCache.java
similarity index 56%
rename from library/src/com/nostra13/universalimageloader/cache/disc/impl/LimitedAgeDiscCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/LimitedAgeDiskCache.java
index c6e316091..f367501a2 100644
--- a/library/src/com/nostra13/universalimageloader/cache/disc/impl/LimitedAgeDiscCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/LimitedAgeDiskCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,11 +15,14 @@
*******************************************************************************/
package com.nostra13.universalimageloader.cache.disc.impl;
-import com.nostra13.universalimageloader.cache.disc.BaseDiscCache;
+import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;
+import com.nostra13.universalimageloader.utils.IoUtils;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -28,10 +31,9 @@
* Cache which deletes files which were loaded more than defined time. Cache size is unlimited.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @see BaseDiscCache
* @since 1.3.1
*/
-public class LimitedAgeDiscCache extends BaseDiscCache {
+public class LimitedAgeDiskCache extends BaseDiskCache {
private final long maxFileAge;
@@ -42,32 +44,35 @@ public class LimitedAgeDiscCache extends BaseDiscCache {
* @param maxAge Max file age (in seconds). If file age will exceed this value then it'll be removed on next
* treatment (and therefore be reloaded).
*/
- public LimitedAgeDiscCache(File cacheDir, long maxAge) {
- this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator(), maxAge);
+ public LimitedAgeDiskCache(File cacheDir, long maxAge) {
+ this(cacheDir, null, DefaultConfigurationFactory.createFileNameGenerator(), maxAge);
+ }
+
+ /**
+ * @param cacheDir Directory for file caching
+ * @param maxAge Max file age (in seconds). If file age will exceed this value then it'll be removed on next
+ * treatment (and therefore be reloaded).
+ */
+ public LimitedAgeDiskCache(File cacheDir, File reserveCacheDir, long maxAge) {
+ this(cacheDir, reserveCacheDir, DefaultConfigurationFactory.createFileNameGenerator(), maxAge);
}
/**
* @param cacheDir Directory for file caching
+ * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
* @param fileNameGenerator Name generator for cached files
* @param maxAge Max file age (in seconds). If file age will exceed this value then it'll be removed on next
* treatment (and therefore be reloaded).
*/
- public LimitedAgeDiscCache(File cacheDir, FileNameGenerator fileNameGenerator, long maxAge) {
- super(cacheDir, fileNameGenerator);
+ public LimitedAgeDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator, long maxAge) {
+ super(cacheDir, reserveCacheDir, fileNameGenerator);
this.maxFileAge = maxAge * 1000; // to milliseconds
}
@Override
- public void put(String key, File file) {
- long currentTime = System.currentTimeMillis();
- file.setLastModified(currentTime);
- loadingDates.put(file, currentTime);
- }
-
- @Override
- public File get(String key) {
- File file = super.get(key);
- if (file.exists()) {
+ public File get(String imageUri) {
+ File file = super.get(imageUri);
+ if (file != null && file.exists()) {
boolean cached;
Long loadingDate = loadingDates.get(file);
if (loadingDate == null) {
@@ -86,4 +91,37 @@ public File get(String key) {
}
return file;
}
+
+ @Override
+ public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
+ boolean saved = super.save(imageUri, imageStream, listener);
+ rememberUsage(imageUri);
+ return saved;
+ }
+
+ @Override
+ public boolean save(String imageUri, Bitmap bitmap) throws IOException {
+ boolean saved = super.save(imageUri, bitmap);
+ rememberUsage(imageUri);
+ return saved;
+ }
+
+ @Override
+ public boolean remove(String imageUri) {
+ loadingDates.remove(getFile(imageUri));
+ return super.remove(imageUri);
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ loadingDates.clear();
+ }
+
+ private void rememberUsage(String imageUri) {
+ File file = getFile(imageUri);
+ long currentTime = System.currentTimeMillis();
+ file.setLastModified(currentTime);
+ loadingDates.put(file, currentTime);
+ }
}
\ No newline at end of file
diff --git a/library/src/com/nostra13/universalimageloader/cache/disc/impl/UnlimitedDiscCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/UnlimitedDiskCache.java
similarity index 51%
rename from library/src/com/nostra13/universalimageloader/cache/disc/impl/UnlimitedDiscCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/UnlimitedDiskCache.java
index 70eed5458..03fad1e96 100644
--- a/library/src/com/nostra13/universalimageloader/cache/disc/impl/UnlimitedDiscCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/UnlimitedDiskCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,37 +15,38 @@
*******************************************************************************/
package com.nostra13.universalimageloader.cache.disc.impl;
-import com.nostra13.universalimageloader.cache.disc.BaseDiscCache;
-import com.nostra13.universalimageloader.cache.disc.DiscCacheAware;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
-import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;
import java.io.File;
/**
- * Default implementation of {@linkplain DiscCacheAware disc cache}. Cache size is unlimited.
+ * Default implementation of {@linkplain com.nostra13.universalimageloader.cache.disc.DiskCache disk cache}.
+ * Cache size is unlimited.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @see BaseDiscCache
* @since 1.0.0
*/
-public class UnlimitedDiscCache extends BaseDiscCache {
-
+public class UnlimitedDiskCache extends BaseDiskCache {
/** @param cacheDir Directory for file caching */
- public UnlimitedDiscCache(File cacheDir) {
- this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator());
+ public UnlimitedDiskCache(File cacheDir) {
+ super(cacheDir);
}
/**
- * @param cacheDir Directory for file caching
- * @param fileNameGenerator Name generator for cached files
+ * @param cacheDir Directory for file caching
+ * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
*/
- public UnlimitedDiscCache(File cacheDir, FileNameGenerator fileNameGenerator) {
- super(cacheDir, fileNameGenerator);
+ public UnlimitedDiskCache(File cacheDir, File reserveCacheDir) {
+ super(cacheDir, reserveCacheDir);
}
- @Override
- public void put(String key, File file) {
- // Do nothing
+ /**
+ * @param cacheDir Directory for file caching
+ * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
+ * @param fileNameGenerator {@linkplain com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator
+ * Name generator} for cached files
+ */
+ public UnlimitedDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
+ super(cacheDir, reserveCacheDir, fileNameGenerator);
}
}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/DiskLruCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/DiskLruCache.java
new file mode 100644
index 000000000..6e18f31ff
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/DiskLruCache.java
@@ -0,0 +1,974 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nostra13.universalimageloader.cache.disc.impl.ext;
+
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A cache that uses a bounded amount of space on a filesystem. Each cache
+ * entry has a string key and a fixed number of values. Each key must match
+ * the regex [a-z0-9_-]{1,64}. Values are byte sequences,
+ * accessible as streams or files. Each value must be between {@code 0} and
+ * {@code Integer.MAX_VALUE} bytes in length.
+ *
+ * The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ *
This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ *
Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ *
+ * - When an entry is being created it is necessary to
+ * supply a full set of values; the empty value should be used as a
+ * placeholder if necessary.
+ *
- When an entry is being edited, it is not necessary
+ * to supply data for every value; values default to their previous
+ * value.
+ *
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ * Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ *
This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ */
+final class DiskLruCache implements Closeable {
+ static final String JOURNAL_FILE = "journal";
+ static final String JOURNAL_FILE_TEMP = "journal.tmp";
+ static final String JOURNAL_FILE_BACKUP = "journal.bkp";
+ static final String MAGIC = "libcore.io.DiskLruCache";
+ static final String VERSION_1 = "1";
+ static final long ANY_SEQUENCE_NUMBER = -1;
+ static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}");
+ private static final String CLEAN = "CLEAN";
+ private static final String DIRTY = "DIRTY";
+ private static final String REMOVE = "REMOVE";
+ private static final String READ = "READ";
+
+ /*
+ * This cache uses a journal file named "journal". A typical journal file
+ * looks like this:
+ * libcore.io.DiskLruCache
+ * 1
+ * 100
+ * 2
+ *
+ * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
+ * DIRTY 335c4c6028171cfddfbaae1a9c313c52
+ * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
+ * REMOVE 335c4c6028171cfddfbaae1a9c313c52
+ * DIRTY 1ab96a171faeeee38496d8b330771a7a
+ * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
+ * READ 335c4c6028171cfddfbaae1a9c313c52
+ * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
+ *
+ * The first five lines of the journal form its header. They are the
+ * constant string "libcore.io.DiskLruCache", the disk cache's version,
+ * the application's version, the value count, and a blank line.
+ *
+ * Each of the subsequent lines in the file is a record of the state of a
+ * cache entry. Each line contains space-separated values: a state, a key,
+ * and optional state-specific values.
+ * o DIRTY lines track that an entry is actively being created or updated.
+ * Every successful DIRTY action should be followed by a CLEAN or REMOVE
+ * action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+ * temporary files may need to be deleted.
+ * o CLEAN lines track a cache entry that has been successfully published
+ * and may be read. A publish line is followed by the lengths of each of
+ * its values.
+ * o READ lines track accesses for LRU.
+ * o REMOVE lines track entries that have been deleted.
+ *
+ * The journal file is appended to as cache operations occur. The journal may
+ * occasionally be compacted by dropping redundant lines. A temporary file named
+ * "journal.tmp" will be used during compaction; that file should be deleted if
+ * it exists when the cache is opened.
+ */
+
+ private final File directory;
+ private final File journalFile;
+ private final File journalFileTmp;
+ private final File journalFileBackup;
+ private final int appVersion;
+ private long maxSize;
+ private int maxFileCount;
+ private final int valueCount;
+ private long size = 0;
+ private int fileCount = 0;
+ private Writer journalWriter;
+ private final LinkedHashMap lruEntries =
+ new LinkedHashMap(0, 0.75f, true);
+ private int redundantOpCount;
+
+ /**
+ * To differentiate between old and current snapshots, each entry is given
+ * a sequence number each time an edit is committed. A snapshot is stale if
+ * its sequence number is not equal to its entry's sequence number.
+ */
+ private long nextSequenceNumber = 0;
+
+ /** This cache uses a single background thread to evict entries. */
+ final ThreadPoolExecutor executorService =
+ new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue());
+ private final Callable cleanupCallable = new Callable() {
+ public Void call() throws Exception {
+ synchronized (DiskLruCache.this) {
+ if (journalWriter == null) {
+ return null; // Closed.
+ }
+ trimToSize();
+ trimToFileCount();
+ if (journalRebuildRequired()) {
+ rebuildJournal();
+ redundantOpCount = 0;
+ }
+ }
+ return null;
+ }
+ };
+
+ private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount) {
+ this.directory = directory;
+ this.appVersion = appVersion;
+ this.journalFile = new File(directory, JOURNAL_FILE);
+ this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
+ this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
+ this.valueCount = valueCount;
+ this.maxSize = maxSize;
+ this.maxFileCount = maxFileCount;
+ }
+
+ /**
+ * Opens the cache in {@code directory}, creating a cache if none exists
+ * there.
+ *
+ * @param directory a writable directory
+ * @param valueCount the number of values per cache entry. Must be positive.
+ * @param maxSize the maximum number of bytes this cache should use to store
+ * @param maxFileCount the maximum file count this cache should store
+ * @throws IOException if reading or writing the cache directory fails
+ */
+ public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount)
+ throws IOException {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+ if (maxFileCount <= 0) {
+ throw new IllegalArgumentException("maxFileCount <= 0");
+ }
+ if (valueCount <= 0) {
+ throw new IllegalArgumentException("valueCount <= 0");
+ }
+
+ // If a bkp file exists, use it instead.
+ File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
+ if (backupFile.exists()) {
+ File journalFile = new File(directory, JOURNAL_FILE);
+ // If journal file also exists just delete backup file.
+ if (journalFile.exists()) {
+ backupFile.delete();
+ } else {
+ renameTo(backupFile, journalFile, false);
+ }
+ }
+
+ // Prefer to pick up where we left off.
+ DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);
+ if (cache.journalFile.exists()) {
+ try {
+ cache.readJournal();
+ cache.processJournal();
+ cache.journalWriter = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII));
+ return cache;
+ } catch (IOException journalIsCorrupt) {
+ System.out
+ .println("DiskLruCache "
+ + directory
+ + " is corrupt: "
+ + journalIsCorrupt.getMessage()
+ + ", removing");
+ cache.delete();
+ }
+ }
+
+ // Create a new empty cache.
+ directory.mkdirs();
+ cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);
+ cache.rebuildJournal();
+ return cache;
+ }
+
+ private void readJournal() throws IOException {
+ StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
+ try {
+ String magic = reader.readLine();
+ String version = reader.readLine();
+ String appVersionString = reader.readLine();
+ String valueCountString = reader.readLine();
+ String blank = reader.readLine();
+ if (!MAGIC.equals(magic)
+ || !VERSION_1.equals(version)
+ || !Integer.toString(appVersion).equals(appVersionString)
+ || !Integer.toString(valueCount).equals(valueCountString)
+ || !"".equals(blank)) {
+ throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ + valueCountString + ", " + blank + "]");
+ }
+
+ int lineCount = 0;
+ while (true) {
+ try {
+ readJournalLine(reader.readLine());
+ lineCount++;
+ } catch (EOFException endOfJournal) {
+ break;
+ }
+ }
+ redundantOpCount = lineCount - lruEntries.size();
+ } finally {
+ Util.closeQuietly(reader);
+ }
+ }
+
+ private void readJournalLine(String line) throws IOException {
+ int firstSpace = line.indexOf(' ');
+ if (firstSpace == -1) {
+ throw new IOException("unexpected journal line: " + line);
+ }
+
+ int keyBegin = firstSpace + 1;
+ int secondSpace = line.indexOf(' ', keyBegin);
+ final String key;
+ if (secondSpace == -1) {
+ key = line.substring(keyBegin);
+ if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
+ lruEntries.remove(key);
+ return;
+ }
+ } else {
+ key = line.substring(keyBegin, secondSpace);
+ }
+
+ Entry entry = lruEntries.get(key);
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ }
+
+ if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
+ String[] parts = line.substring(secondSpace + 1).split(" ");
+ entry.readable = true;
+ entry.currentEditor = null;
+ entry.setLengths(parts);
+ } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
+ entry.currentEditor = new Editor(entry);
+ } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
+ // This work was already done by calling lruEntries.get().
+ } else {
+ throw new IOException("unexpected journal line: " + line);
+ }
+ }
+
+ /**
+ * Computes the initial size and collects garbage as a part of opening the
+ * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+ */
+ private void processJournal() throws IOException {
+ deleteIfExists(journalFileTmp);
+ for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) {
+ Entry entry = i.next();
+ if (entry.currentEditor == null) {
+ for (int t = 0; t < valueCount; t++) {
+ size += entry.lengths[t];
+ fileCount++;
+ }
+ } else {
+ entry.currentEditor = null;
+ for (int t = 0; t < valueCount; t++) {
+ deleteIfExists(entry.getCleanFile(t));
+ deleteIfExists(entry.getDirtyFile(t));
+ }
+ i.remove();
+ }
+ }
+ }
+
+ /**
+ * Creates a new journal that omits redundant information. This replaces the
+ * current journal if it exists.
+ */
+ private synchronized void rebuildJournal() throws IOException {
+ if (journalWriter != null) {
+ journalWriter.close();
+ }
+
+ Writer writer = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
+ try {
+ writer.write(MAGIC);
+ writer.write("\n");
+ writer.write(VERSION_1);
+ writer.write("\n");
+ writer.write(Integer.toString(appVersion));
+ writer.write("\n");
+ writer.write(Integer.toString(valueCount));
+ writer.write("\n");
+ writer.write("\n");
+
+ for (Entry entry : lruEntries.values()) {
+ if (entry.currentEditor != null) {
+ writer.write(DIRTY + ' ' + entry.key + '\n');
+ } else {
+ writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+ }
+ }
+ } finally {
+ writer.close();
+ }
+
+ if (journalFile.exists()) {
+ renameTo(journalFile, journalFileBackup, true);
+ }
+ renameTo(journalFileTmp, journalFile, false);
+ journalFileBackup.delete();
+
+ journalWriter = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
+ }
+
+ private static void deleteIfExists(File file) throws IOException {
+ if (file.exists() && !file.delete()) {
+ throw new IOException();
+ }
+ }
+
+ private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
+ if (deleteDestination) {
+ deleteIfExists(to);
+ }
+ if (!from.renameTo(to)) {
+ throw new IOException();
+ }
+ }
+
+ /**
+ * Returns a snapshot of the entry named {@code key}, or null if it doesn't
+ * exist is not currently readable. If a value is returned, it is moved to
+ * the head of the LRU queue.
+ */
+ public synchronized Snapshot get(String key) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (entry == null) {
+ return null;
+ }
+
+ if (!entry.readable) {
+ return null;
+ }
+
+ // Open all streams eagerly to guarantee that we see a single published
+ // snapshot. If we opened streams lazily then the streams could come
+ // from different edits.
+ File[] files = new File[valueCount];
+ InputStream[] ins = new InputStream[valueCount];
+ try {
+ File file;
+ for (int i = 0; i < valueCount; i++) {
+ file = entry.getCleanFile(i);
+ files[i] = file;
+ ins[i] = new FileInputStream(file);
+ }
+ } catch (FileNotFoundException e) {
+ // A file must have been deleted manually!
+ for (int i = 0; i < valueCount; i++) {
+ if (ins[i] != null) {
+ Util.closeQuietly(ins[i]);
+ } else {
+ break;
+ }
+ }
+ return null;
+ }
+
+ redundantOpCount++;
+ journalWriter.append(READ + ' ' + key + '\n');
+ if (journalRebuildRequired()) {
+ executorService.submit(cleanupCallable);
+ }
+
+ return new Snapshot(key, entry.sequenceNumber, files, ins, entry.lengths);
+ }
+
+ /**
+ * Returns an editor for the entry named {@code key}, or null if another
+ * edit is in progress.
+ */
+ public Editor edit(String key) throws IOException {
+ return edit(key, ANY_SEQUENCE_NUMBER);
+ }
+
+ private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
+ || entry.sequenceNumber != expectedSequenceNumber)) {
+ return null; // Snapshot is stale.
+ }
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ } else if (entry.currentEditor != null) {
+ return null; // Another edit is in progress.
+ }
+
+ Editor editor = new Editor(entry);
+ entry.currentEditor = editor;
+
+ // Flush the journal before creating files to prevent file leaks.
+ journalWriter.write(DIRTY + ' ' + key + '\n');
+ journalWriter.flush();
+ return editor;
+ }
+
+ /** Returns the directory where this cache stores its data. */
+ public File getDirectory() {
+ return directory;
+ }
+
+ /**
+ * Returns the maximum number of bytes that this cache should use to store
+ * its data.
+ */
+ public synchronized long getMaxSize() {
+ return maxSize;
+ }
+
+ /** Returns the maximum number of files that this cache should store */
+ public synchronized int getMaxFileCount() {
+ return maxFileCount;
+ }
+
+ /**
+ * Changes the maximum number of bytes the cache can store and queues a job
+ * to trim the existing store, if necessary.
+ */
+ public synchronized void setMaxSize(long maxSize) {
+ this.maxSize = maxSize;
+ executorService.submit(cleanupCallable);
+ }
+
+ /**
+ * Returns the number of bytes currently being used to store the values in
+ * this cache. This may be greater than the max size if a background
+ * deletion is pending.
+ */
+ public synchronized long size() {
+ return size;
+ }
+
+ /**
+ * Returns the number of files currently being used to store the values in
+ * this cache. This may be greater than the max file count if a background
+ * deletion is pending.
+ */
+ public synchronized long fileCount() {
+ return fileCount;
+ }
+
+ private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
+ Entry entry = editor.entry;
+ if (entry.currentEditor != editor) {
+ throw new IllegalStateException();
+ }
+
+ // If this edit is creating the entry for the first time, every index must have a value.
+ if (success && !entry.readable) {
+ for (int i = 0; i < valueCount; i++) {
+ if (!editor.written[i]) {
+ editor.abort();
+ throw new IllegalStateException("Newly created entry didn't create value for index " + i);
+ }
+ if (!entry.getDirtyFile(i).exists()) {
+ editor.abort();
+ return;
+ }
+ }
+ }
+
+ for (int i = 0; i < valueCount; i++) {
+ File dirty = entry.getDirtyFile(i);
+ if (success) {
+ if (dirty.exists()) {
+ File clean = entry.getCleanFile(i);
+ dirty.renameTo(clean);
+ long oldLength = entry.lengths[i];
+ long newLength = clean.length();
+ entry.lengths[i] = newLength;
+ size = size - oldLength + newLength;
+ fileCount++;
+ }
+ } else {
+ deleteIfExists(dirty);
+ }
+ }
+
+ redundantOpCount++;
+ entry.currentEditor = null;
+ if (entry.readable | success) {
+ entry.readable = true;
+ journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+ if (success) {
+ entry.sequenceNumber = nextSequenceNumber++;
+ }
+ } else {
+ lruEntries.remove(entry.key);
+ journalWriter.write(REMOVE + ' ' + entry.key + '\n');
+ }
+ journalWriter.flush();
+
+ if (size > maxSize || fileCount > maxFileCount || journalRebuildRequired()) {
+ executorService.submit(cleanupCallable);
+ }
+ }
+
+ /**
+ * We only rebuild the journal when it will halve the size of the journal
+ * and eliminate at least 2000 ops.
+ */
+ private boolean journalRebuildRequired() {
+ final int redundantOpCompactThreshold = 2000;
+ return redundantOpCount >= redundantOpCompactThreshold //
+ && redundantOpCount >= lruEntries.size();
+ }
+
+ /**
+ * Drops the entry for {@code key} if it exists and can be removed. Entries
+ * actively being edited cannot be removed.
+ *
+ * @return true if an entry was removed.
+ */
+ public synchronized boolean remove(String key) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (entry == null || entry.currentEditor != null) {
+ return false;
+ }
+
+ for (int i = 0; i < valueCount; i++) {
+ File file = entry.getCleanFile(i);
+ if (file.exists() && !file.delete()) {
+ throw new IOException("failed to delete " + file);
+ }
+ size -= entry.lengths[i];
+ fileCount--;
+ entry.lengths[i] = 0;
+ }
+
+ redundantOpCount++;
+ journalWriter.append(REMOVE + ' ' + key + '\n');
+ lruEntries.remove(key);
+
+ if (journalRebuildRequired()) {
+ executorService.submit(cleanupCallable);
+ }
+
+ return true;
+ }
+
+ /** Returns true if this cache has been closed. */
+ public synchronized boolean isClosed() {
+ return journalWriter == null;
+ }
+
+ private void checkNotClosed() {
+ if (journalWriter == null) {
+ throw new IllegalStateException("cache is closed");
+ }
+ }
+
+ /** Force buffered operations to the filesystem. */
+ public synchronized void flush() throws IOException {
+ checkNotClosed();
+ trimToSize();
+ trimToFileCount();
+ journalWriter.flush();
+ }
+
+ /** Closes this cache. Stored values will remain on the filesystem. */
+ public synchronized void close() throws IOException {
+ if (journalWriter == null) {
+ return; // Already closed.
+ }
+ for (Entry entry : new ArrayList(lruEntries.values())) {
+ if (entry.currentEditor != null) {
+ entry.currentEditor.abort();
+ }
+ }
+ trimToSize();
+ trimToFileCount();
+ journalWriter.close();
+ journalWriter = null;
+ }
+
+ private void trimToSize() throws IOException {
+ while (size > maxSize) {
+ Map.Entry toEvict = lruEntries.entrySet().iterator().next();
+ remove(toEvict.getKey());
+ }
+ }
+
+ private void trimToFileCount() throws IOException {
+ while (fileCount > maxFileCount) {
+ Map.Entry toEvict = lruEntries.entrySet().iterator().next();
+ remove(toEvict.getKey());
+ }
+ }
+
+ /**
+ * Closes the cache and deletes all of its stored values. This will delete
+ * all files in the cache directory including files that weren't created by
+ * the cache.
+ */
+ public void delete() throws IOException {
+ close();
+ Util.deleteContents(directory);
+ }
+
+ private void validateKey(String key) {
+ Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\"");
+ }
+ }
+
+ private static String inputStreamToString(InputStream in) throws IOException {
+ return Util.readFully(new InputStreamReader(in, Util.UTF_8));
+ }
+
+ /** A snapshot of the values for an entry. */
+ public final class Snapshot implements Closeable {
+ private final String key;
+ private final long sequenceNumber;
+ private File[] files;
+ private final InputStream[] ins;
+ private final long[] lengths;
+
+ private Snapshot(String key, long sequenceNumber, File[] files, InputStream[] ins, long[] lengths) {
+ this.key = key;
+ this.sequenceNumber = sequenceNumber;
+ this.files = files;
+ this.ins = ins;
+ this.lengths = lengths;
+ }
+
+ /**
+ * Returns an editor for this snapshot's entry, or null if either the
+ * entry has changed since this snapshot was created or if another edit
+ * is in progress.
+ */
+ public Editor edit() throws IOException {
+ return DiskLruCache.this.edit(key, sequenceNumber);
+ }
+
+ /** Returns file with the value for {@code index}. */
+ public File getFile(int index) {
+ return files[index];
+ }
+
+ /** Returns the unbuffered stream with the value for {@code index}. */
+ public InputStream getInputStream(int index) {
+ return ins[index];
+ }
+
+ /** Returns the string value for {@code index}. */
+ public String getString(int index) throws IOException {
+ return inputStreamToString(getInputStream(index));
+ }
+
+ /** Returns the byte length of the value for {@code index}. */
+ public long getLength(int index) {
+ return lengths[index];
+ }
+
+ public void close() {
+ for (InputStream in : ins) {
+ Util.closeQuietly(in);
+ }
+ }
+ }
+
+ private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ // Eat all writes silently. Nom nom.
+ }
+ };
+
+ /** Edits the values for an entry. */
+ public final class Editor {
+ private final Entry entry;
+ private final boolean[] written;
+ private boolean hasErrors;
+ private boolean committed;
+
+ private Editor(Entry entry) {
+ this.entry = entry;
+ this.written = (entry.readable) ? null : new boolean[valueCount];
+ }
+
+ /**
+ * Returns an unbuffered input stream to read the last committed value,
+ * or null if no value has been committed.
+ */
+ public InputStream newInputStream(int index) throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (entry.currentEditor != this) {
+ throw new IllegalStateException();
+ }
+ if (!entry.readable) {
+ return null;
+ }
+ try {
+ return new FileInputStream(entry.getCleanFile(index));
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns the last committed value as a string, or null if no value
+ * has been committed.
+ */
+ public String getString(int index) throws IOException {
+ InputStream in = newInputStream(index);
+ return in != null ? inputStreamToString(in) : null;
+ }
+
+ /**
+ * Returns a new unbuffered output stream to write the value at
+ * {@code index}. If the underlying output stream encounters errors
+ * when writing to the filesystem, this edit will be aborted when
+ * {@link #commit} is called. The returned output stream does not throw
+ * IOExceptions.
+ */
+ public OutputStream newOutputStream(int index) throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (entry.currentEditor != this) {
+ throw new IllegalStateException();
+ }
+ if (!entry.readable) {
+ written[index] = true;
+ }
+ File dirtyFile = entry.getDirtyFile(index);
+ FileOutputStream outputStream;
+ try {
+ outputStream = new FileOutputStream(dirtyFile);
+ } catch (FileNotFoundException e) {
+ // Attempt to recreate the cache directory.
+ directory.mkdirs();
+ try {
+ outputStream = new FileOutputStream(dirtyFile);
+ } catch (FileNotFoundException e2) {
+ // We are unable to recover. Silently eat the writes.
+ return NULL_OUTPUT_STREAM;
+ }
+ }
+ return new FaultHidingOutputStream(outputStream);
+ }
+ }
+
+ /** Sets the value at {@code index} to {@code value}. */
+ public void set(int index, String value) throws IOException {
+ Writer writer = null;
+ try {
+ writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);
+ writer.write(value);
+ } finally {
+ Util.closeQuietly(writer);
+ }
+ }
+
+ /**
+ * Commits this edit so it is visible to readers. This releases the
+ * edit lock so another edit may be started on the same key.
+ */
+ public void commit() throws IOException {
+ if (hasErrors) {
+ completeEdit(this, false);
+ remove(entry.key); // The previous entry is stale.
+ } else {
+ completeEdit(this, true);
+ }
+ committed = true;
+ }
+
+ /**
+ * Aborts this edit. This releases the edit lock so another edit may be
+ * started on the same key.
+ */
+ public void abort() throws IOException {
+ completeEdit(this, false);
+ }
+
+ public void abortUnlessCommitted() {
+ if (!committed) {
+ try {
+ abort();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ private class FaultHidingOutputStream extends FilterOutputStream {
+ private FaultHidingOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ @Override public void write(int oneByte) {
+ try {
+ out.write(oneByte);
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void write(byte[] buffer, int offset, int length) {
+ try {
+ out.write(buffer, offset, length);
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void close() {
+ try {
+ out.close();
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void flush() {
+ try {
+ out.flush();
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+ }
+ }
+
+ private final class Entry {
+ private final String key;
+
+ /** Lengths of this entry's files. */
+ private final long[] lengths;
+
+ /** True if this entry has ever been published. */
+ private boolean readable;
+
+ /** The ongoing edit or null if this entry is not being edited. */
+ private Editor currentEditor;
+
+ /** The sequence number of the most recently committed edit to this entry. */
+ private long sequenceNumber;
+
+ private Entry(String key) {
+ this.key = key;
+ this.lengths = new long[valueCount];
+ }
+
+ public String getLengths() throws IOException {
+ StringBuilder result = new StringBuilder();
+ for (long size : lengths) {
+ result.append(' ').append(size);
+ }
+ return result.toString();
+ }
+
+ /** Set lengths using decimal numbers like "10123". */
+ private void setLengths(String[] strings) throws IOException {
+ if (strings.length != valueCount) {
+ throw invalidLengths(strings);
+ }
+
+ try {
+ for (int i = 0; i < strings.length; i++) {
+ lengths[i] = Long.parseLong(strings[i]);
+ }
+ } catch (NumberFormatException e) {
+ throw invalidLengths(strings);
+ }
+ }
+
+ private IOException invalidLengths(String[] strings) throws IOException {
+ throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
+ }
+
+ public File getCleanFile(int i) {
+ return new File(directory, key + "." + i);
+ }
+
+ public File getDirtyFile(int i) {
+ return new File(directory, key + "." + i + ".tmp");
+ }
+ }
+}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/LruDiskCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/LruDiskCache.java
new file mode 100644
index 000000000..aa0d91ec0
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/LruDiskCache.java
@@ -0,0 +1,238 @@
+/*******************************************************************************
+ * Copyright 2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.cache.disc.impl.ext;
+
+import android.graphics.Bitmap;
+import com.nostra13.universalimageloader.cache.disc.DiskCache;
+import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
+import com.nostra13.universalimageloader.utils.IoUtils;
+import com.nostra13.universalimageloader.utils.L;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Disk cache based on "Least-Recently Used" principle. Adapter pattern, adapts
+ * {@link com.nostra13.universalimageloader.cache.disc.impl.ext.DiskLruCache DiskLruCache} to
+ * {@link com.nostra13.universalimageloader.cache.disc.DiskCache DiskCache}
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @see FileNameGenerator
+ * @since 1.9.2
+ */
+public class LruDiskCache implements DiskCache {
+ /** {@value */
+ public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 Kb
+ /** {@value */
+ public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG;
+ /** {@value */
+ public static final int DEFAULT_COMPRESS_QUALITY = 100;
+
+ private static final String ERROR_ARG_NULL = " argument must be not null";
+ private static final String ERROR_ARG_NEGATIVE = " argument must be positive number";
+
+ protected DiskLruCache cache;
+ private File reserveCacheDir;
+
+ protected final FileNameGenerator fileNameGenerator;
+
+ protected int bufferSize = DEFAULT_BUFFER_SIZE;
+
+ protected Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
+ protected int compressQuality = DEFAULT_COMPRESS_QUALITY;
+
+ /**
+ * @param cacheDir Directory for file caching
+ * @param fileNameGenerator {@linkplain com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator
+ * Name generator} for cached files. Generated names must match the regex
+ * [a-z0-9_-]{1,64}
+ * @param cacheMaxSize Max cache size in bytes. 0 means cache size is unlimited.
+ * @throws IOException if cache can't be initialized (e.g. "No space left on device")
+ */
+ public LruDiskCache(File cacheDir, FileNameGenerator fileNameGenerator, long cacheMaxSize) throws IOException {
+ this(cacheDir, null, fileNameGenerator, cacheMaxSize, 0);
+ }
+
+ /**
+ * @param cacheDir Directory for file caching
+ * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
+ * @param fileNameGenerator {@linkplain com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator
+ * Name generator} for cached files. Generated names must match the regex
+ * [a-z0-9_-]{1,64}
+ * @param cacheMaxSize Max cache size in bytes. 0 means cache size is unlimited.
+ * @param cacheMaxFileCount Max file count in cache. 0 means file count is unlimited.
+ * @throws IOException if cache can't be initialized (e.g. "No space left on device")
+ */
+ public LruDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator, long cacheMaxSize,
+ int cacheMaxFileCount) throws IOException {
+ if (cacheDir == null) {
+ throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL);
+ }
+ if (cacheMaxSize < 0) {
+ throw new IllegalArgumentException("cacheMaxSize" + ERROR_ARG_NEGATIVE);
+ }
+ if (cacheMaxFileCount < 0) {
+ throw new IllegalArgumentException("cacheMaxFileCount" + ERROR_ARG_NEGATIVE);
+ }
+ if (fileNameGenerator == null) {
+ throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL);
+ }
+
+ if (cacheMaxSize == 0) {
+ cacheMaxSize = Long.MAX_VALUE;
+ }
+ if (cacheMaxFileCount == 0) {
+ cacheMaxFileCount = Integer.MAX_VALUE;
+ }
+
+ this.reserveCacheDir = reserveCacheDir;
+ this.fileNameGenerator = fileNameGenerator;
+ initCache(cacheDir, reserveCacheDir, cacheMaxSize, cacheMaxFileCount);
+ }
+
+ private void initCache(File cacheDir, File reserveCacheDir, long cacheMaxSize, int cacheMaxFileCount)
+ throws IOException {
+ try {
+ cache = DiskLruCache.open(cacheDir, 1, 1, cacheMaxSize, cacheMaxFileCount);
+ } catch (IOException e) {
+ L.e(e);
+ if (reserveCacheDir != null) {
+ initCache(reserveCacheDir, null, cacheMaxSize, cacheMaxFileCount);
+ }
+ if (cache == null) {
+ throw e; //new RuntimeException("Can't initialize disk cache", e);
+ }
+ }
+ }
+
+ @Override
+ public File getDirectory() {
+ return cache.getDirectory();
+ }
+
+ @Override
+ public File get(String imageUri) {
+ DiskLruCache.Snapshot snapshot = null;
+ try {
+ snapshot = cache.get(getKey(imageUri));
+ return snapshot == null ? null : snapshot.getFile(0);
+ } catch (IOException e) {
+ L.e(e);
+ return null;
+ } finally {
+ if (snapshot != null) {
+ snapshot.close();
+ }
+ }
+ }
+
+ @Override
+ public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
+ DiskLruCache.Editor editor = cache.edit(getKey(imageUri));
+ if (editor == null) {
+ return false;
+ }
+
+ OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize);
+ boolean copied = false;
+ try {
+ copied = IoUtils.copyStream(imageStream, os, listener, bufferSize);
+ } finally {
+ IoUtils.closeSilently(os);
+ if (copied) {
+ editor.commit();
+ } else {
+ editor.abort();
+ }
+ }
+ return copied;
+ }
+
+ @Override
+ public boolean save(String imageUri, Bitmap bitmap) throws IOException {
+ DiskLruCache.Editor editor = cache.edit(getKey(imageUri));
+ if (editor == null) {
+ return false;
+ }
+
+ OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize);
+ boolean savedSuccessfully = false;
+ try {
+ savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
+ } finally {
+ IoUtils.closeSilently(os);
+ }
+ if (savedSuccessfully) {
+ editor.commit();
+ } else {
+ editor.abort();
+ }
+ return savedSuccessfully;
+ }
+
+ @Override
+ public boolean remove(String imageUri) {
+ try {
+ return cache.remove(getKey(imageUri));
+ } catch (IOException e) {
+ L.e(e);
+ return false;
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+ cache.close();
+ } catch (IOException e) {
+ L.e(e);
+ }
+ cache = null;
+ }
+
+ @Override
+ public void clear() {
+ try {
+ cache.delete();
+ } catch (IOException e) {
+ L.e(e);
+ }
+ try {
+ initCache(cache.getDirectory(), reserveCacheDir, cache.getMaxSize(), cache.getMaxFileCount());
+ } catch (IOException e) {
+ L.e(e);
+ }
+ }
+
+ private String getKey(String imageUri) {
+ return fileNameGenerator.generate(imageUri);
+ }
+
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+ public void setCompressFormat(Bitmap.CompressFormat compressFormat) {
+ this.compressFormat = compressFormat;
+ }
+
+ public void setCompressQuality(int compressQuality) {
+ this.compressQuality = compressQuality;
+ }
+}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/StrictLineReader.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/StrictLineReader.java
new file mode 100644
index 000000000..0f3e0f56e
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/StrictLineReader.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nostra13.universalimageloader.cache.disc.impl.ext;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+/**
+ * Buffers input from an {@link InputStream} for reading lines.
+ *
+ * This class is used for buffered reading of lines. For purposes of this class, a line ends
+ * with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
+ * line at end of input is invalid and will be ignored, the caller may use {@code
+ * hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
+ *
+ *
This class is intended for reading input that strictly consists of lines, such as line-based
+ * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
+ * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
+ * end-of-input reporting and a more restrictive definition of a line.
+ *
+ *
This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
+ * and 10, respectively, and the representation of no other character contains these values.
+ * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
+ * The default charset is US_ASCII.
+ */
+class StrictLineReader implements Closeable {
+ private static final byte CR = (byte) '\r';
+ private static final byte LF = (byte) '\n';
+
+ private final InputStream in;
+ private final Charset charset;
+
+ /*
+ * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
+ * and the data in the range [pos, end) is buffered for reading. At end of input, if there is
+ * an unterminated line, we set end == -1, otherwise end == pos. If the underlying
+ * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
+ */
+ private byte[] buf;
+ private int pos;
+ private int end;
+
+ /**
+ * Constructs a new {@code LineReader} with the specified charset and the default capacity.
+ *
+ * @param in the {@code InputStream} to read data from.
+ * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
+ * supported.
+ * @throws NullPointerException if {@code in} or {@code charset} is null.
+ * @throws IllegalArgumentException if the specified charset is not supported.
+ */
+ public StrictLineReader(InputStream in, Charset charset) {
+ this(in, 8192, charset);
+ }
+
+ /**
+ * Constructs a new {@code LineReader} with the specified capacity and charset.
+ *
+ * @param in the {@code InputStream} to read data from.
+ * @param capacity the capacity of the buffer.
+ * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
+ * supported.
+ * @throws NullPointerException if {@code in} or {@code charset} is null.
+ * @throws IllegalArgumentException if {@code capacity} is negative or zero
+ * or the specified charset is not supported.
+ */
+ public StrictLineReader(InputStream in, int capacity, Charset charset) {
+ if (in == null || charset == null) {
+ throw new NullPointerException();
+ }
+ if (capacity < 0) {
+ throw new IllegalArgumentException("capacity <= 0");
+ }
+ if (!(charset.equals(Util.US_ASCII))) {
+ throw new IllegalArgumentException("Unsupported encoding");
+ }
+
+ this.in = in;
+ this.charset = charset;
+ buf = new byte[capacity];
+ }
+
+ /**
+ * Closes the reader by closing the underlying {@code InputStream} and
+ * marking this reader as closed.
+ *
+ * @throws IOException for errors when closing the underlying {@code InputStream}.
+ */
+ public void close() throws IOException {
+ synchronized (in) {
+ if (buf != null) {
+ buf = null;
+ in.close();
+ }
+ }
+ }
+
+ /**
+ * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
+ * this end of line marker is not included in the result.
+ *
+ * @return the next line from the input.
+ * @throws IOException for underlying {@code InputStream} errors.
+ * @throws EOFException for the end of source stream.
+ */
+ public String readLine() throws IOException {
+ synchronized (in) {
+ if (buf == null) {
+ throw new IOException("LineReader is closed");
+ }
+
+ // Read more data if we are at the end of the buffered data.
+ // Though it's an error to read after an exception, we will let {@code fillBuf()}
+ // throw again if that happens; thus we need to handle end == -1 as well as end == pos.
+ if (pos >= end) {
+ fillBuf();
+ }
+ // Try to find LF in the buffered data and return the line if successful.
+ for (int i = pos; i != end; ++i) {
+ if (buf[i] == LF) {
+ int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
+ String res = new String(buf, pos, lineEnd - pos, charset.name());
+ pos = i + 1;
+ return res;
+ }
+ }
+
+ // Let's anticipate up to 80 characters on top of those already read.
+ ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
+ @Override
+ public String toString() {
+ int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
+ try {
+ return new String(buf, 0, length, charset.name());
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e); // Since we control the charset this will never happen.
+ }
+ }
+ };
+
+ while (true) {
+ out.write(buf, pos, end - pos);
+ // Mark unterminated line in case fillBuf throws EOFException or IOException.
+ end = -1;
+ fillBuf();
+ // Try to find LF in the buffered data and return the line if successful.
+ for (int i = pos; i != end; ++i) {
+ if (buf[i] == LF) {
+ if (i != pos) {
+ out.write(buf, pos, i - pos);
+ }
+ pos = i + 1;
+ return out.toString();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Reads new input data into the buffer. Call only with pos == end or end == -1,
+ * depending on the desired outcome if the function throws.
+ */
+ private void fillBuf() throws IOException {
+ int result = in.read(buf, 0, buf.length);
+ if (result == -1) {
+ throw new EOFException();
+ }
+ pos = 0;
+ end = result;
+ }
+}
+
diff --git a/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/Util.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/Util.java
new file mode 100644
index 000000000..fd7a4ba17
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/Util.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nostra13.universalimageloader.cache.disc.impl.ext;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+
+/** Junk drawer of utility methods. */
+final class Util {
+ static final Charset US_ASCII = Charset.forName("US-ASCII");
+ static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ private Util() {
+ }
+
+ static String readFully(Reader reader) throws IOException {
+ try {
+ StringWriter writer = new StringWriter();
+ char[] buffer = new char[1024];
+ int count;
+ while ((count = reader.read(buffer)) != -1) {
+ writer.write(buffer, 0, count);
+ }
+ return writer.toString();
+ } finally {
+ reader.close();
+ }
+ }
+
+ /**
+ * Deletes the contents of {@code dir}. Throws an IOException if any file
+ * could not be deleted, or if {@code dir} is not a readable directory.
+ */
+ static void deleteContents(File dir) throws IOException {
+ File[] files = dir.listFiles();
+ if (files == null) {
+ throw new IOException("not a readable directory: " + dir);
+ }
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteContents(file);
+ }
+ if (!file.delete()) {
+ throw new IOException("failed to delete file: " + file);
+ }
+ }
+ }
+
+ static void closeQuietly(/*Auto*/Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/library/src/com/nostra13/universalimageloader/cache/disc/naming/FileNameGenerator.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/naming/FileNameGenerator.java
similarity index 91%
rename from library/src/com/nostra13/universalimageloader/cache/disc/naming/FileNameGenerator.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/disc/naming/FileNameGenerator.java
index d955cc8f1..20e1d664b 100644
--- a/library/src/com/nostra13/universalimageloader/cache/disc/naming/FileNameGenerator.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/naming/FileNameGenerator.java
@@ -16,7 +16,7 @@
package com.nostra13.universalimageloader.cache.disc.naming;
/**
- * Generates names for files at disc cache
+ * Generates names for files at disk cache
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.3.1
@@ -24,5 +24,5 @@
public interface FileNameGenerator {
/** Generates unique file name for image defined by URI */
- public abstract String generate(String imageUri);
+ String generate(String imageUri);
}
diff --git a/library/src/com/nostra13/universalimageloader/cache/disc/naming/HashCodeFileNameGenerator.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/naming/HashCodeFileNameGenerator.java
similarity index 100%
rename from library/src/com/nostra13/universalimageloader/cache/disc/naming/HashCodeFileNameGenerator.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/disc/naming/HashCodeFileNameGenerator.java
diff --git a/library/src/com/nostra13/universalimageloader/cache/disc/naming/Md5FileNameGenerator.java b/library/src/main/java/com/nostra13/universalimageloader/cache/disc/naming/Md5FileNameGenerator.java
similarity index 100%
rename from library/src/com/nostra13/universalimageloader/cache/disc/naming/Md5FileNameGenerator.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/disc/naming/Md5FileNameGenerator.java
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/BaseMemoryCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/BaseMemoryCache.java
similarity index 67%
rename from library/src/com/nostra13/universalimageloader/cache/memory/BaseMemoryCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/BaseMemoryCache.java
index b8e08c806..4043719b5 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/BaseMemoryCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/BaseMemoryCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
*******************************************************************************/
package com.nostra13.universalimageloader.cache.memory;
+import android.graphics.Bitmap;
+
import java.lang.ref.Reference;
import java.util.*;
@@ -25,15 +27,15 @@
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
-public abstract class BaseMemoryCache implements MemoryCacheAware {
+public abstract class BaseMemoryCache implements MemoryCache {
/** Stores not strong references to objects */
- private final Map> softMap = Collections.synchronizedMap(new HashMap>());
+ private final Map> softMap = Collections.synchronizedMap(new HashMap>());
@Override
- public V get(K key) {
- V result = null;
- Reference reference = softMap.get(key);
+ public Bitmap get(String key) {
+ Bitmap result = null;
+ Reference reference = softMap.get(key);
if (reference != null) {
result = reference.get();
}
@@ -41,20 +43,21 @@ public V get(K key) {
}
@Override
- public boolean put(K key, V value) {
+ public boolean put(String key, Bitmap value) {
softMap.put(key, createReference(value));
return true;
}
@Override
- public void remove(K key) {
- softMap.remove(key);
+ public Bitmap remove(String key) {
+ Reference bmpRef = softMap.remove(key);
+ return bmpRef == null ? null : bmpRef.get();
}
@Override
- public Collection keys() {
+ public Collection keys() {
synchronized (softMap) {
- return new HashSet(softMap.keySet());
+ return new HashSet(softMap.keySet());
}
}
@@ -64,5 +67,5 @@ public void clear() {
}
/** Creates {@linkplain Reference not strong} reference of value */
- protected abstract Reference createReference(V value);
+ protected abstract Reference createReference(Bitmap value);
}
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/LimitedMemoryCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/LimitedMemoryCache.java
similarity index 85%
rename from library/src/com/nostra13/universalimageloader/cache/memory/LimitedMemoryCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/LimitedMemoryCache.java
index e68e2f419..d1782aaa6 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/LimitedMemoryCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/LimitedMemoryCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
*******************************************************************************/
package com.nostra13.universalimageloader.cache.memory;
+import android.graphics.Bitmap;
+
import com.nostra13.universalimageloader.utils.L;
import java.util.Collections;
@@ -33,7 +35,7 @@
* @see BaseMemoryCache
* @since 1.0.0
*/
-public abstract class LimitedMemoryCache extends BaseMemoryCache {
+public abstract class LimitedMemoryCache extends BaseMemoryCache {
private static final int MAX_NORMAL_CACHE_SIZE_IN_MB = 16;
private static final int MAX_NORMAL_CACHE_SIZE = MAX_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024;
@@ -47,7 +49,7 @@ public abstract class LimitedMemoryCache extends BaseMemoryCache {
* limit then first object is deleted (but it continue exist at {@link #softMap} and can be collected by GC at any
* time)
*/
- private final List hardCache = Collections.synchronizedList(new LinkedList());
+ private final List hardCache = Collections.synchronizedList(new LinkedList());
/** @param sizeLimit Maximum size for cache (in bytes) */
public LimitedMemoryCache(int sizeLimit) {
@@ -59,7 +61,7 @@ public LimitedMemoryCache(int sizeLimit) {
}
@Override
- public boolean put(K key, V value) {
+ public boolean put(String key, Bitmap value) {
boolean putSuccessfully = false;
// Try to add value to hard cache
int valueSize = getSize(value);
@@ -67,7 +69,7 @@ public boolean put(K key, V value) {
int curCacheSize = cacheSize.get();
if (valueSize < sizeLimit) {
while (curCacheSize + valueSize > sizeLimit) {
- V removedValue = removeNext();
+ Bitmap removedValue = removeNext();
if (hardCache.remove(removedValue)) {
curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
}
@@ -83,14 +85,14 @@ public boolean put(K key, V value) {
}
@Override
- public void remove(K key) {
- V value = super.get(key);
+ public Bitmap remove(String key) {
+ Bitmap value = super.get(key);
if (value != null) {
if (hardCache.remove(value)) {
cacheSize.addAndGet(-getSize(value));
}
}
- super.remove(key);
+ return super.remove(key);
}
@Override
@@ -104,7 +106,7 @@ protected int getSizeLimit() {
return sizeLimit;
}
- protected abstract int getSize(V value);
+ protected abstract int getSize(Bitmap value);
- protected abstract V removeNext();
+ protected abstract Bitmap removeNext();
}
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/MemoryCacheAware.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/MemoryCache.java
similarity index 83%
rename from library/src/com/nostra13/universalimageloader/cache/memory/MemoryCacheAware.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/MemoryCache.java
index 97fb0f29c..6a6dd582e 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/MemoryCacheAware.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/MemoryCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,31 +15,33 @@
*******************************************************************************/
package com.nostra13.universalimageloader.cache.memory;
+import android.graphics.Bitmap;
+
import java.util.Collection;
/**
* Interface for memory cache
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @since 1.0.0
+ * @since 1.9.2
*/
-public interface MemoryCacheAware {
+public interface MemoryCache {
/**
* Puts value into cache by key
*
* @return true - if value was put into cache successfully, false - if value was not put into
- * cache
+ * cache
*/
- boolean put(K key, V value);
+ boolean put(String key, Bitmap value);
/** Returns value by key. If there is no value for key then null will be returned. */
- V get(K key);
+ Bitmap get(String key);
/** Removes item by key */
- void remove(K key);
+ Bitmap remove(String key);
/** Returns all keys of cache */
- Collection keys();
+ Collection keys();
/** Remove all items from cache */
void clear();
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/impl/FIFOLimitedMemoryCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/FIFOLimitedMemoryCache.java
similarity index 93%
rename from library/src/com/nostra13/universalimageloader/cache/memory/impl/FIFOLimitedMemoryCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/FIFOLimitedMemoryCache.java
index 18cec55fb..e56b1a394 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/impl/FIFOLimitedMemoryCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/FIFOLimitedMemoryCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
-public class FIFOLimitedMemoryCache extends LimitedMemoryCache {
+public class FIFOLimitedMemoryCache extends LimitedMemoryCache {
private final List queue = Collections.synchronizedList(new LinkedList());
@@ -53,12 +53,12 @@ public boolean put(String key, Bitmap value) {
}
@Override
- public void remove(String key) {
+ public Bitmap remove(String key) {
Bitmap value = super.get(key);
if (value != null) {
queue.remove(value);
}
- super.remove(key);
+ return super.remove(key);
}
@Override
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/impl/FuzzyKeyMemoryCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/FuzzyKeyMemoryCache.java
similarity index 72%
rename from library/src/com/nostra13/universalimageloader/cache/memory/impl/FuzzyKeyMemoryCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/FuzzyKeyMemoryCache.java
index 9c4709ba6..982c8128a 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/impl/FuzzyKeyMemoryCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/FuzzyKeyMemoryCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,13 +15,15 @@
*******************************************************************************/
package com.nostra13.universalimageloader.cache.memory.impl;
-import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware;
+import android.graphics.Bitmap;
+
+import com.nostra13.universalimageloader.cache.memory.MemoryCache;
import java.util.Collection;
import java.util.Comparator;
/**
- * Decorator for {@link MemoryCacheAware}. Provides special feature for cache: some different keys are considered as
+ * Decorator for {@link MemoryCache}. Provides special feature for cache: some different keys are considered as
* equals (using {@link Comparator comparator}). And when you try to put some value into cache by key so entries with
* "equals" keys will be removed from cache before.
* NOTE: Used for internal needs. Normally you don't need to use this class.
@@ -29,22 +31,22 @@
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
-public class FuzzyKeyMemoryCache implements MemoryCacheAware {
+public class FuzzyKeyMemoryCache implements MemoryCache {
- private final MemoryCacheAware cache;
- private final Comparator keyComparator;
+ private final MemoryCache cache;
+ private final Comparator keyComparator;
- public FuzzyKeyMemoryCache(MemoryCacheAware cache, Comparator keyComparator) {
+ public FuzzyKeyMemoryCache(MemoryCache cache, Comparator keyComparator) {
this.cache = cache;
this.keyComparator = keyComparator;
}
@Override
- public boolean put(K key, V value) {
+ public boolean put(String key, Bitmap value) {
// Search equal key and remove this entry
synchronized (cache) {
- K keyToRemove = null;
- for (K cacheKey : cache.keys()) {
+ String keyToRemove = null;
+ for (String cacheKey : cache.keys()) {
if (keyComparator.compare(key, cacheKey) == 0) {
keyToRemove = cacheKey;
break;
@@ -58,13 +60,13 @@ public boolean put(K key, V value) {
}
@Override
- public V get(K key) {
+ public Bitmap get(String key) {
return cache.get(key);
}
@Override
- public void remove(K key) {
- cache.remove(key);
+ public Bitmap remove(String key) {
+ return cache.remove(key);
}
@Override
@@ -73,7 +75,7 @@ public void clear() {
}
@Override
- public Collection keys() {
+ public Collection keys() {
return cache.keys();
}
}
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/impl/LRULimitedMemoryCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LRULimitedMemoryCache.java
similarity index 94%
rename from library/src/com/nostra13/universalimageloader/cache/memory/impl/LRULimitedMemoryCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LRULimitedMemoryCache.java
index a3b4fd1f3..2d25cde2f 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/impl/LRULimitedMemoryCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LRULimitedMemoryCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.3.0
*/
-public class LRULimitedMemoryCache extends LimitedMemoryCache {
+public class LRULimitedMemoryCache extends LimitedMemoryCache {
private static final int INITIAL_CAPACITY = 10;
private static final float LOAD_FACTOR = 1.1f;
@@ -66,9 +66,9 @@ public Bitmap get(String key) {
}
@Override
- public void remove(String key) {
+ public Bitmap remove(String key) {
lruCache.remove(key);
- super.remove(key);
+ return super.remove(key);
}
@Override
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/impl/LargestLimitedMemoryCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LargestLimitedMemoryCache.java
similarity index 88%
rename from library/src/com/nostra13/universalimageloader/cache/memory/impl/LargestLimitedMemoryCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LargestLimitedMemoryCache.java
index bcec783d5..d7a8d19e2 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/impl/LargestLimitedMemoryCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LargestLimitedMemoryCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
/**
* Limited {@link Bitmap bitmap} cache. Provides {@link Bitmap bitmaps} storing. Size of all stored bitmaps will not to
- * exceed size limit. When cache reaches limit size then the bitmap which used the least frequently is deleted from
+ * exceed size limit. When cache reaches limit size then the bitmap which has the largest size is deleted from
* cache.
*
* NOTE: This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of
@@ -37,10 +37,10 @@
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
-public class LargestLimitedMemoryCache extends LimitedMemoryCache {
+public class LargestLimitedMemoryCache extends LimitedMemoryCache {
/**
- * Contains strong references to stored objects (keys) and last object usage date (in milliseconds). If hard cache
- * size will exceed limit then object with the least frequently usage is deleted (but it continue exist at
+ * Contains strong references to stored objects (keys) and sizes of the objects. If hard cache
+ * size will exceed limit then object with the largest size is deleted (but it continue exist at
* {@link #softMap} and can be collected by GC at any time)
*/
private final Map valueSizes = Collections.synchronizedMap(new HashMap());
@@ -60,12 +60,12 @@ public boolean put(String key, Bitmap value) {
}
@Override
- public void remove(String key) {
+ public Bitmap remove(String key) {
Bitmap value = super.get(key);
if (value != null) {
valueSizes.remove(value);
}
- super.remove(key);
+ return super.remove(key);
}
@Override
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/impl/LimitedAgeMemoryCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LimitedAgeMemoryCache.java
similarity index 75%
rename from library/src/com/nostra13/universalimageloader/cache/memory/impl/LimitedAgeMemoryCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LimitedAgeMemoryCache.java
index 3a7ef66fe..2925b6315 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/impl/LimitedAgeMemoryCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LimitedAgeMemoryCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,9 @@
*******************************************************************************/
package com.nostra13.universalimageloader.cache.memory.impl;
-import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware;
+import android.graphics.Bitmap;
+
+import com.nostra13.universalimageloader.cache.memory.MemoryCache;
import java.util.Collection;
import java.util.Collections;
@@ -23,32 +25,32 @@
import java.util.Map;
/**
- * Decorator for {@link MemoryCacheAware}. Provides special feature for cache: if some cached object age exceeds defined
+ * Decorator for {@link MemoryCache}. Provides special feature for cache: if some cached object age exceeds defined
* value then this object will be removed from cache.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @see MemoryCacheAware
+ * @see MemoryCache
* @since 1.3.1
*/
-public class LimitedAgeMemoryCache implements MemoryCacheAware {
+public class LimitedAgeMemoryCache implements MemoryCache {
- private final MemoryCacheAware cache;
+ private final MemoryCache cache;
private final long maxAge;
- private final Map loadingDates = Collections.synchronizedMap(new HashMap());
+ private final Map loadingDates = Collections.synchronizedMap(new HashMap());
/**
* @param cache Wrapped memory cache
* @param maxAge Max object age (in seconds). If object age will exceed this value then it'll be removed from
* cache on next treatment (and therefore be reloaded).
*/
- public LimitedAgeMemoryCache(MemoryCacheAware cache, long maxAge) {
+ public LimitedAgeMemoryCache(MemoryCache cache, long maxAge) {
this.cache = cache;
this.maxAge = maxAge * 1000; // to milliseconds
}
@Override
- public boolean put(K key, V value) {
+ public boolean put(String key, Bitmap value) {
boolean putSuccesfully = cache.put(key, value);
if (putSuccesfully) {
loadingDates.put(key, System.currentTimeMillis());
@@ -57,7 +59,7 @@ public boolean put(K key, V value) {
}
@Override
- public V get(K key) {
+ public Bitmap get(String key) {
Long loadingDate = loadingDates.get(key);
if (loadingDate != null && System.currentTimeMillis() - loadingDate > maxAge) {
cache.remove(key);
@@ -68,13 +70,13 @@ public V get(K key) {
}
@Override
- public void remove(K key) {
- cache.remove(key);
+ public Bitmap remove(String key) {
loadingDates.remove(key);
+ return cache.remove(key);
}
@Override
- public Collection keys() {
+ public Collection keys() {
return cache.keys();
}
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/impl/LruMemoryCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LruMemoryCache.java
similarity index 96%
rename from library/src/com/nostra13/universalimageloader/cache/memory/impl/LruMemoryCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LruMemoryCache.java
index 48e9a5212..134a4f838 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/impl/LruMemoryCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LruMemoryCache.java
@@ -1,7 +1,8 @@
package com.nostra13.universalimageloader.cache.memory.impl;
import android.graphics.Bitmap;
-import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware;
+
+import com.nostra13.universalimageloader.cache.memory.MemoryCache;
import java.util.Collection;
import java.util.HashSet;
@@ -18,7 +19,7 @@
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.8.1
*/
-public class LruMemoryCache implements MemoryCacheAware {
+public class LruMemoryCache implements MemoryCache {
private final LinkedHashMap map;
@@ -101,7 +102,7 @@ private void trimToSize(int maxSize) {
/** Removes the entry for {@code key} if it exists. */
@Override
- public final void remove(String key) {
+ public final Bitmap remove(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
@@ -111,6 +112,7 @@ public final void remove(String key) {
if (previous != null) {
size -= sizeOf(key, previous);
}
+ return previous;
}
}
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/impl/UsingFreqLimitedMemoryCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/UsingFreqLimitedMemoryCache.java
similarity index 96%
rename from library/src/com/nostra13/universalimageloader/cache/memory/impl/UsingFreqLimitedMemoryCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/UsingFreqLimitedMemoryCache.java
index ce28a9f23..1c78dc1bc 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/impl/UsingFreqLimitedMemoryCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/UsingFreqLimitedMemoryCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
-public class UsingFreqLimitedMemoryCache extends LimitedMemoryCache {
+public class UsingFreqLimitedMemoryCache extends LimitedMemoryCache {
/**
* Contains strong references to stored objects (keys) and last object usage date (in milliseconds). If hard cache
* size will exceed limit then object with the least frequently usage is deleted (but it continue exist at
@@ -73,12 +73,12 @@ public Bitmap get(String key) {
}
@Override
- public void remove(String key) {
+ public Bitmap remove(String key) {
Bitmap value = super.get(key);
if (value != null) {
usingCounts.remove(value);
}
- super.remove(key);
+ return super.remove(key);
}
@Override
diff --git a/library/src/com/nostra13/universalimageloader/cache/memory/impl/WeakMemoryCache.java b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/WeakMemoryCache.java
similarity index 92%
rename from library/src/com/nostra13/universalimageloader/cache/memory/impl/WeakMemoryCache.java
rename to library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/WeakMemoryCache.java
index e9500579e..456257476 100644
--- a/library/src/com/nostra13/universalimageloader/cache/memory/impl/WeakMemoryCache.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/WeakMemoryCache.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.5.3
*/
-public class WeakMemoryCache extends BaseMemoryCache {
+public class WeakMemoryCache extends BaseMemoryCache {
@Override
protected Reference createReference(Bitmap value) {
return new WeakReference(value);
diff --git a/library/src/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java b/library/src/main/java/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java
similarity index 55%
rename from library/src/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java
index b27422b9e..af244d651 100644
--- a/library/src/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,17 +15,17 @@
*******************************************************************************/
package com.nostra13.universalimageloader.core;
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
import android.content.Context;
-import android.graphics.Bitmap;
+import android.content.pm.ApplicationInfo;
import android.os.Build;
-import com.nostra13.universalimageloader.cache.disc.DiscCacheAware;
-import com.nostra13.universalimageloader.cache.disc.impl.FileCountLimitedDiscCache;
-import com.nostra13.universalimageloader.cache.disc.impl.TotalSizeLimitedDiscCache;
-import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
+import com.nostra13.universalimageloader.cache.disc.DiskCache;
+import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
+import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.cache.disc.naming.HashCodeFileNameGenerator;
-import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware;
-import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
+import com.nostra13.universalimageloader.cache.memory.MemoryCache;
import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.core.assist.deque.LIFOLinkedBlockingDeque;
@@ -35,10 +35,18 @@
import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
import com.nostra13.universalimageloader.core.download.ImageDownloader;
+import com.nostra13.universalimageloader.utils.L;
import com.nostra13.universalimageloader.utils.StorageUtils;
import java.io.File;
-import java.util.concurrent.*;
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+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;
/**
@@ -50,10 +58,18 @@
public class DefaultConfigurationFactory {
/** Creates default implementation of task executor */
- public static Executor createExecutor(int threadPoolSize, int threadPriority, QueueProcessingType tasksProcessingType) {
+ 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));
+ BlockingQueue taskQueue =
+ lifo ? new LIFOLinkedBlockingDeque() : new LinkedBlockingQueue();
+ return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue,
+ createThreadFactory(threadPriority, "uil-pool-"));
+ }
+
+ /** Creates default implementation of task distributor */
+ public static Executor createTaskDistributor() {
+ return Executors.newCachedThreadPool(createThreadFactory(Thread.NORM_PRIORITY, "uil-pool-d-"));
}
/** Creates {@linkplain HashCodeFileNameGenerator default implementation} of FileNameGenerator */
@@ -61,46 +77,64 @@ public static FileNameGenerator createFileNameGenerator() {
return new HashCodeFileNameGenerator();
}
- /** Creates default implementation of {@link DiscCacheAware} depends on incoming parameters */
- public static DiscCacheAware createDiscCache(Context context, FileNameGenerator discCacheFileNameGenerator, int discCacheSize, int discCacheFileCount) {
- if (discCacheSize > 0) {
- File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context);
- return new TotalSizeLimitedDiscCache(individualCacheDir, discCacheFileNameGenerator, discCacheSize);
- } else if (discCacheFileCount > 0) {
+ /**
+ * Creates default implementation of {@link DiskCache} depends on incoming parameters
+ */
+ public static DiskCache createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator,
+ long diskCacheSize, int diskCacheFileCount) {
+ File reserveCacheDir = createReserveDiskCacheDir(context);
+ if (diskCacheSize > 0 || diskCacheFileCount > 0) {
File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context);
- return new FileCountLimitedDiscCache(individualCacheDir, discCacheFileNameGenerator, discCacheFileCount);
- } else {
- File cacheDir = StorageUtils.getCacheDirectory(context);
- return new UnlimitedDiscCache(cacheDir, discCacheFileNameGenerator);
+ try {
+ return new LruDiskCache(individualCacheDir, reserveCacheDir, diskCacheFileNameGenerator, diskCacheSize,
+ diskCacheFileCount);
+ } catch (IOException e) {
+ L.e(e);
+ // continue and create unlimited cache
+ }
}
+ File cacheDir = StorageUtils.getCacheDirectory(context);
+ return new UnlimitedDiskCache(cacheDir, reserveCacheDir, diskCacheFileNameGenerator);
}
- /** Creates reserve disc cache which will be used if primary disc cache becomes unavailable */
- public static DiscCacheAware createReserveDiscCache(Context context) {
- File cacheDir = context.getCacheDir();
+ /** Creates reserve disk cache folder which will be used if primary disk cache folder becomes unavailable */
+ private static File createReserveDiskCacheDir(Context context) {
+ File cacheDir = StorageUtils.getCacheDirectory(context, false);
File individualDir = new File(cacheDir, "uil-images");
if (individualDir.exists() || individualDir.mkdir()) {
cacheDir = individualDir;
}
- return new TotalSizeLimitedDiscCache(cacheDir, 2 * 1024 * 1024); // limit - 2 Mb
+ return cacheDir;
}
/**
- * Creates default implementation of {@link MemoryCacheAware} depends on incoming parameters:
- * {@link LruMemoryCache} (for API >= 9) or {@link LRULimitedMemoryCache} (for API < 9).
+ * Creates default implementation of {@link MemoryCache} - {@link LruMemoryCache}
* Default cache size = 1/8 of available app memory.
*/
- public static MemoryCacheAware createMemoryCache(int memoryCacheSize) {
+ public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
if (memoryCacheSize == 0) {
- memoryCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8);
- }
- MemoryCacheAware memoryCache;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
- memoryCache = new LruMemoryCache(memoryCacheSize);
- } else {
- memoryCache = new LRULimitedMemoryCache(memoryCacheSize);
+ ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ int memoryClass = am.getMemoryClass();
+ if (hasHoneycomb() && isLargeHeap(context)) {
+ memoryClass = getLargeMemoryClass(am);
+ }
+ memoryCacheSize = 1024 * 1024 * memoryClass / 8;
}
- return memoryCache;
+ return new LruMemoryCache(memoryCacheSize);
+ }
+
+ private static boolean hasHoneycomb() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private static boolean isLargeHeap(Context context) {
+ return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_LARGE_HEAP) != 0;
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private static int getLargeMemoryClass(ActivityManager am) {
+ return am.getLargeMemoryClass();
}
/** Creates default implementation of {@link ImageDownloader} - {@link BaseImageDownloader} */
@@ -119,8 +153,8 @@ public static BitmapDisplayer createBitmapDisplayer() {
}
/** Creates default implementation of {@linkplain ThreadFactory thread factory} for task executor */
- private static ThreadFactory createThreadFactory(int threadPriority) {
- return new DefaultThreadFactory(threadPriority);
+ private static ThreadFactory createThreadFactory(int threadPriority, String threadNamePrefix) {
+ return new DefaultThreadFactory(threadPriority, threadNamePrefix);
}
private static class DefaultThreadFactory implements ThreadFactory {
@@ -132,13 +166,13 @@ private static class DefaultThreadFactory implements ThreadFactory {
private final String namePrefix;
private final int threadPriority;
- DefaultThreadFactory(int threadPriority) {
+ DefaultThreadFactory(int threadPriority, String threadNamePrefix) {
this.threadPriority = threadPriority;
- SecurityManager s = System.getSecurityManager();
- group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
- namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
+ group = Thread.currentThread().getThreadGroup();
+ namePrefix = threadNamePrefix + poolNumber.getAndIncrement() + "-thread-";
}
+ @Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon()) t.setDaemon(false);
diff --git a/library/src/com/nostra13/universalimageloader/core/DisplayBitmapTask.java b/library/src/main/java/com/nostra13/universalimageloader/core/DisplayBitmapTask.java
similarity index 56%
rename from library/src/com/nostra13/universalimageloader/core/DisplayBitmapTask.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/DisplayBitmapTask.java
index 18eb76b96..fbdf762ff 100644
--- a/library/src/com/nostra13/universalimageloader/core/DisplayBitmapTask.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/DisplayBitmapTask.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,14 @@
package com.nostra13.universalimageloader.core;
import android.graphics.Bitmap;
-import android.widget.ImageView;
-import com.nostra13.universalimageloader.core.assist.ImageLoadingListener;
import com.nostra13.universalimageloader.core.assist.LoadedFrom;
import com.nostra13.universalimageloader.core.display.BitmapDisplayer;
+import com.nostra13.universalimageloader.core.imageaware.ImageAware;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.utils.L;
/**
- * Displays bitmap in {@link ImageView}. Must be called on UI thread.
+ * Displays bitmap in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}. Must be called on UI thread.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @see ImageLoadingListener
@@ -32,24 +32,24 @@
*/
final class DisplayBitmapTask implements Runnable {
- private static final String LOG_DISPLAY_IMAGE_IN_IMAGEVIEW = "Display image in ImageView (loaded from %1$s) [%2$s]";
- private static final String LOG_TASK_CANCELLED = "ImageView is reused for another image. Task is cancelled. [%s]";
+ private static final String LOG_DISPLAY_IMAGE_IN_IMAGEAWARE = "Display image in ImageAware (loaded from %1$s) [%2$s]";
+ private static final String LOG_TASK_CANCELLED_IMAGEAWARE_REUSED = "ImageAware is reused for another image. Task is cancelled. [%s]";
+ private static final String LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED = "ImageAware was collected by GC. Task is cancelled. [%s]";
private final Bitmap bitmap;
private final String imageUri;
- private final ImageView imageView;
+ private final ImageAware imageAware;
private final String memoryCacheKey;
private final BitmapDisplayer displayer;
private final ImageLoadingListener listener;
private final ImageLoaderEngine engine;
private final LoadedFrom loadedFrom;
- private boolean loggingEnabled;
-
- public DisplayBitmapTask(Bitmap bitmap, ImageLoadingInfo imageLoadingInfo, ImageLoaderEngine engine, LoadedFrom loadedFrom) {
+ public DisplayBitmapTask(Bitmap bitmap, ImageLoadingInfo imageLoadingInfo, ImageLoaderEngine engine,
+ LoadedFrom loadedFrom) {
this.bitmap = bitmap;
imageUri = imageLoadingInfo.uri;
- imageView = imageLoadingInfo.imageView;
+ imageAware = imageLoadingInfo.imageAware;
memoryCacheKey = imageLoadingInfo.memoryCacheKey;
displayer = imageLoadingInfo.options.getDisplayer();
listener = imageLoadingInfo.listener;
@@ -57,25 +57,25 @@ public DisplayBitmapTask(Bitmap bitmap, ImageLoadingInfo imageLoadingInfo, Image
this.loadedFrom = loadedFrom;
}
+ @Override
public void run() {
- if (isViewWasReused()) {
- if (loggingEnabled) L.d(LOG_TASK_CANCELLED, memoryCacheKey);
- listener.onLoadingCancelled(imageUri, imageView);
+ if (imageAware.isCollected()) {
+ L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
+ listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
+ } else if (isViewWasReused()) {
+ L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
+ listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else {
- if (loggingEnabled) L.d(LOG_DISPLAY_IMAGE_IN_IMAGEVIEW, loadedFrom, memoryCacheKey);
- Bitmap displayedBitmap = displayer.display(bitmap, imageView, loadedFrom);
- listener.onLoadingComplete(imageUri, imageView, displayedBitmap);
- engine.cancelDisplayTaskFor(imageView);
+ L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
+ displayer.display(bitmap, imageAware, loadedFrom);
+ engine.cancelDisplayTaskFor(imageAware);
+ listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
}
}
- /** Checks whether memory cache key (image URI) for current ImageView is actual */
+ /** Checks whether memory cache key (image URI) for current ImageAware is actual */
private boolean isViewWasReused() {
- String currentCacheKey = engine.getLoadingUriForView(imageView);
+ String currentCacheKey = engine.getLoadingUriForView(imageAware);
return !memoryCacheKey.equals(currentCacheKey);
}
-
- void setLoggingEnabled(boolean loggingEnabled) {
- this.loggingEnabled = loggingEnabled;
- }
}
diff --git a/library/src/com/nostra13/universalimageloader/core/DisplayImageOptions.java b/library/src/main/java/com/nostra13/universalimageloader/core/DisplayImageOptions.java
similarity index 59%
rename from library/src/com/nostra13/universalimageloader/core/DisplayImageOptions.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/DisplayImageOptions.java
index 75aab6ad3..3e19b7de5 100644
--- a/library/src/com/nostra13/universalimageloader/core/DisplayImageOptions.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/DisplayImageOptions.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,11 +15,12 @@
*******************************************************************************/
package com.nostra13.universalimageloader.core;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory.Options;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.widget.ImageView;
-import com.nostra13.universalimageloader.core.assist.ImageLoadingListener;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.BitmapDisplayer;
import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;
@@ -29,15 +30,20 @@
/**
* Contains options for image display. Defines:
*
- * - whether stub image will be displayed in {@link android.widget.ImageView ImageView} during image loading
- * - whether stub image will be displayed in {@link android.widget.ImageView ImageView} if empty URI is passed
- * - whether stub image will be displayed in {@link android.widget.ImageView ImageView} if image loading fails
- * - whether {@link android.widget.ImageView ImageView} should be reset before image loading start
+ * - whether stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} during image loading
+ * - whether stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} if empty URI is passed
+ * - whether stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} if image loading fails
+ * - whether {@link com.nostra13.universalimageloader.core.imageaware.ImageAware image aware view} should be reset
+ * before image loading start
* - whether loaded image will be cached in memory
- * - whether loaded image will be cached on disc
+ * - whether loaded image will be cached on disk
* - image scale type
* - decoding options (including bitmap decoding configuration)
* - delay before loading of image
+ * - whether consider EXIF parameters of image
* - auxiliary object which will be passed to {@link ImageDownloader#getStream(String, Object) ImageDownloader}
* - pre-processor for image Bitmap (before caching in memory)
* - post-processor for image Bitmap (after caching in memory, before displaying)
@@ -48,8 +54,8 @@
*
* - with {@link Builder}:
* i.e. :
- * new {@link DisplayImageOptions}.{@link Builder#Builder() Builder()}.{@link Builder#cacheInMemory() cacheInMemory()}.
- * {@link Builder#showStubImage(int) showStubImage()}.{@link Builder#build() build()}
+ * new {@link DisplayImageOptions}.Builder().{@link Builder#cacheInMemory() cacheInMemory()}.
+ * {@link Builder#showImageOnLoading(int) showImageOnLoading()}.{@link Builder#build() build()}
*
* - or by static method: {@link #createSimple()}
*
@@ -58,48 +64,58 @@
*/
public final class DisplayImageOptions {
- private final int stubImage;
- private final int imageForEmptyUri;
- private final int imageOnFail;
+ private final int imageResOnLoading;
+ private final int imageResForEmptyUri;
+ private final int imageResOnFail;
+ private final Drawable imageOnLoading;
+ private final Drawable imageForEmptyUri;
+ private final Drawable imageOnFail;
private final boolean resetViewBeforeLoading;
private final boolean cacheInMemory;
- private final boolean cacheOnDisc;
+ private final boolean cacheOnDisk;
private final ImageScaleType imageScaleType;
private final Options decodingOptions;
private final int delayBeforeLoading;
+ private final boolean considerExifParams;
private final Object extraForDownloader;
private final BitmapProcessor preProcessor;
private final BitmapProcessor postProcessor;
private final BitmapDisplayer displayer;
private final Handler handler;
+ private final boolean isSyncLoading;
private DisplayImageOptions(Builder builder) {
- stubImage = builder.stubImage;
+ imageResOnLoading = builder.imageResOnLoading;
+ imageResForEmptyUri = builder.imageResForEmptyUri;
+ imageResOnFail = builder.imageResOnFail;
+ imageOnLoading = builder.imageOnLoading;
imageForEmptyUri = builder.imageForEmptyUri;
imageOnFail = builder.imageOnFail;
resetViewBeforeLoading = builder.resetViewBeforeLoading;
cacheInMemory = builder.cacheInMemory;
- cacheOnDisc = builder.cacheOnDisc;
+ cacheOnDisk = builder.cacheOnDisk;
imageScaleType = builder.imageScaleType;
decodingOptions = builder.decodingOptions;
delayBeforeLoading = builder.delayBeforeLoading;
+ considerExifParams = builder.considerExifParams;
extraForDownloader = builder.extraForDownloader;
preProcessor = builder.preProcessor;
postProcessor = builder.postProcessor;
displayer = builder.displayer;
handler = builder.handler;
+ isSyncLoading = builder.isSyncLoading;
}
- public boolean shouldShowStubImage() {
- return stubImage != 0;
+ public boolean shouldShowImageOnLoading() {
+ return imageOnLoading != null || imageResOnLoading != 0;
}
public boolean shouldShowImageForEmptyUri() {
- return imageForEmptyUri != 0;
+ return imageForEmptyUri != null || imageResForEmptyUri != 0;
}
public boolean shouldShowImageOnFail() {
- return imageOnFail != 0;
+ return imageOnFail != null || imageResOnFail != 0;
}
public boolean shouldPreProcess() {
@@ -114,16 +130,16 @@ public boolean shouldDelayBeforeLoading() {
return delayBeforeLoading > 0;
}
- public int getStubImage() {
- return stubImage;
+ public Drawable getImageOnLoading(Resources res) {
+ return imageResOnLoading != 0 ? res.getDrawable(imageResOnLoading) : imageOnLoading;
}
- public int getImageForEmptyUri() {
- return imageForEmptyUri;
+ public Drawable getImageForEmptyUri(Resources res) {
+ return imageResForEmptyUri != 0 ? res.getDrawable(imageResForEmptyUri) : imageForEmptyUri;
}
- public int getImageOnFail() {
- return imageOnFail;
+ public Drawable getImageOnFail(Resources res) {
+ return imageResOnFail != 0 ? res.getDrawable(imageResOnFail) : imageOnFail;
}
public boolean isResetViewBeforeLoading() {
@@ -134,8 +150,8 @@ public boolean isCacheInMemory() {
return cacheInMemory;
}
- public boolean isCacheOnDisc() {
- return cacheOnDisc;
+ public boolean isCacheOnDisk() {
+ return cacheOnDisk;
}
public ImageScaleType getImageScaleType() {
@@ -150,6 +166,10 @@ public int getDelayBeforeLoading() {
return delayBeforeLoading;
}
+ public boolean isConsiderExifParams() {
+ return considerExifParams;
+ }
+
public Object getExtraForDownloader() {
return extraForDownloader;
}
@@ -167,7 +187,11 @@ public BitmapDisplayer getDisplayer() {
}
public Handler getHandler() {
- return (handler == null ? new Handler() : handler);
+ return handler;
+ }
+
+ boolean isSyncLoading() {
+ return isSyncLoading;
}
/**
@@ -176,60 +200,109 @@ public Handler getHandler() {
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
*/
public static class Builder {
- private int stubImage = 0;
- private int imageForEmptyUri = 0;
- private int imageOnFail = 0;
+ private int imageResOnLoading = 0;
+ private int imageResForEmptyUri = 0;
+ private int imageResOnFail = 0;
+ private Drawable imageOnLoading = null;
+ private Drawable imageForEmptyUri = null;
+ private Drawable imageOnFail = null;
private boolean resetViewBeforeLoading = false;
private boolean cacheInMemory = false;
- private boolean cacheOnDisc = false;
+ private boolean cacheOnDisk = false;
private ImageScaleType imageScaleType = ImageScaleType.IN_SAMPLE_POWER_OF_2;
private Options decodingOptions = new Options();
private int delayBeforeLoading = 0;
+ private boolean considerExifParams = false;
private Object extraForDownloader = null;
private BitmapProcessor preProcessor = null;
private BitmapProcessor postProcessor = null;
private BitmapDisplayer displayer = DefaultConfigurationFactory.createBitmapDisplayer();
private Handler handler = null;
+ private boolean isSyncLoading = false;
- public Builder() {
- decodingOptions.inPurgeable = true;
- decodingOptions.inInputShareable = true;
+ /**
+ * Stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} during image loading
+ *
+ * @param imageRes Stub image resource
+ * @deprecated Use {@link #showImageOnLoading(int)} instead
+ */
+ @Deprecated
+ public Builder showStubImage(int imageRes) {
+ imageResOnLoading = imageRes;
+ return this;
}
/**
- * Stub image will be displayed in {@link android.widget.ImageView ImageView} during image loading
+ * Incoming image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} during image loading
*
- * @param stubImageRes Stub image resource
+ * @param imageRes Image resource
+ */
+ public Builder showImageOnLoading(int imageRes) {
+ imageResOnLoading = imageRes;
+ return this;
+ }
+
+ /**
+ * Incoming drawable will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} during image loading.
+ * This option will be ignored if {@link DisplayImageOptions.Builder#showImageOnLoading(int)} is set.
*/
- public Builder showStubImage(int stubImageRes) {
- stubImage = stubImageRes;
+ public Builder showImageOnLoading(Drawable drawable) {
+ imageOnLoading = drawable;
return this;
}
/**
- * Incoming image will be displayed in {@link android.widget.ImageView ImageView} if empty URI (null or empty
+ * Incoming image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} if empty URI (null or empty
* string) will be passed to ImageLoader.displayImage(...) method.
*
* @param imageRes Image resource
*/
public Builder showImageForEmptyUri(int imageRes) {
- imageForEmptyUri = imageRes;
+ imageResForEmptyUri = imageRes;
+ return this;
+ }
+
+ /**
+ * Incoming drawable will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} if empty URI (null or empty
+ * string) will be passed to ImageLoader.displayImage(...) method.
+ * This option will be ignored if {@link DisplayImageOptions.Builder#showImageForEmptyUri(int)} is set.
+ */
+ public Builder showImageForEmptyUri(Drawable drawable) {
+ imageForEmptyUri = drawable;
return this;
}
/**
- * Incoming image will be displayed in {@link android.widget.ImageView ImageView} if some error occurs during
+ * Incoming image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} if some error occurs during
* requested image loading/decoding.
*
* @param imageRes Image resource
*/
public Builder showImageOnFail(int imageRes) {
- imageOnFail = imageRes;
+ imageResOnFail = imageRes;
return this;
}
/**
- * {@link android.widget.ImageView ImageView} will be reset (set null) before image loading start
+ * Incoming drawable will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} if some error occurs during
+ * requested image loading/decoding.
+ * This option will be ignored if {@link DisplayImageOptions.Builder#showImageOnFail(int)} is set.
+ */
+ public Builder showImageOnFail(Drawable drawable) {
+ imageOnFail = drawable;
+ return this;
+ }
+
+ /**
+ * {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} will be reset (set null) before image loading start
*
* @deprecated Use {@link #resetViewBeforeLoading(boolean) resetViewBeforeLoading(true)} instead
*/
@@ -238,7 +311,10 @@ public Builder resetViewBeforeLoading() {
return this;
}
- /** Sets whether {@link android.widget.ImageView ImageView} will be reset (set null) before image loading start */
+ /**
+ * Sets whether {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * image aware view} will be reset (set null) before image loading start
+ */
public Builder resetViewBeforeLoading(boolean resetViewBeforeLoading) {
this.resetViewBeforeLoading = resetViewBeforeLoading;
return this;
@@ -249,6 +325,7 @@ public Builder resetViewBeforeLoading(boolean resetViewBeforeLoading) {
*
* @deprecated Use {@link #cacheInMemory(boolean) cacheInMemory(true)} instead
*/
+ @Deprecated
public Builder cacheInMemory() {
cacheInMemory = true;
return this;
@@ -261,18 +338,28 @@ public Builder cacheInMemory(boolean cacheInMemory) {
}
/**
- * Loaded image will be cached on disc
+ * Loaded image will be cached on disk
*
- * @deprecated Use {@link #cacheOnDisc(boolean) cacheOnDisc(true)} instead
+ * @deprecated Use {@link #cacheOnDisk(boolean) cacheOnDisk(true)} instead
*/
+ @Deprecated
public Builder cacheOnDisc() {
- cacheOnDisc = true;
- return this;
+ return cacheOnDisk(true);
+ }
+
+ /**
+ * Sets whether loaded image will be cached on disk
+ *
+ * @deprecated Use {@link #cacheOnDisk(boolean)} instead
+ */
+ @Deprecated
+ public Builder cacheOnDisc(boolean cacheOnDisk) {
+ return cacheOnDisk(cacheOnDisk);
}
- /** Sets whether loaded image will be cached on disc */
- public Builder cacheOnDisc(boolean cacheOnDisc) {
- this.cacheOnDisc = cacheOnDisc;
+ /** Sets whether loaded image will be cached on disk */
+ public Builder cacheOnDisk(boolean cacheOnDisk) {
+ this.cacheOnDisk = cacheOnDisk;
return this;
}
@@ -318,6 +405,12 @@ public Builder extraForDownloader(Object extra) {
return this;
}
+ /** Sets whether ImageLoader will consider EXIF parameters of JPEG image (rotate, flip) */
+ public Builder considerExifParams(boolean considerExifParams) {
+ this.considerExifParams = considerExifParams;
+ return this;
+ }
+
/**
* Sets bitmap processor which will be process bitmaps before they will be cached in memory. So memory cache
* will contain bitmap processed by incoming preProcessor.
@@ -329,7 +422,8 @@ public Builder preProcessor(BitmapProcessor preProcessor) {
}
/**
- * Sets bitmap processor which will be process bitmaps before they will be displayed in {@link ImageView} but
+ * Sets bitmap processor which will be process bitmaps before they will be displayed in
+ * {@link com.nostra13.universalimageloader.core.imageaware.ImageAware image aware view} but
* after they'll have been saved in memory cache.
*/
public Builder postProcessor(BitmapProcessor postProcessor) {
@@ -347,6 +441,11 @@ public Builder displayer(BitmapDisplayer displayer) {
return this;
}
+ Builder syncLoading(boolean isSyncLoading) {
+ this.isSyncLoading = isSyncLoading;
+ return this;
+ }
+
/**
* Sets custom {@linkplain Handler handler} for displaying images and firing {@linkplain ImageLoadingListener
* listener} events.
@@ -358,20 +457,25 @@ public Builder handler(Handler handler) {
/** Sets all options equal to incoming options */
public Builder cloneFrom(DisplayImageOptions options) {
- stubImage = options.stubImage;
+ imageResOnLoading = options.imageResOnLoading;
+ imageResForEmptyUri = options.imageResForEmptyUri;
+ imageResOnFail = options.imageResOnFail;
+ imageOnLoading = options.imageOnLoading;
imageForEmptyUri = options.imageForEmptyUri;
imageOnFail = options.imageOnFail;
resetViewBeforeLoading = options.resetViewBeforeLoading;
cacheInMemory = options.cacheInMemory;
- cacheOnDisc = options.cacheOnDisc;
+ cacheOnDisk = options.cacheOnDisk;
imageScaleType = options.imageScaleType;
decodingOptions = options.decodingOptions;
delayBeforeLoading = options.delayBeforeLoading;
+ considerExifParams = options.considerExifParams;
extraForDownloader = options.extraForDownloader;
preProcessor = options.preProcessor;
postProcessor = options.postProcessor;
displayer = options.displayer;
handler = options.handler;
+ isSyncLoading = options.isSyncLoading;
return this;
}
@@ -386,7 +490,7 @@ public DisplayImageOptions build() {
*
* - View will not be reset before loading
* - Loaded image will not be cached in memory
- * - Loaded image will not be cached on disc
+ * - Loaded image will not be cached on disk
* - {@link ImageScaleType#IN_SAMPLE_POWER_OF_2} decoding type will be used
* - {@link Bitmap.Config#ARGB_8888} bitmap config will be used for image decoding
* - {@link SimpleBitmapDisplayer} will be used for image displaying
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/ImageLoader.java b/library/src/main/java/com/nostra13/universalimageloader/core/ImageLoader.java
new file mode 100644
index 000000000..16a67d61f
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/ImageLoader.java
@@ -0,0 +1,829 @@
+/*******************************************************************************
+ * Copyright 2011-2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core;
+
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.nostra13.universalimageloader.cache.disc.DiskCache;
+import com.nostra13.universalimageloader.cache.memory.MemoryCache;
+import com.nostra13.universalimageloader.core.assist.FailReason;
+import com.nostra13.universalimageloader.core.assist.FlushedInputStream;
+import com.nostra13.universalimageloader.core.assist.ImageSize;
+import com.nostra13.universalimageloader.core.assist.LoadedFrom;
+import com.nostra13.universalimageloader.core.assist.ViewScaleType;
+import com.nostra13.universalimageloader.core.imageaware.ImageAware;
+import com.nostra13.universalimageloader.core.imageaware.ImageViewAware;
+import com.nostra13.universalimageloader.core.imageaware.NonViewAware;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener;
+import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
+import com.nostra13.universalimageloader.utils.ImageSizeUtils;
+import com.nostra13.universalimageloader.utils.L;
+import com.nostra13.universalimageloader.utils.MemoryCacheUtils;
+
+/**
+ * Singletone for image loading and displaying at {@link ImageView ImageViews}
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before any other method.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.0.0
+ */
+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_LOAD_IMAGE_FROM_MEMORY_CACHE = "Load image from memory cache [%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";
+
+ private ImageLoaderConfiguration configuration;
+ private ImageLoaderEngine engine;
+
+ private ImageLoadingListener defaultListener = new SimpleImageLoadingListener();
+
+ private volatile static ImageLoader instance;
+
+ /** Returns singleton class instance */
+ public static ImageLoader getInstance() {
+ if (instance == null) {
+ synchronized (ImageLoader.class) {
+ if (instance == null) {
+ instance = new ImageLoader();
+ }
+ }
+ }
+ return instance;
+ }
+
+ protected ImageLoader() {
+ }
+
+ /**
+ * 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
+ */
+ public synchronized void init(ImageLoaderConfiguration configuration) {
+ if (configuration == null) {
+ throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
+ }
+ if (this.configuration == null) {
+ L.d(LOG_INIT_CONFIG);
+ engine = new ImageLoaderEngine(configuration);
+ this.configuration = configuration;
+ } else {
+ L.w(WARNING_RE_INIT_CONFIG);
+ }
+ }
+
+ /**
+ * Returns true - if ImageLoader {@linkplain #init(ImageLoaderConfiguration) is initialized with
+ * configuration}; false - otherwise
+ */
+ public boolean isInited() {
+ return configuration != null;
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.
+ * Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration
+ * configuration} will be used.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view}
+ * which should display image
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageAware is null
+ */
+ public void displayImage(String uri, ImageAware imageAware) {
+ displayImage(uri, imageAware, null, null, null);
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.
+ * Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration
+ * configuration} will be used.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view}
+ * which should display image
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on
+ * UI thread if this method is called on UI thread.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageAware is null
+ */
+ public void displayImage(String uri, ImageAware imageAware, ImageLoadingListener listener) {
+ displayImage(uri, imageAware, null, listener, null);
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view}
+ * which should display image
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and displaying. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
+ * from configuration} will be used.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageAware is null
+ */
+ public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options) {
+ displayImage(uri, imageAware, options, null, null);
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view}
+ * which should display image
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and displaying. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
+ * from configuration} will be used.
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on
+ * UI thread if this method is called on UI thread.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageAware is null
+ */
+ public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
+ ImageLoadingListener listener) {
+ displayImage(uri, imageAware, options, listener, null);
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view}
+ * which should display image
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and displaying. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
+ * from configuration} will be used.
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires
+ * events on UI thread if this method is called on UI thread.
+ * @param progressListener {@linkplain com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener
+ * Listener} for image loading progress. Listener fires events on UI thread if this method
+ * is called on UI thread. Caching on disk should be enabled in
+ * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions options} to make
+ * this listener work.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageAware is null
+ */
+ public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
+ ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
+ displayImage(uri, imageAware, options, null, listener, progressListener);
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view}
+ * which should display image
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and displaying. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
+ * from configuration} will be used.
+ * @param targetSize {@linkplain ImageSize} Image target size. If null - size will depend on the view
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires
+ * events on UI thread if this method is called on UI thread.
+ * @param progressListener {@linkplain com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener
+ * Listener} for image loading progress. Listener fires events on UI thread if this method
+ * is called on UI thread. Caching on disk should be enabled in
+ * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions options} to make
+ * this listener work.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageAware is null
+ */
+ public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
+ ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
+ checkConfiguration();
+ if (imageAware == null) {
+ throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
+ }
+ if (listener == null) {
+ listener = defaultListener;
+ }
+ if (options == null) {
+ options = configuration.defaultDisplayImageOptions;
+ }
+
+ if (TextUtils.isEmpty(uri)) {
+ engine.cancelDisplayTaskFor(imageAware);
+ listener.onLoadingStarted(uri, imageAware.getWrappedView());
+ if (options.shouldShowImageForEmptyUri()) {
+ imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
+ } else {
+ imageAware.setImageDrawable(null);
+ }
+ listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
+ return;
+ }
+
+ if (targetSize == null) {
+ targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
+ }
+ String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
+ engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
+
+ listener.onLoadingStarted(uri, imageAware.getWrappedView());
+
+ Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
+ if (bmp != null && !bmp.isRecycled()) {
+ L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
+
+ if (options.shouldPostProcess()) {
+ ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
+ options, listener, progressListener, engine.getLockForUri(uri));
+ ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
+ defineHandler(options));
+ if (options.isSyncLoading()) {
+ displayTask.run();
+ } else {
+ engine.submit(displayTask);
+ }
+ } else {
+ options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
+ listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
+ }
+ } else {
+ if (options.shouldShowImageOnLoading()) {
+ imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
+ } else if (options.isResetViewBeforeLoading()) {
+ imageAware.setImageDrawable(null);
+ }
+
+ ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
+ options, listener, progressListener, engine.getLockForUri(uri));
+ LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
+ defineHandler(options));
+ if (options.isSyncLoading()) {
+ displayTask.run();
+ } else {
+ engine.submit(displayTask);
+ }
+ }
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageView when it's turn.
+ * Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration
+ * configuration} will be used.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageView {@link ImageView} which should display image
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageView is null
+ */
+ public void displayImage(String uri, ImageView imageView) {
+ displayImage(uri, new ImageViewAware(imageView), null, null, null);
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageView when it's turn.
+ * Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration
+ * configuration} will be used.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageView {@link ImageView} which should display image
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageView is null
+ */
+ public void displayImage(String uri, ImageView imageView, ImageSize targetImageSize) {
+ displayImage(uri, new ImageViewAware(imageView), null, targetImageSize, null, null);
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageView when it's turn.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageView {@link ImageView} which should display image
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and displaying. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
+ * from configuration} will be used.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageView is null
+ */
+ public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
+ displayImage(uri, new ImageViewAware(imageView), options, null, null);
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageView when it's turn.
+ * Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration
+ * configuration} will be used.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageView {@link ImageView} which should display image
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on
+ * UI thread if this method is called on UI thread.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageView is null
+ */
+ public void displayImage(String uri, ImageView imageView, ImageLoadingListener listener) {
+ displayImage(uri, new ImageViewAware(imageView), null, listener, null);
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageView when it's turn.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageView {@link ImageView} which should display image
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and displaying. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
+ * from configuration} will be used.
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on
+ * UI thread if this method is called on UI thread.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageView is null
+ */
+ public void displayImage(String uri, ImageView imageView, DisplayImageOptions options,
+ ImageLoadingListener listener) {
+ displayImage(uri, imageView, options, listener, null);
+ }
+
+ /**
+ * Adds display image task to execution pool. Image will be set to ImageView when it's turn.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param imageView {@link ImageView} which should display image
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and displaying. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
+ * from configuration} will be used.
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires
+ * events on UI thread if this method is called on UI thread.
+ * @param progressListener {@linkplain com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener
+ * Listener} for image loading progress. Listener fires events on UI thread if this method
+ * is called on UI thread. Caching on disk should be enabled in
+ * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions options} to make
+ * this listener work.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @throws IllegalArgumentException if passed imageView is null
+ */
+ public void displayImage(String uri, ImageView imageView, DisplayImageOptions options,
+ ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
+ displayImage(uri, new ImageViewAware(imageView), options, listener, progressListener);
+ }
+
+ /**
+ * Adds load image task to execution pool. Image will be returned with
+ * {@link ImageLoadingListener#onLoadingComplete(String, android.view.View, android.graphics.Bitmap)} callback}.
+ *
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on UI
+ * thread if this method is called on UI thread.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public void loadImage(String uri, ImageLoadingListener listener) {
+ loadImage(uri, null, null, listener, null);
+ }
+
+ /**
+ * Adds load image task to execution pool. Image will be returned with
+ * {@link ImageLoadingListener#onLoadingComplete(String, android.view.View, android.graphics.Bitmap)} callback}.
+ *
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param targetImageSize Minimal size for {@link Bitmap} which will be returned in
+ * {@linkplain ImageLoadingListener#onLoadingComplete(String, android.view.View,
+ * android.graphics.Bitmap)} callback}. Downloaded image will be decoded
+ * and scaled to {@link Bitmap} of the size which is equal or larger (usually a bit
+ * larger) than incoming targetImageSize.
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires
+ * events on UI thread if this method is called on UI thread.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public void loadImage(String uri, ImageSize targetImageSize, ImageLoadingListener listener) {
+ loadImage(uri, targetImageSize, null, listener, null);
+ }
+
+ /**
+ * Adds load image task to execution pool. Image will be returned with
+ * {@link ImageLoadingListener#onLoadingComplete(String, android.view.View, android.graphics.Bitmap)} callback}.
+ *
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and displaying. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from
+ * configuration} will be used.
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on UI
+ * thread if this method is called on UI thread.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public void loadImage(String uri, DisplayImageOptions options, ImageLoadingListener listener) {
+ loadImage(uri, null, options, listener, null);
+ }
+
+ /**
+ * Adds load image task to execution pool. Image will be returned with
+ * {@link ImageLoadingListener#onLoadingComplete(String, android.view.View, android.graphics.Bitmap)} callback}.
+ *
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param targetImageSize Minimal size for {@link Bitmap} which will be returned in
+ * {@linkplain ImageLoadingListener#onLoadingComplete(String, android.view.View,
+ * android.graphics.Bitmap)} callback}. Downloaded image will be decoded
+ * and scaled to {@link Bitmap} of the size which is equal or larger (usually a bit
+ * larger) than incoming targetImageSize.
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and displaying. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
+ * from configuration} will be used.
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires
+ * events on UI thread if this method is called on UI thread.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
+ ImageLoadingListener listener) {
+ loadImage(uri, targetImageSize, options, listener, null);
+ }
+
+ /**
+ * Adds load image task to execution pool. Image will be returned with
+ * {@link ImageLoadingListener#onLoadingComplete(String, android.view.View, android.graphics.Bitmap)} callback}.
+ *
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param targetImageSize Minimal size for {@link Bitmap} which will be returned in
+ * {@linkplain ImageLoadingListener#onLoadingComplete(String, android.view.View,
+ * android.graphics.Bitmap)} callback}. Downloaded image will be decoded
+ * and scaled to {@link Bitmap} of the size which is equal or larger (usually a bit
+ * larger) than incoming targetImageSize.
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and displaying. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
+ * from configuration} will be used.
+ * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires
+ * events on UI thread if this method is called on UI thread.
+ * @param progressListener {@linkplain com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener
+ * Listener} for image loading progress. Listener fires events on UI thread if this method
+ * is called on UI thread. Caching on disk should be enabled in
+ * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions options} to make
+ * this listener work.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
+ ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
+ checkConfiguration();
+ if (targetImageSize == null) {
+ targetImageSize = configuration.getMaxImageSize();
+ }
+ if (options == null) {
+ options = configuration.defaultDisplayImageOptions;
+ }
+
+ NonViewAware imageAware = new NonViewAware(uri, targetImageSize, ViewScaleType.CROP);
+ displayImage(uri, imageAware, options, listener, progressListener);
+ }
+
+ /**
+ * Loads and decodes image synchronously.
+ * Default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from
+ * configuration} will be used.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @return Result image Bitmap. Can be null if image loading/decoding was failed or cancelled.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public Bitmap loadImageSync(String uri) {
+ return loadImageSync(uri, null, null);
+ }
+
+ /**
+ * Loads and decodes image synchronously.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and scaling. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from
+ * configuration} will be used.
+ * @return Result image Bitmap. Can be null if image loading/decoding was failed or cancelled.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public Bitmap loadImageSync(String uri, DisplayImageOptions options) {
+ return loadImageSync(uri, null, options);
+ }
+
+ /**
+ * Loads and decodes image synchronously.
+ * Default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from
+ * configuration} will be used.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param targetImageSize Minimal size for {@link Bitmap} which will be returned. Downloaded image will be decoded
+ * and scaled to {@link Bitmap} of the size which is equal or larger (usually a bit
+ * larger) than incoming targetImageSize.
+ * @return Result image Bitmap. Can be null if image loading/decoding was failed or cancelled.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public Bitmap loadImageSync(String uri, ImageSize targetImageSize) {
+ return loadImageSync(uri, targetImageSize, null);
+ }
+
+ /**
+ * Loads and decodes image synchronously.
+ * NOTE: {@link #init(ImageLoaderConfiguration)} method must be called before this method call
+ *
+ * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
+ * @param targetImageSize Minimal size for {@link Bitmap} which will be returned. Downloaded image will be decoded
+ * and scaled to {@link Bitmap} of the size which is equal or larger (usually a bit
+ * larger) than incoming targetImageSize.
+ * @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
+ * decoding and scaling. If null - default display image options
+ * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
+ * from configuration} will be used.
+ * @return Result image Bitmap. Can be null if image loading/decoding was failed or cancelled.
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public Bitmap loadImageSync(String uri, ImageSize targetImageSize, DisplayImageOptions options) {
+ if (options == null) {
+ options = configuration.defaultDisplayImageOptions;
+ }
+ options = new DisplayImageOptions.Builder().cloneFrom(options).syncLoading(true).build();
+
+ SyncImageLoadingListener listener = new SyncImageLoadingListener();
+ loadImage(uri, targetImageSize, options, listener);
+ return listener.getLoadedBitmap();
+ }
+
+ /**
+ * Checks if ImageLoader's configuration was initialized
+ *
+ * @throws IllegalStateException if configuration wasn't initialized
+ */
+ private void checkConfiguration() {
+ if (configuration == null) {
+ throw new IllegalStateException(ERROR_NOT_INIT);
+ }
+ }
+
+ /** Sets a default loading listener for all display and loading tasks. */
+ public void setDefaultLoadingListener(ImageLoadingListener listener) {
+ defaultListener = listener == null ? new SimpleImageLoadingListener() : listener;
+ }
+
+ /**
+ * Returns memory cache
+ *
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public MemoryCache getMemoryCache() {
+ checkConfiguration();
+ return configuration.memoryCache;
+ }
+
+ /**
+ * Clears memory cache
+ *
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public void clearMemoryCache() {
+ checkConfiguration();
+ configuration.memoryCache.clear();
+ }
+
+ /**
+ * Returns disk cache
+ *
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @deprecated Use {@link #getDiskCache()} instead
+ */
+ @Deprecated
+ public DiskCache getDiscCache() {
+ return getDiskCache();
+ }
+
+ /**
+ * Returns disk cache
+ *
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public DiskCache getDiskCache() {
+ checkConfiguration();
+ return configuration.diskCache;
+ }
+
+ /**
+ * Clears disk cache.
+ *
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ * @deprecated Use {@link #clearDiskCache()} instead
+ */
+ @Deprecated
+ public void clearDiscCache() {
+ clearDiskCache();
+ }
+
+ /**
+ * Clears disk cache.
+ *
+ * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before
+ */
+ public void clearDiskCache() {
+ checkConfiguration();
+ configuration.diskCache.clear();
+ }
+
+ /**
+ * Returns URI of image which is loading at this moment into passed
+ * {@link com.nostra13.universalimageloader.core.imageaware.ImageAware ImageAware}
+ */
+ public String getLoadingUriForView(ImageAware imageAware) {
+ return engine.getLoadingUriForView(imageAware);
+ }
+
+ /**
+ * Returns URI of image which is loading at this moment into passed
+ * {@link android.widget.ImageView ImageView}
+ */
+ public String getLoadingUriForView(ImageView imageView) {
+ return engine.getLoadingUriForView(new ImageViewAware(imageView));
+ }
+
+ /**
+ * Cancel the task of loading and displaying image for passed
+ * {@link com.nostra13.universalimageloader.core.imageaware.ImageAware ImageAware}.
+ *
+ * @param imageAware {@link com.nostra13.universalimageloader.core.imageaware.ImageAware ImageAware} for
+ * which display task will be cancelled
+ */
+ public void cancelDisplayTask(ImageAware imageAware) {
+ engine.cancelDisplayTaskFor(imageAware);
+ }
+
+ /**
+ * Cancel the task of loading and displaying image for passed
+ * {@link android.widget.ImageView ImageView}.
+ *
+ * @param imageView {@link android.widget.ImageView ImageView} for which display task will be cancelled
+ */
+ public void cancelDisplayTask(ImageView imageView) {
+ engine.cancelDisplayTaskFor(new ImageViewAware(imageView));
+ }
+
+ /**
+ * Denies or allows ImageLoader to download images from the network.
+ *
+ * If downloads are denied and if image isn't cached then
+ * {@link ImageLoadingListener#onLoadingFailed(String, View, FailReason)} callback will be fired with
+ * {@link FailReason.FailType#NETWORK_DENIED}
+ *
+ * @param denyNetworkDownloads pass true - to deny engine to download images from the network; false -
+ * to allow engine to download images from network.
+ */
+ public void denyNetworkDownloads(boolean denyNetworkDownloads) {
+ engine.denyNetworkDownloads(denyNetworkDownloads);
+ }
+
+ /**
+ * Sets option whether ImageLoader will use {@link FlushedInputStream} for network downloads to handle this known problem or not.
+ *
+ * @param handleSlowNetwork pass true - to use {@link FlushedInputStream} for network downloads; false
+ * - otherwise.
+ */
+ public void handleSlowNetwork(boolean handleSlowNetwork) {
+ engine.handleSlowNetwork(handleSlowNetwork);
+ }
+
+ /**
+ * Pause ImageLoader. All new "load&display" tasks won't be executed until ImageLoader is {@link #resume() resumed}.
+ *
+ * Already running tasks are not paused.
+ */
+ public void pause() {
+ if (isInited()) {
+ engine.pause();
+ } else {
+ L.w("Trying to pause not-initialized ImageLoader");
+ }
+ }
+
+ /** Resumes waiting "load&display" tasks */
+ public void resume() {
+ if (isInited()) {
+ engine.resume();
+ } else {
+ L.w("Trying to resume not-initialized ImageLoader");
+ }
+ }
+
+ /**
+ * Cancels all running and scheduled display image tasks.
+ * NOTE: This method doesn't shutdown
+ * {@linkplain com.nostra13.universalimageloader.core.ImageLoaderConfiguration.Builder#taskExecutor(java.util.concurrent.Executor)
+ * custom task executors} if you set them.
+ * ImageLoader still can be used after calling this method.
+ */
+ public void stop() {
+ if (isInited()) {
+ engine.stop();
+ } else {
+ L.w("Trying to stop not-initialized ImageLoader");
+ }
+ }
+
+ /**
+ * {@linkplain #stop() Stops ImageLoader} and clears current configuration.
+ * You can {@linkplain #init(ImageLoaderConfiguration) init} ImageLoader with new configuration after calling this
+ * method.
+ */
+ public void destroy() {
+ if (isInited()) {
+ L.d(LOG_DESTROY);
+ stop();
+ configuration.diskCache.close();
+ engine = null;
+ configuration = null;
+ } else {
+ L.w("Trying to destroy not-initialized ImageLoader");
+ }
+ }
+
+ private static Handler defineHandler(DisplayImageOptions options) {
+ Handler handler = options.getHandler();
+ if (options.isSyncLoading()) {
+ handler = null;
+ } else if (handler == null && Looper.myLooper() == Looper.getMainLooper()) {
+ handler = new Handler();
+ }
+ return handler;
+ }
+
+ /**
+ * Listener which is designed for synchronous image loading.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.9.0
+ */
+ private static class SyncImageLoadingListener extends SimpleImageLoadingListener {
+
+ private Bitmap loadedImage;
+
+ @Override
+ public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
+ this.loadedImage = loadedImage;
+ }
+
+ public Bitmap getLoadedBitmap() {
+ return loadedImage;
+ }
+ }
+}
diff --git a/library/src/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java b/library/src/main/java/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java
similarity index 62%
rename from library/src/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java
index 8fbd8b147..f9f6f74ce 100644
--- a/library/src/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,49 +15,47 @@
*******************************************************************************/
package com.nostra13.universalimageloader.core;
-import java.util.concurrent.Executor;
-
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-
-import com.nostra13.universalimageloader.cache.disc.DiscCacheAware;
-import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import com.nostra13.universalimageloader.cache.disc.DiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
-import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware;
+import com.nostra13.universalimageloader.cache.memory.MemoryCache;
import com.nostra13.universalimageloader.cache.memory.impl.FuzzyKeyMemoryCache;
-import com.nostra13.universalimageloader.core.assist.MemoryCacheUtil;
+import com.nostra13.universalimageloader.core.assist.FlushedInputStream;
+import com.nostra13.universalimageloader.core.assist.ImageSize;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.core.decode.ImageDecoder;
import com.nostra13.universalimageloader.core.download.ImageDownloader;
-import com.nostra13.universalimageloader.core.download.NetworkDeniedImageDownloader;
-import com.nostra13.universalimageloader.core.download.SlowNetworkImageDownloader;
import com.nostra13.universalimageloader.core.process.BitmapProcessor;
import com.nostra13.universalimageloader.utils.L;
+import com.nostra13.universalimageloader.utils.MemoryCacheUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.Executor;
/**
* Presents configuration for {@link ImageLoader}
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @since 1.0.0
* @see ImageLoader
- * @see MemoryCacheAware
- * @see DiscCacheAware
+ * @see MemoryCache
+ * @see DiskCache
* @see DisplayImageOptions
* @see ImageDownloader
* @see FileNameGenerator
+ * @since 1.0.0
*/
public final class ImageLoaderConfiguration {
- final Context context;
+ final Resources resources;
final int maxImageWidthForMemoryCache;
final int maxImageHeightForMemoryCache;
- final int maxImageWidthForDiscCache;
- final int maxImageHeightForDiscCache;
- final CompressFormat imageCompressFormatForDiscCache;
- final int imageQualityForDiscCache;
- final BitmapProcessor processorForDiscCache;
+ final int maxImageWidthForDiskCache;
+ final int maxImageHeightForDiskCache;
+ final BitmapProcessor processorForDiskCache;
final Executor taskExecutor;
final Executor taskExecutorForCachedImages;
@@ -68,35 +66,30 @@ public final class ImageLoaderConfiguration {
final int threadPriority;
final QueueProcessingType tasksProcessingType;
- final MemoryCacheAware memoryCache;
- final DiscCacheAware discCache;
+ final MemoryCache memoryCache;
+ final DiskCache diskCache;
final ImageDownloader downloader;
final ImageDecoder decoder;
final DisplayImageOptions defaultDisplayImageOptions;
- final boolean writeLogs;
- final DiscCacheAware reserveDiscCache;
final ImageDownloader networkDeniedDownloader;
final ImageDownloader slowNetworkDownloader;
private ImageLoaderConfiguration(final Builder builder) {
- context = builder.context;
+ resources = builder.context.getResources();
maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache;
maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache;
- maxImageWidthForDiscCache = builder.maxImageWidthForDiscCache;
- maxImageHeightForDiscCache = builder.maxImageHeightForDiscCache;
- imageCompressFormatForDiscCache = builder.imageCompressFormatForDiscCache;
- imageQualityForDiscCache = builder.imageQualityForDiscCache;
- processorForDiscCache = builder.processorForDiscCache;
+ maxImageWidthForDiskCache = builder.maxImageWidthForDiskCache;
+ maxImageHeightForDiskCache = builder.maxImageHeightForDiskCache;
+ processorForDiskCache = builder.processorForDiskCache;
taskExecutor = builder.taskExecutor;
taskExecutorForCachedImages = builder.taskExecutorForCachedImages;
threadPoolSize = builder.threadPoolSize;
threadPriority = builder.threadPriority;
tasksProcessingType = builder.tasksProcessingType;
- discCache = builder.discCache;
+ diskCache = builder.diskCache;
memoryCache = builder.memoryCache;
defaultDisplayImageOptions = builder.defaultDisplayImageOptions;
- writeLogs = builder.writeLogs;
downloader = builder.downloader;
decoder = builder.decoder;
@@ -106,7 +99,7 @@ private ImageLoaderConfiguration(final Builder builder) {
networkDeniedDownloader = new NetworkDeniedImageDownloader(downloader);
slowNetworkDownloader = new SlowNetworkImageDownloader(downloader);
- reserveDiscCache = DefaultConfigurationFactory.createReserveDiscCache(context);
+ L.writeDebugLogs(builder.writeLogs);
}
/**
@@ -115,25 +108,39 @@ private ImageLoaderConfiguration(final Builder builder) {
*
* - maxImageWidthForMemoryCache = device's screen width
* - maxImageHeightForMemoryCache = device's screen height
- * - maxImageWidthForDiscCache = unlimited
- * - maxImageHeightForDiscCache = unlimited
+ * - maxImageWidthForDikcCache = unlimited
+ * - maxImageHeightForDiskCache = unlimited
* - threadPoolSize = {@link Builder#DEFAULT_THREAD_POOL_SIZE this}
* - threadPriority = {@link Builder#DEFAULT_THREAD_PRIORITY this}
* - allow to cache different sizes of image in memory
- * - memoryCache = {@link DefaultConfigurationFactory#createMemoryCache(int)}
- * - discCache = {@link UnlimitedDiscCache}
+ * - memoryCache = {@link DefaultConfigurationFactory#createMemoryCache(android.content.Context, int)}
+ * - diskCache = {@link com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache}
* - imageDownloader = {@link DefaultConfigurationFactory#createImageDownloader(Context)}
* - imageDecoder = {@link DefaultConfigurationFactory#createImageDecoder(boolean)}
- * - discCacheFileNameGenerator = {@link DefaultConfigurationFactory#createFileNameGenerator()}
+ * - diskCacheFileNameGenerator = {@link DefaultConfigurationFactory#createFileNameGenerator()}
* - defaultDisplayImageOptions = {@link DisplayImageOptions#createSimple() Simple options}
* - tasksProcessingOrder = {@link QueueProcessingType#FIFO}
* - detailed logging disabled
*
- * */
+ */
public static ImageLoaderConfiguration createDefault(Context context) {
return new Builder(context).build();
}
+ ImageSize getMaxImageSize() {
+ DisplayMetrics displayMetrics = resources.getDisplayMetrics();
+
+ int width = maxImageWidthForMemoryCache;
+ if (width <= 0) {
+ width = displayMetrics.widthPixels;
+ }
+ int height = maxImageHeightForMemoryCache;
+ if (height <= 0) {
+ height = displayMetrics.heightPixels;
+ }
+ return new ImageSize(width, height);
+ }
+
/**
* Builder for {@link ImageLoaderConfiguration}
*
@@ -141,8 +148,8 @@ public static ImageLoaderConfiguration createDefault(Context context) {
*/
public static class Builder {
- 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_DISK_CACHE_PARAMS = "diskCache(), diskCacheSize() and diskCacheFileCount calls overlap each other";
+ private static final String WARNING_OVERLAP_DISK_CACHE_NAME_GENERATOR = "diskCache() and diskCacheFileNameGenerator() 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.";
@@ -150,7 +157,7 @@ public static class Builder {
/** {@value} */
public static final int DEFAULT_THREAD_POOL_SIZE = 3;
/** {@value} */
- public static final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY - 1;
+ public static final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY - 2;
/** {@value} */
public static final QueueProcessingType DEFAULT_TASK_PROCESSING_TYPE = QueueProcessingType.FIFO;
@@ -158,11 +165,9 @@ public static class Builder {
private int maxImageWidthForMemoryCache = 0;
private int maxImageHeightForMemoryCache = 0;
- private int maxImageWidthForDiscCache = 0;
- private int maxImageHeightForDiscCache = 0;
- private CompressFormat imageCompressFormatForDiscCache = null;
- private int imageQualityForDiscCache = 0;
- private BitmapProcessor processorForDiscCache = null;
+ private int maxImageWidthForDiskCache = 0;
+ private int maxImageHeightForDiskCache = 0;
+ private BitmapProcessor processorForDiskCache = null;
private Executor taskExecutor = null;
private Executor taskExecutorForCachedImages = null;
@@ -175,12 +180,12 @@ public static class Builder {
private QueueProcessingType tasksProcessingType = DEFAULT_TASK_PROCESSING_TYPE;
private int memoryCacheSize = 0;
- private int discCacheSize = 0;
- private int discCacheFileCount = 0;
+ private long diskCacheSize = 0;
+ private int diskCacheFileCount = 0;
- private MemoryCacheAware memoryCache = null;
- private DiscCacheAware discCache = null;
- private FileNameGenerator discCacheFileNameGenerator = null;
+ private MemoryCache memoryCache = null;
+ private DiskCache diskCache = null;
+ private FileNameGenerator diskCacheFileNameGenerator = null;
private ImageDownloader downloader = null;
private ImageDecoder decoder;
private DisplayImageOptions defaultDisplayImageOptions = null;
@@ -194,10 +199,10 @@ public Builder(Context context) {
/**
* Sets options for memory cache
*
- * @param maxImageWidthForMemoryCache Maximum image width which will be used for memory saving during decoding
- * an image to {@link android.graphics.Bitmap Bitmap}. Default value - device's screen width
+ * @param maxImageWidthForMemoryCache Maximum image width which will be used for memory saving during decoding
+ * an image to {@link android.graphics.Bitmap Bitmap}. Default value - device's screen width
* @param maxImageHeightForMemoryCache Maximum image height which will be used for memory saving during decoding
- * an image to {@link android.graphics.Bitmap Bitmap}. Default value - device's screen height
+ * an image to {@link android.graphics.Bitmap Bitmap}. Default value - device's screen height
*/
public Builder memoryCacheExtraOptions(int maxImageWidthForMemoryCache, int maxImageHeightForMemoryCache) {
this.maxImageWidthForMemoryCache = maxImageWidthForMemoryCache;
@@ -206,23 +211,29 @@ public Builder memoryCacheExtraOptions(int maxImageWidthForMemoryCache, int maxI
}
/**
- * Sets options for resizing/compressing of downloaded images before saving to disc cache.
+ * @deprecated Use
+ * {@link #diskCacheExtraOptions(int, int, com.nostra13.universalimageloader.core.process.BitmapProcessor)}
+ * instead
+ */
+ @Deprecated
+ public Builder discCacheExtraOptions(int maxImageWidthForDiskCache, int maxImageHeightForDiskCache,
+ BitmapProcessor processorForDiskCache) {
+ return diskCacheExtraOptions(maxImageWidthForDiskCache, maxImageHeightForDiskCache, processorForDiskCache);
+ }
+
+ /**
+ * Sets options for resizing/compressing of downloaded images before saving to disk cache.
* NOTE: Use this option only when you have appropriate needs. It can make ImageLoader slower.
*
- * @param maxImageWidthForDiscCache Maximum width of downloaded images for saving at disc cache
- * @param maxImageHeightForDiscCache Maximum height of downloaded images for saving at disc cache
- * @param compressFormat {@link android.graphics.Bitmap.CompressFormat Compress format} downloaded images to
- * save them at disc cache
- * @param compressQuality Hint to the compressor, 0-100. 0 meaning compress for small size, 100 meaning compress
- * for max quality. Some formats, like PNG which is lossless, will ignore the quality setting
- * @param processorForDiscCache null-ok; {@linkplain BitmapProcessor Bitmap processor} which process images before saving them in disc cache
+ * @param maxImageWidthForDiskCache Maximum width of downloaded images for saving at disk cache
+ * @param maxImageHeightForDiskCache Maximum height of downloaded images for saving at disk cache
+ * @param processorForDiskCache null-ok; {@linkplain BitmapProcessor Bitmap processor} which process images before saving them in disc cache
*/
- public Builder discCacheExtraOptions(int maxImageWidthForDiscCache, int maxImageHeightForDiscCache, CompressFormat compressFormat, int compressQuality, BitmapProcessor processorForDiscCache) {
- this.maxImageWidthForDiscCache = maxImageWidthForDiscCache;
- this.maxImageHeightForDiscCache = maxImageHeightForDiscCache;
- this.imageCompressFormatForDiscCache = compressFormat;
- this.imageQualityForDiscCache = compressQuality;
- this.processorForDiscCache = processorForDiscCache;
+ public Builder diskCacheExtraOptions(int maxImageWidthForDiskCache, int maxImageHeightForDiskCache,
+ BitmapProcessor processorForDiskCache) {
+ this.maxImageWidthForDiskCache = maxImageWidthForDiskCache;
+ this.maxImageHeightForDiskCache = maxImageHeightForDiskCache;
+ this.processorForDiskCache = processorForDiskCache;
return this;
}
@@ -249,11 +260,11 @@ public Builder taskExecutor(Executor executor) {
}
/**
- * Sets custom {@linkplain Executor executor} for tasks of displaying cached on disc images (these tasks
+ * Sets custom {@linkplain Executor executor} for tasks of displaying cached on disk 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
+ * tasks about cached images (this method) 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
@@ -278,7 +289,7 @@ public Builder taskExecutorForCachedImages(Executor executorForCachedImages) {
/**
* 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);
@@ -292,7 +303,7 @@ public Builder threadPoolSize(int threadPoolSize) {
* 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);
@@ -302,7 +313,7 @@ public Builder threadPriority(int threadPriority) {
this.threadPriority = Thread.MIN_PRIORITY;
} else {
if (threadPriority > Thread.MAX_PRIORITY) {
- threadPriority = Thread.MAX_PRIORITY;
+ this.threadPriority = Thread.MAX_PRIORITY;
} else {
this.threadPriority = threadPriority;
}
@@ -317,7 +328,7 @@ public Builder threadPriority(int threadPriority) {
* So the default behavior is to allow to cache multiple sizes of one image in memory. You can
* deny it by calling this method: so when some image will be cached in memory then previous
* cached size of this image (if it exists) will be removed from memory cache before.
- * */
+ */
public Builder denyCacheImageMultipleSizesInMemory() {
this.denyCacheImageMultipleSizesInMemory = true;
return this;
@@ -341,8 +352,8 @@ public Builder tasksProcessingOrder(QueueProcessingType tasksProcessingType) {
* Default value - 1/8 of available app memory.
* NOTE: If you use this method then
* {@link com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache LruMemoryCache} will be used as
- * memory cache. You can use {@link #memoryCache(MemoryCacheAware)} method to set your own implementation of
- * {@link MemoryCacheAware}.
+ * memory cache. You can use {@link #memoryCache(MemoryCache)} method to set your own implementation of
+ * {@link MemoryCache}.
*/
public Builder memoryCacheSize(int memoryCacheSize) {
if (memoryCacheSize <= 0) throw new IllegalArgumentException("memoryCacheSize must be a positive number");
@@ -361,19 +372,20 @@ public Builder memoryCacheSize(int memoryCacheSize) {
* Default value - 1/8 of available app memory.
* NOTE: If you use this method then
* {@link com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache LruMemoryCache} will be used as
- * memory cache. You can use {@link #memoryCache(MemoryCacheAware)} method to set your own implementation of
- * {@link MemoryCacheAware}.
+ * memory cache. You can use {@link #memoryCache(MemoryCache)} method to set your own implementation of
+ * {@link MemoryCache}.
*/
- public Builder memoryCacheSizePercentage(int avaialbleMemoryPercent) {
- if (avaialbleMemoryPercent <= 0 || avaialbleMemoryPercent >= 100)
- throw new IllegalArgumentException("avaialbleMemoryPercent must be in range (0 < % < 100)");
+ public Builder memoryCacheSizePercentage(int availableMemoryPercent) {
+ if (availableMemoryPercent <= 0 || availableMemoryPercent >= 100) {
+ throw new IllegalArgumentException("availableMemoryPercent must be in range (0 < % < 100)");
+ }
if (memoryCache != null) {
L.w(WARNING_OVERLAP_MEMORY_CACHE);
}
long availableMemory = Runtime.getRuntime().maxMemory();
- memoryCacheSize = (int) (availableMemory * (avaialbleMemoryPercent / 100f));
+ memoryCacheSize = (int) (availableMemory * (availableMemoryPercent / 100f));
return this;
}
@@ -387,7 +399,7 @@ public Builder memoryCacheSizePercentage(int avaialbleMemoryPercent) {
* - {@link #memoryCacheSize(int)}
*
*/
- public Builder memoryCache(MemoryCacheAware memoryCache) {
+ public Builder memoryCache(MemoryCache memoryCache) {
if (memoryCacheSize != 0) {
L.w(WARNING_OVERLAP_MEMORY_CACHE);
}
@@ -396,57 +408,106 @@ public Builder memoryCache(MemoryCacheAware memoryCache) {
return this;
}
+ /** @deprecated Use {@link #diskCacheSize(int)} instead */
+ @Deprecated
+ public Builder discCacheSize(int maxCacheSize) {
+ return diskCacheSize(maxCacheSize);
+ }
+
/**
- * Sets maximum disc cache size for images (in bytes).
- * By default: disc cache is unlimited.
+ * Sets maximum disk cache size for images (in bytes).
+ * By default: disk cache is unlimited.
* NOTE: If you use this method then
- * {@link com.nostra13.universalimageloader.cache.disc.impl.TotalSizeLimitedDiscCache TotalSizeLimitedDiscCache}
- * will be used as disc cache. You can use {@link #discCache(DiscCacheAware)} method for introduction your own
- * implementation of {@link DiscCacheAware}
+ * {@link com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache LruDiskCache}
+ * will be used as disk cache. You can use {@link #diskCache(DiskCache)} method for introduction your own
+ * implementation of {@link DiskCache}
*/
- public Builder discCacheSize(int maxCacheSize) {
+ public Builder diskCacheSize(int maxCacheSize) {
if (maxCacheSize <= 0) throw new IllegalArgumentException("maxCacheSize must be a positive number");
- if (discCache != null || discCacheFileCount > 0) {
- L.w(WARNING_OVERLAP_DISC_CACHE_PARAMS);
+ if (diskCache != null) {
+ L.w(WARNING_OVERLAP_DISK_CACHE_PARAMS);
}
- this.discCacheSize = maxCacheSize;
+ this.diskCacheSize = maxCacheSize;
return this;
}
+ /** @deprecated Use {@link #diskCacheFileCount(int)} instead */
+ @Deprecated
+ public Builder discCacheFileCount(int maxFileCount) {
+ return diskCacheFileCount(maxFileCount);
+ }
+
/**
- * Sets maximum file count in disc cache directory.
- * By default: disc cache is unlimited.
+ * Sets maximum file count in disk cache directory.
+ * By default: disk cache is unlimited.
* NOTE: If you use this method then
- * {@link com.nostra13.universalimageloader.cache.disc.impl.FileCountLimitedDiscCache FileCountLimitedDiscCache}
- * will be used as disc cache. You can use {@link #discCache(DiscCacheAware)} method for introduction your own
- * implementation of {@link DiscCacheAware}
+ * {@link com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache LruDiskCache}
+ * will be used as disk cache. You can use {@link #diskCache(DiskCache)} method for introduction your own
+ * implementation of {@link DiskCache}
*/
- public Builder discCacheFileCount(int maxFileCount) {
+ public Builder diskCacheFileCount(int maxFileCount) {
if (maxFileCount <= 0) throw new IllegalArgumentException("maxFileCount must be a positive number");
- if (discCache != null || discCacheSize > 0) {
- L.w(WARNING_OVERLAP_DISC_CACHE_PARAMS);
+ if (diskCache != null) {
+ L.w(WARNING_OVERLAP_DISK_CACHE_PARAMS);
}
- this.discCacheSize = 0;
- this.discCacheFileCount = maxFileCount;
+ this.diskCacheFileCount = maxFileCount;
return this;
}
+ /** @deprecated Use {@link #diskCacheFileNameGenerator(com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator)} */
+ @Deprecated
+ public Builder discCacheFileNameGenerator(FileNameGenerator fileNameGenerator) {
+ return diskCacheFileNameGenerator(fileNameGenerator);
+ }
+
/**
- * Sets name generator for files cached in disc cache.
+ * Sets name generator for files cached in disk cache.
* Default value -
* {@link com.nostra13.universalimageloader.core.DefaultConfigurationFactory#createFileNameGenerator()
* DefaultConfigurationFactory.createFileNameGenerator()}
*/
- public Builder discCacheFileNameGenerator(FileNameGenerator fileNameGenerator) {
- if (discCache != null) {
- L.w(WARNING_OVERLAP_DISC_CACHE_NAME_GENERATOR);
+ public Builder diskCacheFileNameGenerator(FileNameGenerator fileNameGenerator) {
+ if (diskCache != null) {
+ L.w(WARNING_OVERLAP_DISK_CACHE_NAME_GENERATOR);
}
- this.discCacheFileNameGenerator = fileNameGenerator;
+ this.diskCacheFileNameGenerator = fileNameGenerator;
+ return this;
+ }
+
+ /** @deprecated Use {@link #diskCache(com.nostra13.universalimageloader.cache.disc.DiskCache)} */
+ @Deprecated
+ public Builder discCache(DiskCache diskCache) {
+ return diskCache(diskCache);
+ }
+
+ /**
+ * Sets disk cache for images.
+ * Default value - {@link com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache
+ * UnlimitedDiskCache}. Cache directory is defined by
+ * {@link com.nostra13.universalimageloader.utils.StorageUtils#getCacheDirectory(Context)
+ * StorageUtils.getCacheDirectory(Context)}.
+ *
+ * NOTE: If you set custom disk cache then following configuration option will not be considered:
+ *
+ * - {@link #diskCacheSize(int)}
+ * - {@link #diskCacheFileCount(int)}
+ * - {@link #diskCacheFileNameGenerator(FileNameGenerator)}
+ *
+ */
+ public Builder diskCache(DiskCache diskCache) {
+ if (diskCacheSize > 0 || diskCacheFileCount > 0) {
+ L.w(WARNING_OVERLAP_DISK_CACHE_PARAMS);
+ }
+ if (diskCacheFileNameGenerator != null) {
+ L.w(WARNING_OVERLAP_DISK_CACHE_NAME_GENERATOR);
+ }
+
+ this.diskCache = diskCache;
return this;
}
@@ -455,7 +516,7 @@ public Builder discCacheFileNameGenerator(FileNameGenerator fileNameGenerator) {
* Default value -
* {@link com.nostra13.universalimageloader.core.DefaultConfigurationFactory#createImageDownloader(Context)
* DefaultConfigurationFactory.createImageDownloader()}
- * */
+ */
public Builder imageDownloader(ImageDownloader imageDownloader) {
this.downloader = imageDownloader;
return this;
@@ -466,38 +527,12 @@ public Builder imageDownloader(ImageDownloader imageDownloader) {
* Default value -
* {@link com.nostra13.universalimageloader.core.DefaultConfigurationFactory#createImageDecoder(boolean)
* DefaultConfigurationFactory.createImageDecoder()}
- * */
+ */
public Builder imageDecoder(ImageDecoder imageDecoder) {
this.decoder = imageDecoder;
return this;
}
- /**
- * Sets disc cache for images.
- * Default value - {@link com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache
- * 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 || 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;
- }
-
/**
* Sets default {@linkplain DisplayImageOptions display image options} for image displaying. These options will
* be used for every {@linkplain ImageLoader#displayImage(String, android.widget.ImageView) image display call}
@@ -511,7 +546,8 @@ public Builder defaultDisplayImageOptions(DisplayImageOptions defaultDisplayImag
/**
* Enables detail logging of {@link ImageLoader} work. To prevent detail logs don't call this method.
- * Consider {@link com.nostra13.universalimageloader.utils.L#disableLogging()} to disable ImageLoader logging completely (even error logs)
+ * Consider {@link com.nostra13.universalimageloader.utils.L#disableLogging()} to disable
+ * ImageLoader logging completely (even error logs)
*/
public Builder writeDebugLogs() {
this.writeLogs = true;
@@ -526,26 +562,29 @@ public ImageLoaderConfiguration build() {
private void initEmptyFieldsWithDefaultValues() {
if (taskExecutor == null) {
- taskExecutor = DefaultConfigurationFactory.createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
+ taskExecutor = DefaultConfigurationFactory
+ .createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
} else {
customExecutor = true;
}
if (taskExecutorForCachedImages == null) {
- taskExecutorForCachedImages = DefaultConfigurationFactory.createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
+ taskExecutorForCachedImages = DefaultConfigurationFactory
+ .createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
} else {
customExecutorForCachedImages = true;
}
- if (discCache == null) {
- if (discCacheFileNameGenerator == null) {
- discCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator();
+ if (diskCache == null) {
+ if (diskCacheFileNameGenerator == null) {
+ diskCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator();
}
- discCache = DefaultConfigurationFactory.createDiscCache(context, discCacheFileNameGenerator, discCacheSize, discCacheFileCount);
+ diskCache = DefaultConfigurationFactory
+ .createDiskCache(context, diskCacheFileNameGenerator, diskCacheSize, diskCacheFileCount);
}
if (memoryCache == null) {
- memoryCache = DefaultConfigurationFactory.createMemoryCache(memoryCacheSize);
+ memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);
}
if (denyCacheImageMultipleSizesInMemory) {
- memoryCache = new FuzzyKeyMemoryCache(memoryCache, MemoryCacheUtil.createFuzzyKeyComparator());
+ memoryCache = new FuzzyKeyMemoryCache(memoryCache, MemoryCacheUtils.createFuzzyKeyComparator());
}
if (downloader == null) {
downloader = DefaultConfigurationFactory.createImageDownloader(context);
@@ -558,4 +597,59 @@ private void initEmptyFieldsWithDefaultValues() {
}
}
}
+
+ /**
+ * Decorator. Prevents downloads from network (throws {@link IllegalStateException exception}).
+ * In most cases this downloader shouldn't be used directly.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.8.0
+ */
+ private static class NetworkDeniedImageDownloader implements ImageDownloader {
+
+ private final ImageDownloader wrappedDownloader;
+
+ public NetworkDeniedImageDownloader(ImageDownloader wrappedDownloader) {
+ this.wrappedDownloader = wrappedDownloader;
+ }
+
+ @Override
+ public InputStream getStream(String imageUri, Object extra) throws IOException {
+ switch (Scheme.ofUri(imageUri)) {
+ case HTTP:
+ case HTTPS:
+ throw new IllegalStateException();
+ default:
+ return wrappedDownloader.getStream(imageUri, extra);
+ }
+ }
+ }
+
+ /**
+ * Decorator. Handles this problem on slow networks
+ * using {@link com.nostra13.universalimageloader.core.assist.FlushedInputStream}.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.8.1
+ */
+ private static class SlowNetworkImageDownloader implements ImageDownloader {
+
+ private final ImageDownloader wrappedDownloader;
+
+ public SlowNetworkImageDownloader(ImageDownloader wrappedDownloader) {
+ this.wrappedDownloader = wrappedDownloader;
+ }
+
+ @Override
+ public InputStream getStream(String imageUri, Object extra) throws IOException {
+ InputStream imageStream = wrappedDownloader.getStream(imageUri, extra);
+ switch (Scheme.ofUri(imageUri)) {
+ case HTTP:
+ case HTTPS:
+ return new FlushedInputStream(imageStream);
+ default:
+ return imageStream;
+ }
+ }
+ }
}
diff --git a/library/src/com/nostra13/universalimageloader/core/ImageLoaderEngine.java b/library/src/main/java/com/nostra13/universalimageloader/core/ImageLoaderEngine.java
similarity index 62%
rename from library/src/com/nostra13/universalimageloader/core/ImageLoaderEngine.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/ImageLoaderEngine.java
index 25bb2cd11..57831af6c 100644
--- a/library/src/com/nostra13/universalimageloader/core/ImageLoaderEngine.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/ImageLoaderEngine.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,21 +16,23 @@
package com.nostra13.universalimageloader.core;
import android.view.View;
-import android.widget.ImageView;
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.imageaware.ImageAware;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
+import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
+import static com.nostra13.universalimageloader.core.download.ImageDownloader.*;
+
/**
* {@link ImageLoader} engine which responsible for {@linkplain LoadAndDisplayImageTask display task} execution.
*
@@ -43,22 +45,25 @@ class ImageLoaderEngine {
private Executor taskExecutor;
private Executor taskExecutorForCachedImages;
- private ExecutorService taskDistributor;
+ private Executor taskDistributor;
- private final Map cacheKeysForImageViews = Collections.synchronizedMap(new HashMap());
+ private final Map cacheKeysForImageAwares = Collections
+ .synchronizedMap(new HashMap());
private final Map uriLocks = new WeakHashMap();
private final AtomicBoolean paused = new AtomicBoolean(false);
private final AtomicBoolean networkDenied = new AtomicBoolean(false);
private final AtomicBoolean slowNetwork = new AtomicBoolean(false);
+ private final Object pauseLock = new Object();
+
ImageLoaderEngine(ImageLoaderConfiguration configuration) {
this.configuration = configuration;
taskExecutor = configuration.taskExecutor;
taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;
- taskDistributor = Executors.newCachedThreadPool();
+ taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
}
/** Submits task to execution pool */
@@ -66,9 +71,11 @@ void submit(final LoadAndDisplayImageTask task) {
taskDistributor.execute(new Runnable() {
@Override
public void run() {
- boolean isImageCachedOnDisc = configuration.discCache.get(task.getLoadingUri()).exists();
+ File image = configuration.diskCache.get(task.getLoadingUri());
+ boolean isImageCachedOnDisk = image != null && image.exists()
+ || isLocalUri(task.getLoadingUri());
initExecutorsIfNeed();
- if (isImageCachedOnDisc) {
+ if (isImageCachedOnDisk) {
taskExecutorForCachedImages.execute(task);
} else {
taskExecutor.execute(task);
@@ -83,47 +90,56 @@ void submit(ProcessAndDisplayImageTask task) {
taskExecutorForCachedImages.execute(task);
}
+ private boolean isLocalUri(String uri) {
+ Scheme scheme = Scheme.ofUri(uri);
+ return scheme == Scheme.ASSETS || scheme == Scheme.FILE || scheme == Scheme.DRAWABLE;
+ }
+
private void initExecutorsIfNeed() {
if (!configuration.customExecutor && ((ExecutorService) taskExecutor).isShutdown()) {
taskExecutor = createTaskExecutor();
}
- if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages).isShutdown()) {
+ if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages)
+ .isShutdown()) {
taskExecutorForCachedImages = createTaskExecutor();
}
}
private Executor createTaskExecutor() {
- return DefaultConfigurationFactory.createExecutor(configuration.threadPoolSize, configuration.threadPriority, configuration.tasksProcessingType);
+ return DefaultConfigurationFactory
+ .createExecutor(configuration.threadPoolSize, configuration.threadPriority,
+ configuration.tasksProcessingType);
}
- /** Returns URI of image which is loading at this moment into passed {@link ImageView} */
- String getLoadingUriForView(ImageView imageView) {
- return cacheKeysForImageViews.get(imageView.hashCode());
+ /**
+ * Returns URI of image which is loading at this moment into passed {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}
+ */
+ String getLoadingUriForView(ImageAware imageAware) {
+ return cacheKeysForImageAwares.get(imageAware.getId());
}
/**
- * Associates memoryCacheKey with imageView. Then it helps to define image URI is loaded into
- * ImageView at exact moment.
+ * Associates memoryCacheKey with imageAware. Then it helps to define image URI is loaded into View at
+ * exact moment.
*/
- void prepareDisplayTaskFor(ImageView imageView, String memoryCacheKey) {
- cacheKeysForImageViews.put(imageView.hashCode(), memoryCacheKey);
+ void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
+ cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
}
/**
- * Cancels the task of loading and displaying image for incoming imageView.
+ * Cancels the task of loading and displaying image for incoming imageAware.
*
- * @param imageView {@link ImageView} for which display task will be cancelled
+ * @param imageAware {@link com.nostra13.universalimageloader.core.imageaware.ImageAware} for which display task
+ * will be cancelled
*/
- void cancelDisplayTaskFor(ImageView imageView) {
- cacheKeysForImageViews.remove(imageView.hashCode());
+ void cancelDisplayTaskFor(ImageAware imageAware) {
+ cacheKeysForImageAwares.remove(imageAware.getId());
}
/**
- * Denies or allows engine to download images from the network.
- *
- * If downloads are denied and if image isn't cached then
- * {@link ImageLoadingListener#onLoadingFailed(String, View, FailReason)} callback will be fired with
- * {@link FailReason.FailType#NETWORK_DENIED}
+ * Denies or allows engine to download images from the network.
If downloads are denied and if image
+ * isn't cached then {@link ImageLoadingListener#onLoadingFailed(String, View, FailReason)} callback will be fired
+ * with {@link FailReason.FailType#NETWORK_DENIED}
*
* @param denyNetworkDownloads pass true - to deny engine to download images from the network; false -
* to allow engine to download images from network.
@@ -144,8 +160,8 @@ void handleSlowNetwork(boolean handleSlowNetwork) {
}
/**
- * Pauses engine. All new "load&display" tasks won't be executed until ImageLoader is {@link #resume() resumed}.
- * Already running tasks are not paused.
+ * Pauses engine. All new "load&display" tasks won't be executed until ImageLoader is {@link #resume() resumed}.
Already running tasks are not paused.
*/
void pause() {
paused.set(true);
@@ -153,13 +169,19 @@ void pause() {
/** Resumes engine work. Paused "load&display" tasks will continue its work. */
void resume() {
- synchronized (paused) {
- paused.set(false);
- paused.notifyAll();
+ paused.set(false);
+ synchronized (pauseLock) {
+ pauseLock.notifyAll();
}
}
- /** Stops engine, cancels all running and scheduled display image tasks. Clears internal data. */
+ /**
+ * Stops engine, cancels all running and scheduled display image tasks. Clears internal data.
+ *
+ * NOTE: This method doesn't shutdown
+ * {@linkplain com.nostra13.universalimageloader.core.ImageLoaderConfiguration.Builder#taskExecutor(java.util.concurrent.Executor)
+ * custom task executors} if you set them.
+ */
void stop() {
if (!configuration.customExecutor) {
((ExecutorService) taskExecutor).shutdownNow();
@@ -168,10 +190,14 @@ void stop() {
((ExecutorService) taskExecutorForCachedImages).shutdownNow();
}
- cacheKeysForImageViews.clear();
+ cacheKeysForImageAwares.clear();
uriLocks.clear();
}
+ void fireCallback(Runnable r) {
+ taskDistributor.execute(r);
+ }
+
ReentrantLock getLockForUri(String uri) {
ReentrantLock lock = uriLocks.get(uri);
if (lock == null) {
@@ -185,6 +211,10 @@ AtomicBoolean getPause() {
return paused;
}
+ Object getPauseLock() {
+ return pauseLock;
+ }
+
boolean isNetworkDenied() {
return networkDenied.get();
}
diff --git a/library/src/com/nostra13/universalimageloader/core/ImageLoadingInfo.java b/library/src/main/java/com/nostra13/universalimageloader/core/ImageLoadingInfo.java
similarity index 65%
rename from library/src/com/nostra13/universalimageloader/core/ImageLoadingInfo.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/ImageLoadingInfo.java
index dbc4425b6..2950fc771 100644
--- a/library/src/com/nostra13/universalimageloader/core/ImageLoadingInfo.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/ImageLoadingInfo.java
@@ -15,10 +15,10 @@
*******************************************************************************/
package com.nostra13.universalimageloader.core;
-import android.widget.ImageView;
-import com.nostra13.universalimageloader.core.assist.ImageLoadingListener;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener;
import com.nostra13.universalimageloader.core.assist.ImageSize;
-import com.nostra13.universalimageloader.core.assist.MemoryCacheUtil;
+import com.nostra13.universalimageloader.core.imageaware.ImageAware;
import java.util.concurrent.locks.ReentrantLock;
@@ -26,27 +26,32 @@
* Information for load'n'display image task
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @see MemoryCacheUtil
+ * @see com.nostra13.universalimageloader.utils.MemoryCacheUtils
* @see DisplayImageOptions
* @see ImageLoadingListener
+ * @see com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener
* @since 1.3.1
*/
final class ImageLoadingInfo {
final String uri;
final String memoryCacheKey;
- final ImageView imageView;
+ final ImageAware imageAware;
final ImageSize targetSize;
final DisplayImageOptions options;
final ImageLoadingListener listener;
+ final ImageLoadingProgressListener progressListener;
final ReentrantLock loadFromUriLock;
- public ImageLoadingInfo(String uri, ImageView imageView, ImageSize targetSize, String memoryCacheKey, DisplayImageOptions options, ImageLoadingListener listener, ReentrantLock loadFromUriLock) {
+ public ImageLoadingInfo(String uri, ImageAware imageAware, ImageSize targetSize, String memoryCacheKey,
+ DisplayImageOptions options, ImageLoadingListener listener,
+ ImageLoadingProgressListener progressListener, ReentrantLock loadFromUriLock) {
this.uri = uri;
- this.imageView = imageView;
+ this.imageAware = imageAware;
this.targetSize = targetSize;
this.options = options;
this.listener = listener;
+ this.progressListener = progressListener;
this.loadFromUriLock = loadFromUriLock;
this.memoryCacheKey = memoryCacheKey;
}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/LoadAndDisplayImageTask.java b/library/src/main/java/com/nostra13/universalimageloader/core/LoadAndDisplayImageTask.java
new file mode 100644
index 000000000..90a737b41
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/LoadAndDisplayImageTask.java
@@ -0,0 +1,482 @@
+/*******************************************************************************
+ * Copyright 2011-2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core;
+
+import android.graphics.Bitmap;
+import android.os.Handler;
+import com.nostra13.universalimageloader.core.assist.FailReason;
+import com.nostra13.universalimageloader.core.assist.FailReason.FailType;
+import com.nostra13.universalimageloader.core.assist.ImageScaleType;
+import com.nostra13.universalimageloader.core.assist.ImageSize;
+import com.nostra13.universalimageloader.core.assist.LoadedFrom;
+import com.nostra13.universalimageloader.core.assist.ViewScaleType;
+import com.nostra13.universalimageloader.core.decode.ImageDecoder;
+import com.nostra13.universalimageloader.core.decode.ImageDecodingInfo;
+import com.nostra13.universalimageloader.core.download.ImageDownloader;
+import com.nostra13.universalimageloader.core.download.ImageDownloader.Scheme;
+import com.nostra13.universalimageloader.core.imageaware.ImageAware;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener;
+import com.nostra13.universalimageloader.utils.IoUtils;
+import com.nostra13.universalimageloader.utils.L;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Presents load'n'display image task. Used to load image from Internet or file system, decode it to {@link Bitmap}, and
+ * display it in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware} using {@link DisplayBitmapTask}.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @see ImageLoaderConfiguration
+ * @see ImageLoadingInfo
+ * @since 1.3.1
+ */
+final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {
+
+ private static final String LOG_WAITING_FOR_RESUME = "ImageLoader is paused. Waiting... [%s]";
+ private static final String LOG_RESUME_AFTER_PAUSE = ".. Resume loading [%s]";
+ private static final String LOG_DELAY_BEFORE_LOADING = "Delay %d ms before loading... [%s]";
+ private static final String LOG_START_DISPLAY_IMAGE_TASK = "Start display image task [%s]";
+ private static final String LOG_WAITING_FOR_IMAGE_LOADED = "Image already is loading. Waiting... [%s]";
+ private static final String LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING = "...Get cached bitmap from memory after waiting. [%s]";
+ private static final String LOG_LOAD_IMAGE_FROM_NETWORK = "Load image from network [%s]";
+ private static final String LOG_LOAD_IMAGE_FROM_DISK_CACHE = "Load image from disk cache [%s]";
+ private static final String LOG_RESIZE_CACHED_IMAGE_FILE = "Resize image in disk cache [%s]";
+ private static final String LOG_PREPROCESS_IMAGE = "PreProcess image before caching in memory [%s]";
+ private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]";
+ private static final String LOG_CACHE_IMAGE_IN_MEMORY = "Cache image in memory [%s]";
+ private static final String LOG_CACHE_IMAGE_ON_DISK = "Cache image on disk [%s]";
+ private static final String LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK = "Process image before cache on disk [%s]";
+ private static final String LOG_TASK_CANCELLED_IMAGEAWARE_REUSED = "ImageAware is reused for another image. Task is cancelled. [%s]";
+ private static final String LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED = "ImageAware was collected by GC. Task is cancelled. [%s]";
+ private static final String LOG_TASK_INTERRUPTED = "Task was interrupted [%s]";
+
+ private static final String ERROR_NO_IMAGE_STREAM = "No stream for image [%s]";
+ private static final String ERROR_PRE_PROCESSOR_NULL = "Pre-processor returned null [%s]";
+ private static final String ERROR_POST_PROCESSOR_NULL = "Post-processor returned null [%s]";
+ private static final String ERROR_PROCESSOR_FOR_DISK_CACHE_NULL = "Bitmap processor for disk cache returned null [%s]";
+
+ private final ImageLoaderEngine engine;
+ private final ImageLoadingInfo imageLoadingInfo;
+ private final Handler handler;
+
+ // Helper references
+ private final ImageLoaderConfiguration configuration;
+ private final ImageDownloader downloader;
+ private final ImageDownloader networkDeniedDownloader;
+ private final ImageDownloader slowNetworkDownloader;
+ private final ImageDecoder decoder;
+ final String uri;
+ private final String memoryCacheKey;
+ final ImageAware imageAware;
+ private final ImageSize targetSize;
+ final DisplayImageOptions options;
+ final ImageLoadingListener listener;
+ final ImageLoadingProgressListener progressListener;
+ private final boolean syncLoading;
+
+ // State vars
+ private LoadedFrom loadedFrom = LoadedFrom.NETWORK;
+
+ public LoadAndDisplayImageTask(ImageLoaderEngine engine, ImageLoadingInfo imageLoadingInfo, Handler handler) {
+ this.engine = engine;
+ this.imageLoadingInfo = imageLoadingInfo;
+ this.handler = handler;
+
+ configuration = engine.configuration;
+ downloader = configuration.downloader;
+ networkDeniedDownloader = configuration.networkDeniedDownloader;
+ slowNetworkDownloader = configuration.slowNetworkDownloader;
+ decoder = configuration.decoder;
+ uri = imageLoadingInfo.uri;
+ memoryCacheKey = imageLoadingInfo.memoryCacheKey;
+ imageAware = imageLoadingInfo.imageAware;
+ targetSize = imageLoadingInfo.targetSize;
+ options = imageLoadingInfo.options;
+ listener = imageLoadingInfo.listener;
+ progressListener = imageLoadingInfo.progressListener;
+ syncLoading = options.isSyncLoading();
+ }
+
+ @Override
+ public void run() {
+ if (waitIfPaused()) return;
+ if (delayIfNeed()) return;
+
+ ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
+ L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
+ if (loadFromUriLock.isLocked()) {
+ L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
+ }
+
+ loadFromUriLock.lock();
+ Bitmap bmp;
+ try {
+ checkTaskNotActual();
+
+ bmp = configuration.memoryCache.get(memoryCacheKey);
+ if (bmp == null || bmp.isRecycled()) {
+ bmp = tryLoadBitmap();
+ if (bmp == null) return; // listener callback already was fired
+
+ checkTaskNotActual();
+ checkTaskInterrupted();
+
+ if (options.shouldPreProcess()) {
+ L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
+ bmp = options.getPreProcessor().process(bmp);
+ if (bmp == null) {
+ L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
+ }
+ }
+
+ if (bmp != null && options.isCacheInMemory()) {
+ L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
+ configuration.memoryCache.put(memoryCacheKey, bmp);
+ }
+ } else {
+ loadedFrom = LoadedFrom.MEMORY_CACHE;
+ L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);
+ }
+
+ if (bmp != null && options.shouldPostProcess()) {
+ L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);
+ bmp = options.getPostProcessor().process(bmp);
+ if (bmp == null) {
+ L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
+ }
+ }
+ checkTaskNotActual();
+ checkTaskInterrupted();
+ } catch (TaskCancelledException e) {
+ fireCancelEvent();
+ return;
+ } finally {
+ loadFromUriLock.unlock();
+ }
+
+ DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
+ runTask(displayBitmapTask, syncLoading, handler, engine);
+ }
+
+ /** @return true - if task should be interrupted; false - otherwise */
+ private boolean waitIfPaused() {
+ AtomicBoolean pause = engine.getPause();
+ if (pause.get()) {
+ synchronized (engine.getPauseLock()) {
+ if (pause.get()) {
+ L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);
+ try {
+ engine.getPauseLock().wait();
+ } catch (InterruptedException e) {
+ L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
+ return true;
+ }
+ L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);
+ }
+ }
+ }
+ return isTaskNotActual();
+ }
+
+ /** @return true - if task should be interrupted; false - otherwise */
+ private boolean delayIfNeed() {
+ if (options.shouldDelayBeforeLoading()) {
+ L.d(LOG_DELAY_BEFORE_LOADING, options.getDelayBeforeLoading(), memoryCacheKey);
+ try {
+ Thread.sleep(options.getDelayBeforeLoading());
+ } catch (InterruptedException e) {
+ L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
+ return true;
+ }
+ return isTaskNotActual();
+ }
+ return false;
+ }
+
+ private Bitmap tryLoadBitmap() throws TaskCancelledException {
+ Bitmap bitmap = null;
+ try {
+ File imageFile = configuration.diskCache.get(uri);
+ if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
+ L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
+ loadedFrom = LoadedFrom.DISC_CACHE;
+
+ checkTaskNotActual();
+ bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
+ }
+ if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
+ L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
+ loadedFrom = LoadedFrom.NETWORK;
+
+ String imageUriForDecoding = uri;
+ if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
+ imageFile = configuration.diskCache.get(uri);
+ if (imageFile != null) {
+ imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
+ }
+ }
+
+ checkTaskNotActual();
+ bitmap = decodeImage(imageUriForDecoding);
+
+ if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
+ fireFailEvent(FailType.DECODING_ERROR, null);
+ }
+ }
+ } catch (IllegalStateException e) {
+ fireFailEvent(FailType.NETWORK_DENIED, null);
+ } catch (TaskCancelledException e) {
+ throw e;
+ } catch (IOException e) {
+ L.e(e);
+ fireFailEvent(FailType.IO_ERROR, e);
+ } catch (OutOfMemoryError e) {
+ L.e(e);
+ fireFailEvent(FailType.OUT_OF_MEMORY, e);
+ } catch (Throwable e) {
+ L.e(e);
+ fireFailEvent(FailType.UNKNOWN, e);
+ }
+ return bitmap;
+ }
+
+ private Bitmap decodeImage(String imageUri) throws IOException {
+ ViewScaleType viewScaleType = imageAware.getScaleType();
+ ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
+ getDownloader(), options);
+ return decoder.decode(decodingInfo);
+ }
+
+ /** @return true - if image was downloaded successfully; false - otherwise */
+ private boolean tryCacheImageOnDisk() throws TaskCancelledException {
+ L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
+
+ boolean loaded;
+ try {
+ loaded = downloadImage();
+ if (loaded) {
+ int width = configuration.maxImageWidthForDiskCache;
+ int height = configuration.maxImageHeightForDiskCache;
+ if (width > 0 || height > 0) {
+ L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
+ resizeAndSaveImage(width, height); // TODO : process boolean result
+ }
+ }
+ } catch (IOException e) {
+ L.e(e);
+ loaded = false;
+ }
+ return loaded;
+ }
+
+ private boolean downloadImage() throws IOException {
+ InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
+ if (is == null) {
+ L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
+ return false;
+ } else {
+ try {
+ return configuration.diskCache.save(uri, is, this);
+ } finally {
+ IoUtils.closeSilently(is);
+ }
+ }
+ }
+
+ /** Decodes image file into Bitmap, resize it and save it back */
+ private boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException {
+ // Decode image file, compress and re-save it
+ boolean saved = false;
+ File targetFile = configuration.diskCache.get(uri);
+ if (targetFile != null && targetFile.exists()) {
+ ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
+ DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options)
+ .imageScaleType(ImageScaleType.IN_SAMPLE_INT).build();
+ ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey,
+ Scheme.FILE.wrap(targetFile.getAbsolutePath()), uri, targetImageSize, ViewScaleType.FIT_INSIDE,
+ getDownloader(), specialOptions);
+ Bitmap bmp = decoder.decode(decodingInfo);
+ if (bmp != null && configuration.processorForDiskCache != null) {
+ L.d(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK, memoryCacheKey);
+ bmp = configuration.processorForDiskCache.process(bmp);
+ if (bmp == null) {
+ L.e(ERROR_PROCESSOR_FOR_DISK_CACHE_NULL, memoryCacheKey);
+ }
+ }
+ if (bmp != null) {
+ saved = configuration.diskCache.save(uri, bmp);
+ bmp.recycle();
+ }
+ }
+ return saved;
+ }
+
+ @Override
+ public boolean onBytesCopied(int current, int total) {
+ return syncLoading || fireProgressEvent(current, total);
+ }
+
+ /** @return true - if loading should be continued; false - if loading should be interrupted */
+ private boolean fireProgressEvent(final int current, final int total) {
+ if (isTaskInterrupted() || isTaskNotActual()) return false;
+ if (progressListener != null) {
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ progressListener.onProgressUpdate(uri, imageAware.getWrappedView(), current, total);
+ }
+ };
+ runTask(r, false, handler, engine);
+ }
+ return true;
+ }
+
+ private void fireFailEvent(final FailType failType, final Throwable failCause) {
+ if (syncLoading || isTaskInterrupted() || isTaskNotActual()) return;
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ if (options.shouldShowImageOnFail()) {
+ imageAware.setImageDrawable(options.getImageOnFail(configuration.resources));
+ }
+ listener.onLoadingFailed(uri, imageAware.getWrappedView(), new FailReason(failType, failCause));
+ }
+ };
+ runTask(r, false, handler, engine);
+ }
+
+ private void fireCancelEvent() {
+ if (syncLoading || isTaskInterrupted()) return;
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ listener.onLoadingCancelled(uri, imageAware.getWrappedView());
+ }
+ };
+ runTask(r, false, handler, engine);
+ }
+
+ private ImageDownloader getDownloader() {
+ ImageDownloader d;
+ if (engine.isNetworkDenied()) {
+ d = networkDeniedDownloader;
+ } else if (engine.isSlowNetwork()) {
+ d = slowNetworkDownloader;
+ } else {
+ d = downloader;
+ }
+ return d;
+ }
+
+ /**
+ * @throws TaskCancelledException if task is not actual (target ImageAware is collected by GC or the image URI of
+ * this task doesn't match to image URI which is actual for current ImageAware at
+ * this moment)
+ */
+ private void checkTaskNotActual() throws TaskCancelledException {
+ checkViewCollected();
+ checkViewReused();
+ }
+
+ /**
+ * @return true - if task is not actual (target ImageAware is collected by GC or the image URI of this task
+ * doesn't match to image URI which is actual for current ImageAware at this moment)); false - otherwise
+ */
+ private boolean isTaskNotActual() {
+ return isViewCollected() || isViewReused();
+ }
+
+ /** @throws TaskCancelledException if target ImageAware is collected */
+ private void checkViewCollected() throws TaskCancelledException {
+ if (isViewCollected()) {
+ throw new TaskCancelledException();
+ }
+ }
+
+ /** @return true - if target ImageAware is collected by GC; false - otherwise */
+ private boolean isViewCollected() {
+ if (imageAware.isCollected()) {
+ L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
+ return true;
+ }
+ return false;
+ }
+
+ /** @throws TaskCancelledException if target ImageAware is collected by GC */
+ private void checkViewReused() throws TaskCancelledException {
+ if (isViewReused()) {
+ throw new TaskCancelledException();
+ }
+ }
+
+ /** @return true - if current ImageAware is reused for displaying another image; false - otherwise */
+ private boolean isViewReused() {
+ String currentCacheKey = engine.getLoadingUriForView(imageAware);
+ // Check whether memory cache key (image URI) for current ImageAware is actual.
+ // If ImageAware is reused for another task then current task should be cancelled.
+ boolean imageAwareWasReused = !memoryCacheKey.equals(currentCacheKey);
+ if (imageAwareWasReused) {
+ L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
+ return true;
+ }
+ return false;
+ }
+
+ /** @throws TaskCancelledException if current task was interrupted */
+ private void checkTaskInterrupted() throws TaskCancelledException {
+ if (isTaskInterrupted()) {
+ throw new TaskCancelledException();
+ }
+ }
+
+ /** @return true - if current task was interrupted; false - otherwise */
+ private boolean isTaskInterrupted() {
+ if (Thread.interrupted()) {
+ L.d(LOG_TASK_INTERRUPTED, memoryCacheKey);
+ return true;
+ }
+ return false;
+ }
+
+ String getLoadingUri() {
+ return uri;
+ }
+
+ static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
+ if (sync) {
+ r.run();
+ } else if (handler == null) {
+ engine.fireCallback(r);
+ } else {
+ handler.post(r);
+ }
+ }
+
+ /**
+ * Exceptions for case when task is cancelled (thread is interrupted, image view is reused for another task, view is
+ * collected by GC).
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.9.1
+ */
+ class TaskCancelledException extends Exception {
+ }
+}
diff --git a/library/src/com/nostra13/universalimageloader/core/ProcessAndDisplayImageTask.java b/library/src/main/java/com/nostra13/universalimageloader/core/ProcessAndDisplayImageTask.java
similarity index 77%
rename from library/src/com/nostra13/universalimageloader/core/ProcessAndDisplayImageTask.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/ProcessAndDisplayImageTask.java
index c2a0a4531..d09dc5a3a 100644
--- a/library/src/com/nostra13/universalimageloader/core/ProcessAndDisplayImageTask.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/ProcessAndDisplayImageTask.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.8.0
*/
-class ProcessAndDisplayImageTask implements Runnable {
+final class ProcessAndDisplayImageTask implements Runnable {
private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]";
@@ -38,7 +38,8 @@ class ProcessAndDisplayImageTask implements Runnable {
private final ImageLoadingInfo imageLoadingInfo;
private final Handler handler;
- public ProcessAndDisplayImageTask(ImageLoaderEngine engine, Bitmap bitmap, ImageLoadingInfo imageLoadingInfo, Handler handler) {
+ public ProcessAndDisplayImageTask(ImageLoaderEngine engine, Bitmap bitmap, ImageLoadingInfo imageLoadingInfo,
+ Handler handler) {
this.engine = engine;
this.bitmap = bitmap;
this.imageLoadingInfo = imageLoadingInfo;
@@ -47,9 +48,12 @@ public ProcessAndDisplayImageTask(ImageLoaderEngine engine, Bitmap bitmap, Image
@Override
public void run() {
- if (engine.configuration.writeLogs) L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);
+ L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);
+
BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
- final Bitmap processedBitmap = processor.process(bitmap);
- handler.post(new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine, LoadedFrom.MEMORY_CACHE));
+ Bitmap processedBitmap = processor.process(bitmap);
+ DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
+ LoadedFrom.MEMORY_CACHE);
+ LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}
}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/assist/ContentLengthInputStream.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/ContentLengthInputStream.java
new file mode 100644
index 000000000..316a52d06
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/assist/ContentLengthInputStream.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright 2013-2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core.assist;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Decorator for {@link java.io.InputStream InputStream}. Provides possibility to return defined stream length by
+ * {@link #available()} method.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com), Mariotaku
+ * @since 1.9.1
+ */
+public class ContentLengthInputStream extends InputStream {
+
+ private final InputStream stream;
+ private final int length;
+
+ public ContentLengthInputStream(InputStream stream, int length) {
+ this.stream = stream;
+ this.length = length;
+ }
+
+ @Override
+ public int available() {
+ return length;
+ }
+
+ @Override
+ public void close() throws IOException {
+ stream.close();
+ }
+
+ @Override
+ public void mark(int readLimit) {
+ stream.mark(readLimit);
+ }
+
+ @Override
+ public int read() throws IOException {
+ return stream.read();
+ }
+
+ @Override
+ public int read(byte[] buffer) throws IOException {
+ return stream.read(buffer);
+ }
+
+ @Override
+ public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ return stream.read(buffer, byteOffset, byteCount);
+ }
+
+ @Override
+ public void reset() throws IOException {
+ stream.reset();
+ }
+
+ @Override
+ public long skip(long byteCount) throws IOException {
+ return stream.skip(byteCount);
+ }
+
+ @Override
+ public boolean markSupported() {
+ return stream.markSupported();
+ }
+}
\ No newline at end of file
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/FailReason.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/FailReason.java
similarity index 97%
rename from library/src/com/nostra13/universalimageloader/core/assist/FailReason.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/FailReason.java
index 7f525fc8b..061acffe1 100644
--- a/library/src/com/nostra13/universalimageloader/core/assist/FailReason.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/assist/FailReason.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,7 +54,7 @@ public static enum FailType {
DECODING_ERROR,
/**
* {@linkplain com.nostra13.universalimageloader.core.ImageLoader#denyNetworkDownloads(boolean) Network
- * downloads are denied} and requested image wasn't cached in disc cache before.
+ * downloads are denied} and requested image wasn't cached in disk cache before.
*/
NETWORK_DENIED,
/** Not enough memory to create needed Bitmap for image */
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/FlushedInputStream.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/FlushedInputStream.java
similarity index 100%
rename from library/src/com/nostra13/universalimageloader/core/assist/FlushedInputStream.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/FlushedInputStream.java
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/ImageScaleType.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/ImageScaleType.java
similarity index 86%
rename from library/src/com/nostra13/universalimageloader/core/assist/ImageScaleType.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/ImageScaleType.java
index 219ba02f5..4dfd894e0 100644
--- a/library/src/com/nostra13/universalimageloader/core/assist/ImageScaleType.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/assist/ImageScaleType.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,15 @@
public enum ImageScaleType {
/** Image won't be scaled */
NONE,
+ /**
+ * Image will be scaled down only if image size is greater than
+ * {@linkplain javax.microedition.khronos.opengles.GL10#GL_MAX_TEXTURE_SIZE maximum acceptable texture size}.
+ * Usually it's 2048x2048.
+ * If Bitmap is expected to display than it must not exceed this size (otherwise you'll get the exception
+ * "OpenGLRenderer: Bitmap too large to be uploaded into a texture".
+ * Image will be subsampled in an integer number of times (1, 2, 3, ...) to maximum texture size of device.
+ */
+ NONE_SAFE,
/**
* Image will be reduces 2-fold until next reduce step make image smaller target size.
* It's fast type and it's preferable for usage in lists/grids/galleries (and other
@@ -52,7 +61,6 @@ public enum ImageScaleType {
* Pros: Requires more memory in one time for creation of result Bitmap.
*/
EXACTLY,
-
/**
* Image will scaled exactly to target size (scaled width or height or both will be equal to target size; depends on
* {@linkplain android.widget.ImageView.ScaleType ImageView's scale type}). Use it if memory economy is critically
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/ImageSize.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/ImageSize.java
similarity index 100%
rename from library/src/com/nostra13/universalimageloader/core/assist/ImageSize.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/ImageSize.java
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/LoadedFrom.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/LoadedFrom.java
similarity index 79%
rename from library/src/com/nostra13/universalimageloader/core/assist/LoadedFrom.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/LoadedFrom.java
index 6889ce45a..adb061208 100644
--- a/library/src/com/nostra13/universalimageloader/core/assist/LoadedFrom.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/assist/LoadedFrom.java
@@ -1,10 +1,10 @@
-package com.nostra13.universalimageloader.core.assist;
-
-/**
- * Source image loaded from.
- *
- * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- */
-public enum LoadedFrom {
- NETWORK, DISC_CACHE, MEMORY_CACHE
-}
+package com.nostra13.universalimageloader.core.assist;
+
+/**
+ * Source image loaded from.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ */
+public enum LoadedFrom {
+ NETWORK, DISC_CACHE, MEMORY_CACHE
+}
\ No newline at end of file
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/QueueProcessingType.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/QueueProcessingType.java
similarity index 100%
rename from library/src/com/nostra13/universalimageloader/core/assist/QueueProcessingType.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/QueueProcessingType.java
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/ViewScaleType.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/ViewScaleType.java
similarity index 94%
rename from library/src/com/nostra13/universalimageloader/core/assist/ViewScaleType.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/ViewScaleType.java
index 192096ba4..c618cfba8 100644
--- a/library/src/com/nostra13/universalimageloader/core/assist/ViewScaleType.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/assist/ViewScaleType.java
@@ -26,8 +26,8 @@
*/
public enum ViewScaleType {
/**
- * Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the
- * image will be equal to or less the corresponding dimension of the view.
+ * Scale the image uniformly (maintain the image's aspect ratio) so that at least one dimension (width or height) of
+ * the image will be equal to or less the corresponding dimension of the view.
*/
FIT_INSIDE,
/**
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/deque/BlockingDeque.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/BlockingDeque.java
similarity index 63%
rename from library/src/com/nostra13/universalimageloader/core/assist/deque/BlockingDeque.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/BlockingDeque.java
index 571eb6ca5..8e693c61b 100644
--- a/library/src/com/nostra13/universalimageloader/core/assist/deque/BlockingDeque.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/BlockingDeque.java
@@ -1,616 +1,617 @@
-/*
- * Written by Doug Lea with assistance from members of JCP JSR-166
- * Expert Group and released to the public domain, as explained at
- * http://creativecommons.org/licenses/publicdomain
- */
-
-package com.nostra13.universalimageloader.core.assist.deque;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A {@link Deque} that additionally supports blocking operations that wait
- * for the deque to become non-empty when retrieving an element, and wait for
- * space to become available in the deque when storing an element.
- *
- * BlockingDeque methods come in four forms, with different ways
- * of handling operations that cannot be satisfied immediately, but may be
- * satisfied at some point in the future:
- * one throws an exception, the second returns a special value (either
- * null or false, depending on the operation), the third
- * blocks the current thread indefinitely until the operation can succeed,
- * and the fourth blocks for only a given maximum time limit before giving
- * up. These methods are summarized in the following table:
- *
- *
- *
- *
- * First Element (Head) |
- *
- *
- * |
- * Throws exception |
- * Special value |
- * Blocks |
- * Times out |
- *
- *
- * Insert |
- * {@link #addFirst addFirst(e)} |
- * {@link #offerFirst offerFirst(e)} |
- * {@link #putFirst putFirst(e)} |
- * {@link #offerFirst offerFirst(e, time, unit)} |
- *
- *
- * Remove |
- * {@link #removeFirst removeFirst()} |
- * {@link #pollFirst pollFirst()} |
- * {@link #takeFirst takeFirst()} |
- * {@link #pollFirst(long, TimeUnit) pollFirst(time, unit)} |
- *
- *
- * Examine |
- * {@link #getFirst getFirst()} |
- * {@link #peekFirst peekFirst()} |
- * not applicable |
- * not applicable |
- *
- *
- * Last Element (Tail) |
- *
- *
- * |
- * Throws exception |
- * Special value |
- * Blocks |
- * Times out |
- *
- *
- * Insert |
- * {@link #addLast addLast(e)} |
- * {@link #offerLast offerLast(e)} |
- * {@link #putLast putLast(e)} |
- * {@link #offerLast offerLast(e, time, unit)} |
- *
- *
- * Remove |
- * {@link #removeLast() removeLast()} |
- * {@link #pollLast() pollLast()} |
- * {@link #takeLast takeLast()} |
- * {@link #pollLast(long, TimeUnit) pollLast(time, unit)} |
- *
- *
- * Examine |
- * {@link #getLast getLast()} |
- * {@link #peekLast peekLast()} |
- * not applicable |
- * not applicable |
- *
- *
- *
- * Like any {@link BlockingQueue}, a BlockingDeque is thread safe,
- * does not permit null elements, and may (or may not) be
- * capacity-constrained.
- *
- *
A BlockingDeque implementation may be used directly as a FIFO
- * BlockingQueue. The methods inherited from the
- * BlockingQueue interface are precisely equivalent to
- * BlockingDeque methods as indicated in the following table:
- *
- *
- *
- *
- * BlockingQueue Method |
- * Equivalent BlockingDeque Method |
- *
- *
- * Insert |
- *
- *
- * {@link #add add(e)} |
- * {@link #addLast addLast(e)} |
- *
- *
- * {@link #offer offer(e)} |
- * {@link #offerLast offerLast(e)} |
- *
- *
- * {@link #put put(e)} |
- * {@link #putLast putLast(e)} |
- *
- *
- * {@link #offer offer(e, time, unit)} |
- * {@link #offerLast offerLast(e, time, unit)} |
- *
- *
- * Remove |
- *
- *
- * {@link #remove() remove()} |
- * {@link #removeFirst() removeFirst()} |
- *
- *
- * {@link #poll() poll()} |
- * {@link #pollFirst() pollFirst()} |
- *
- *
- * {@link #take() take()} |
- * {@link #takeFirst() takeFirst()} |
- *
- *
- * {@link #poll(long, TimeUnit) poll(time, unit)} |
- * {@link #pollFirst(long, TimeUnit) pollFirst(time, unit)} |
- *
- *
- * Examine |
- *
- *
- * {@link #element() element()} |
- * {@link #getFirst() getFirst()} |
- *
- *
- * {@link #peek() peek()} |
- * {@link #peekFirst() peekFirst()} |
- *
- *
- *
- * Memory consistency effects: As with other concurrent
- * collections, actions in a thread prior to placing an object into a
- * {@code BlockingDeque}
- * happen-before
- * actions subsequent to the access or removal of that element from
- * the {@code BlockingDeque} in another thread.
- *
- *
This interface is a member of the
- *
- * Java Collections Framework.
- *
- * @since 1.6
- * @author Doug Lea
- * @param the type of elements held in this collection
- */
-public interface BlockingDeque extends BlockingQueue, Deque {
- /*
- * We have "diamond" multiple interface inheritance here, and that
- * introduces ambiguities. Methods might end up with different
- * specs depending on the branch chosen by javadoc. Thus a lot of
- * methods specs here are copied from superinterfaces.
- */
-
- /**
- * Inserts the specified element at the front of this deque if it is
- * possible to do so immediately without violating capacity restrictions,
- * throwing an IllegalStateException if no space is currently
- * available. When using a capacity-restricted deque, it is generally
- * preferable to use {@link #offerFirst offerFirst}.
- *
- * @param e the element to add
- * @throws IllegalStateException {@inheritDoc}
- * @throws ClassCastException {@inheritDoc}
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException {@inheritDoc}
- */
- void addFirst(E e);
-
- /**
- * Inserts the specified element at the end of this deque if it is
- * possible to do so immediately without violating capacity restrictions,
- * throwing an IllegalStateException if no space is currently
- * available. When using a capacity-restricted deque, it is generally
- * preferable to use {@link #offerLast offerLast}.
- *
- * @param e the element to add
- * @throws IllegalStateException {@inheritDoc}
- * @throws ClassCastException {@inheritDoc}
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException {@inheritDoc}
- */
- void addLast(E e);
-
- /**
- * Inserts the specified element at the front of this deque if it is
- * possible to do so immediately without violating capacity restrictions,
- * returning true upon success and false if no space is
- * currently available.
- * When using a capacity-restricted deque, this method is generally
- * preferable to the {@link #addFirst addFirst} method, which can
- * fail to insert an element only by throwing an exception.
- *
- * @param e the element to add
- * @throws ClassCastException {@inheritDoc}
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException {@inheritDoc}
- */
- boolean offerFirst(E e);
-
- /**
- * Inserts the specified element at the end of this deque if it is
- * possible to do so immediately without violating capacity restrictions,
- * returning true upon success and false if no space is
- * currently available.
- * When using a capacity-restricted deque, this method is generally
- * preferable to the {@link #addLast addLast} method, which can
- * fail to insert an element only by throwing an exception.
- *
- * @param e the element to add
- * @throws ClassCastException {@inheritDoc}
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException {@inheritDoc}
- */
- boolean offerLast(E e);
-
- /**
- * Inserts the specified element at the front of this deque,
- * waiting if necessary for space to become available.
- *
- * @param e the element to add
- * @throws InterruptedException if interrupted while waiting
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- void putFirst(E e) throws InterruptedException;
-
- /**
- * Inserts the specified element at the end of this deque,
- * waiting if necessary for space to become available.
- *
- * @param e the element to add
- * @throws InterruptedException if interrupted while waiting
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- void putLast(E e) throws InterruptedException;
-
- /**
- * Inserts the specified element at the front of this deque,
- * waiting up to the specified wait time if necessary for space to
- * become available.
- *
- * @param e the element to add
- * @param timeout how long to wait before giving up, in units of
- * unit
- * @param unit a TimeUnit determining how to interpret the
- * timeout parameter
- * @return true if successful, or false if
- * the specified waiting time elapses before space is available
- * @throws InterruptedException if interrupted while waiting
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- boolean offerFirst(E e, long timeout, TimeUnit unit)
- throws InterruptedException;
-
- /**
- * Inserts the specified element at the end of this deque,
- * waiting up to the specified wait time if necessary for space to
- * become available.
- *
- * @param e the element to add
- * @param timeout how long to wait before giving up, in units of
- * unit
- * @param unit a TimeUnit determining how to interpret the
- * timeout parameter
- * @return true if successful, or false if
- * the specified waiting time elapses before space is available
- * @throws InterruptedException if interrupted while waiting
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- boolean offerLast(E e, long timeout, TimeUnit unit)
- throws InterruptedException;
-
- /**
- * Retrieves and removes the first element of this deque, waiting
- * if necessary until an element becomes available.
- *
- * @return the head of this deque
- * @throws InterruptedException if interrupted while waiting
- */
- E takeFirst() throws InterruptedException;
-
- /**
- * Retrieves and removes the last element of this deque, waiting
- * if necessary until an element becomes available.
- *
- * @return the tail of this deque
- * @throws InterruptedException if interrupted while waiting
- */
- E takeLast() throws InterruptedException;
-
- /**
- * Retrieves and removes the first element of this deque, waiting
- * up to the specified wait time if necessary for an element to
- * become available.
- *
- * @param timeout how long to wait before giving up, in units of
- * unit
- * @param unit a TimeUnit determining how to interpret the
- * timeout parameter
- * @return the head of this deque, or null if the specified
- * waiting time elapses before an element is available
- * @throws InterruptedException if interrupted while waiting
- */
- E pollFirst(long timeout, TimeUnit unit)
- throws InterruptedException;
-
- /**
- * Retrieves and removes the last element of this deque, waiting
- * up to the specified wait time if necessary for an element to
- * become available.
- *
- * @param timeout how long to wait before giving up, in units of
- * unit
- * @param unit a TimeUnit determining how to interpret the
- * timeout parameter
- * @return the tail of this deque, or null if the specified
- * waiting time elapses before an element is available
- * @throws InterruptedException if interrupted while waiting
- */
- E pollLast(long timeout, TimeUnit unit)
- throws InterruptedException;
-
- /**
- * Removes the first occurrence of the specified element from this deque.
- * If the deque does not contain the element, it is unchanged.
- * More formally, removes the first element e such that
- * o.equals(e) (if such an element exists).
- * Returns true if this deque contained the specified element
- * (or equivalently, if this deque changed as a result of the call).
- *
- * @param o element to be removed from this deque, if present
- * @return true if an element was removed as a result of this call
- * @throws ClassCastException if the class of the specified element
- * is incompatible with this deque (optional)
- * @throws NullPointerException if the specified element is null (optional)
- */
- boolean removeFirstOccurrence(Object o);
-
- /**
- * Removes the last occurrence of the specified element from this deque.
- * If the deque does not contain the element, it is unchanged.
- * More formally, removes the last element e such that
- * o.equals(e) (if such an element exists).
- * Returns true if this deque contained the specified element
- * (or equivalently, if this deque changed as a result of the call).
- *
- * @param o element to be removed from this deque, if present
- * @return true if an element was removed as a result of this call
- * @throws ClassCastException if the class of the specified element
- * is incompatible with this deque (optional)
- * @throws NullPointerException if the specified element is null (optional)
- */
- boolean removeLastOccurrence(Object o);
-
- // *** BlockingQueue methods ***
-
- /**
- * Inserts the specified element into the queue represented by this deque
- * (in other words, at the tail of this deque) if it is possible to do so
- * immediately without violating capacity restrictions, returning
- * true upon success and throwing an
- * IllegalStateException if no space is currently available.
- * When using a capacity-restricted deque, it is generally preferable to
- * use {@link #offer offer}.
- *
- * This method is equivalent to {@link #addLast addLast}.
- *
- * @param e the element to add
- * @throws IllegalStateException {@inheritDoc}
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- boolean add(E e);
-
- /**
- * Inserts the specified element into the queue represented by this deque
- * (in other words, at the tail of this deque) if it is possible to do so
- * immediately without violating capacity restrictions, returning
- * true upon success and false if no space is currently
- * available. When using a capacity-restricted deque, this method is
- * generally preferable to the {@link #add} method, which can fail to
- * insert an element only by throwing an exception.
- *
- *
This method is equivalent to {@link #offerLast offerLast}.
- *
- * @param e the element to add
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- boolean offer(E e);
-
- /**
- * Inserts the specified element into the queue represented by this deque
- * (in other words, at the tail of this deque), waiting if necessary for
- * space to become available.
- *
- *
This method is equivalent to {@link #putLast putLast}.
- *
- * @param e the element to add
- * @throws InterruptedException {@inheritDoc}
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- void put(E e) throws InterruptedException;
-
- /**
- * Inserts the specified element into the queue represented by this deque
- * (in other words, at the tail of this deque), waiting up to the
- * specified wait time if necessary for space to become available.
- *
- *
This method is equivalent to
- * {@link #offerLast offerLast}.
- *
- * @param e the element to add
- * @return true if the element was added to this deque, else
- * false
- * @throws InterruptedException {@inheritDoc}
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- boolean offer(E e, long timeout, TimeUnit unit)
- throws InterruptedException;
-
- /**
- * Retrieves and removes the head of the queue represented by this deque
- * (in other words, the first element of this deque).
- * This method differs from {@link #poll poll} only in that it
- * throws an exception if this deque is empty.
- *
- *
This method is equivalent to {@link #removeFirst() removeFirst}.
- *
- * @return the head of the queue represented by this deque
- * @throws NoSuchElementException if this deque is empty
- */
- E remove();
-
- /**
- * Retrieves and removes the head of the queue represented by this deque
- * (in other words, the first element of this deque), or returns
- * null if this deque is empty.
- *
- *
This method is equivalent to {@link #pollFirst()}.
- *
- * @return the head of this deque, or null if this deque is empty
- */
- E poll();
-
- /**
- * Retrieves and removes the head of the queue represented by this deque
- * (in other words, the first element of this deque), waiting if
- * necessary until an element becomes available.
- *
- *
This method is equivalent to {@link #takeFirst() takeFirst}.
- *
- * @return the head of this deque
- * @throws InterruptedException if interrupted while waiting
- */
- E take() throws InterruptedException;
-
- /**
- * Retrieves and removes the head of the queue represented by this deque
- * (in other words, the first element of this deque), waiting up to the
- * specified wait time if necessary for an element to become available.
- *
- *
This method is equivalent to
- * {@link #pollFirst(long,TimeUnit) pollFirst}.
- *
- * @return the head of this deque, or null if the
- * specified waiting time elapses before an element is available
- * @throws InterruptedException if interrupted while waiting
- */
- E poll(long timeout, TimeUnit unit)
- throws InterruptedException;
-
- /**
- * Retrieves, but does not remove, the head of the queue represented by
- * this deque (in other words, the first element of this deque).
- * This method differs from {@link #peek peek} only in that it throws an
- * exception if this deque is empty.
- *
- *
This method is equivalent to {@link #getFirst() getFirst}.
- *
- * @return the head of this deque
- * @throws NoSuchElementException if this deque is empty
- */
- E element();
-
- /**
- * Retrieves, but does not remove, the head of the queue represented by
- * this deque (in other words, the first element of this deque), or
- * returns null if this deque is empty.
- *
- *
This method is equivalent to {@link #peekFirst() peekFirst}.
- *
- * @return the head of this deque, or null if this deque is empty
- */
- E peek();
-
- /**
- * Removes the first occurrence of the specified element from this deque.
- * If the deque does not contain the element, it is unchanged.
- * More formally, removes the first element e such that
- * o.equals(e) (if such an element exists).
- * Returns true if this deque contained the specified element
- * (or equivalently, if this deque changed as a result of the call).
- *
- *
This method is equivalent to
- * {@link #removeFirstOccurrence removeFirstOccurrence}.
- *
- * @param o element to be removed from this deque, if present
- * @return true if this deque changed as a result of the call
- * @throws ClassCastException if the class of the specified element
- * is incompatible with this deque (optional)
- * @throws NullPointerException if the specified element is null (optional)
- */
- boolean remove(Object o);
-
- /**
- * Returns true if this deque contains the specified element.
- * More formally, returns true if and only if this deque contains
- * at least one element e such that o.equals(e).
- *
- * @param o object to be checked for containment in this deque
- * @return true if this deque contains the specified element
- * @throws ClassCastException if the class of the specified element
- * is incompatible with this deque (optional)
- * @throws NullPointerException if the specified element is null (optional)
- */
- public boolean contains(Object o);
-
- /**
- * Returns the number of elements in this deque.
- *
- * @return the number of elements in this deque
- */
- public int size();
-
- /**
- * Returns an iterator over the elements in this deque in proper sequence.
- * The elements will be returned in order from first (head) to last (tail).
- *
- * @return an iterator over the elements in this deque in proper sequence
- */
- Iterator iterator();
-
- // *** Stack methods ***
-
- /**
- * Pushes an element onto the stack represented by this deque. In other
- * words, inserts the element at the front of this deque unless it would
- * violate capacity restrictions.
- *
- * This method is equivalent to {@link #addFirst addFirst}.
- *
- * @throws IllegalStateException {@inheritDoc}
- * @throws ClassCastException {@inheritDoc}
- * @throws NullPointerException if the specified element is null
- * @throws IllegalArgumentException {@inheritDoc}
- */
- void push(E e);
-}
+/*
+ * Written by Doug Lea with assistance from members of JCP JSR-166
+ * Expert Group and released to the public domain, as explained at
+ * http://creativecommons.org/licenses/publicdomain
+ */
+
+package com.nostra13.universalimageloader.core.assist.deque;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link Deque} that additionally supports blocking operations that wait
+ * for the deque to become non-empty when retrieving an element, and wait for
+ * space to become available in the deque when storing an element.
+ *
+ * BlockingDeque methods come in four forms, with different ways
+ * of handling operations that cannot be satisfied immediately, but may be
+ * satisfied at some point in the future:
+ * one throws an exception, the second returns a special value (either
+ * null or false, depending on the operation), the third
+ * blocks the current thread indefinitely until the operation can succeed,
+ * and the fourth blocks for only a given maximum time limit before giving
+ * up. These methods are summarized in the following table:
+ *
+ *
+ *
+ *
+ * First Element (Head) |
+ *
+ *
+ * |
+ * Throws exception |
+ * Special value |
+ * Blocks |
+ * Times out |
+ *
+ *
+ * Insert |
+ * {@link #addFirst addFirst(e)} |
+ * {@link #offerFirst offerFirst(e)} |
+ * {@link #putFirst putFirst(e)} |
+ * {@link #offerFirst offerFirst(e, time, unit)} |
+ *
+ *
+ * Remove |
+ * {@link #removeFirst removeFirst()} |
+ * {@link #pollFirst pollFirst()} |
+ * {@link #takeFirst takeFirst()} |
+ * {@link #pollFirst(long, TimeUnit) pollFirst(time, unit)} |
+ *
+ *
+ * Examine |
+ * {@link #getFirst getFirst()} |
+ * {@link #peekFirst peekFirst()} |
+ * not applicable |
+ * not applicable |
+ *
+ *
+ * Last Element (Tail) |
+ *
+ *
+ * |
+ * Throws exception |
+ * Special value |
+ * Blocks |
+ * Times out |
+ *
+ *
+ * Insert |
+ * {@link #addLast addLast(e)} |
+ * {@link #offerLast offerLast(e)} |
+ * {@link #putLast putLast(e)} |
+ * {@link #offerLast offerLast(e, time, unit)} |
+ *
+ *
+ * Remove |
+ * {@link #removeLast() removeLast()} |
+ * {@link #pollLast() pollLast()} |
+ * {@link #takeLast takeLast()} |
+ * {@link #pollLast(long, TimeUnit) pollLast(time, unit)} |
+ *
+ *
+ * Examine |
+ * {@link #getLast getLast()} |
+ * {@link #peekLast peekLast()} |
+ * not applicable |
+ * not applicable |
+ *
+ *
+ *
+ * Like any {@link BlockingQueue}, a BlockingDeque is thread safe,
+ * does not permit null elements, and may (or may not) be
+ * capacity-constrained.
+ *
+ * A BlockingDeque implementation may be used directly as a FIFO
+ * BlockingQueue. The methods inherited from the
+ * BlockingQueue interface are precisely equivalent to
+ * BlockingDeque methods as indicated in the following table:
+ *
+ *
+ *
+ *
+ * BlockingQueue Method |
+ * Equivalent BlockingDeque Method |
+ *
+ *
+ * Insert |
+ *
+ *
+ * {@link #add add(e)} |
+ * {@link #addLast addLast(e)} |
+ *
+ *
+ * {@link #offer offer(e)} |
+ * {@link #offerLast offerLast(e)} |
+ *
+ *
+ * {@link #put put(e)} |
+ * {@link #putLast putLast(e)} |
+ *
+ *
+ * {@link #offer offer(e, time, unit)} |
+ * {@link #offerLast offerLast(e, time, unit)} |
+ *
+ *
+ * Remove |
+ *
+ *
+ * {@link #remove() remove()} |
+ * {@link #removeFirst() removeFirst()} |
+ *
+ *
+ * {@link #poll() poll()} |
+ * {@link #pollFirst() pollFirst()} |
+ *
+ *
+ * {@link #take() take()} |
+ * {@link #takeFirst() takeFirst()} |
+ *
+ *
+ * {@link #poll(long, TimeUnit) poll(time, unit)} |
+ * {@link #pollFirst(long, TimeUnit) pollFirst(time, unit)} |
+ *
+ *
+ * Examine |
+ *
+ *
+ * {@link #element() element()} |
+ * {@link #getFirst() getFirst()} |
+ *
+ *
+ * {@link #peek() peek()} |
+ * {@link #peekFirst() peekFirst()} |
+ *
+ *
+ *
+ * Memory consistency effects: As with other concurrent
+ * collections, actions in a thread prior to placing an object into a
+ * {@code BlockingDeque}
+ * happen-before
+ * actions subsequent to the access or removal of that element from
+ * the {@code BlockingDeque} in another thread.
+ *
+ * This interface is a member of the
+ *
+ * Java Collections Framework.
+ *
+ * @param the type of elements held in this collection
+ * @author Doug Lea
+ * @since 1.6
+ */
+public interface BlockingDeque extends BlockingQueue, Deque {
+ /*
+ * We have "diamond" multiple interface inheritance here, and that
+ * introduces ambiguities. Methods might end up with different
+ * specs depending on the branch chosen by javadoc. Thus a lot of
+ * methods specs here are copied from superinterfaces.
+ */
+
+ /**
+ * Inserts the specified element at the front of this deque if it is
+ * possible to do so immediately without violating capacity restrictions,
+ * throwing an IllegalStateException if no space is currently
+ * available. When using a capacity-restricted deque, it is generally
+ * preferable to use {@link #offerFirst offerFirst}.
+ *
+ * @param e the element to add
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws ClassCastException {@inheritDoc}
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException {@inheritDoc}
+ */
+ void addFirst(E e);
+
+ /**
+ * Inserts the specified element at the end of this deque if it is
+ * possible to do so immediately without violating capacity restrictions,
+ * throwing an IllegalStateException if no space is currently
+ * available. When using a capacity-restricted deque, it is generally
+ * preferable to use {@link #offerLast offerLast}.
+ *
+ * @param e the element to add
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws ClassCastException {@inheritDoc}
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException {@inheritDoc}
+ */
+ void addLast(E e);
+
+ /**
+ * Inserts the specified element at the front of this deque if it is
+ * possible to do so immediately without violating capacity restrictions,
+ * returning true upon success and false if no space is
+ * currently available.
+ * When using a capacity-restricted deque, this method is generally
+ * preferable to the {@link #addFirst addFirst} method, which can
+ * fail to insert an element only by throwing an exception.
+ *
+ * @param e the element to add
+ * @throws ClassCastException {@inheritDoc}
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException {@inheritDoc}
+ */
+ boolean offerFirst(E e);
+
+ /**
+ * Inserts the specified element at the end of this deque if it is
+ * possible to do so immediately without violating capacity restrictions,
+ * returning true upon success and false if no space is
+ * currently available.
+ * When using a capacity-restricted deque, this method is generally
+ * preferable to the {@link #addLast addLast} method, which can
+ * fail to insert an element only by throwing an exception.
+ *
+ * @param e the element to add
+ * @throws ClassCastException {@inheritDoc}
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException {@inheritDoc}
+ */
+ boolean offerLast(E e);
+
+ /**
+ * Inserts the specified element at the front of this deque,
+ * waiting if necessary for space to become available.
+ *
+ * @param e the element to add
+ * @throws InterruptedException if interrupted while waiting
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ void putFirst(E e) throws InterruptedException;
+
+ /**
+ * Inserts the specified element at the end of this deque,
+ * waiting if necessary for space to become available.
+ *
+ * @param e the element to add
+ * @throws InterruptedException if interrupted while waiting
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ void putLast(E e) throws InterruptedException;
+
+ /**
+ * Inserts the specified element at the front of this deque,
+ * waiting up to the specified wait time if necessary for space to
+ * become available.
+ *
+ * @param e the element to add
+ * @param timeout how long to wait before giving up, in units of
+ * unit
+ * @param unit a TimeUnit determining how to interpret the
+ * timeout parameter
+ * @return true if successful, or false if
+ * the specified waiting time elapses before space is available
+ * @throws InterruptedException if interrupted while waiting
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ boolean offerFirst(E e, long timeout, TimeUnit unit)
+ throws InterruptedException;
+
+ /**
+ * Inserts the specified element at the end of this deque,
+ * waiting up to the specified wait time if necessary for space to
+ * become available.
+ *
+ * @param e the element to add
+ * @param timeout how long to wait before giving up, in units of
+ * unit
+ * @param unit a TimeUnit determining how to interpret the
+ * timeout parameter
+ * @return true if successful, or false if
+ * the specified waiting time elapses before space is available
+ * @throws InterruptedException if interrupted while waiting
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ boolean offerLast(E e, long timeout, TimeUnit unit)
+ throws InterruptedException;
+
+ /**
+ * Retrieves and removes the first element of this deque, waiting
+ * if necessary until an element becomes available.
+ *
+ * @return the head of this deque
+ * @throws InterruptedException if interrupted while waiting
+ */
+ E takeFirst() throws InterruptedException;
+
+ /**
+ * Retrieves and removes the last element of this deque, waiting
+ * if necessary until an element becomes available.
+ *
+ * @return the tail of this deque
+ * @throws InterruptedException if interrupted while waiting
+ */
+ E takeLast() throws InterruptedException;
+
+ /**
+ * Retrieves and removes the first element of this deque, waiting
+ * up to the specified wait time if necessary for an element to
+ * become available.
+ *
+ * @param timeout how long to wait before giving up, in units of
+ * unit
+ * @param unit a TimeUnit determining how to interpret the
+ * timeout parameter
+ * @return the head of this deque, or null if the specified
+ * waiting time elapses before an element is available
+ * @throws InterruptedException if interrupted while waiting
+ */
+ E pollFirst(long timeout, TimeUnit unit)
+ throws InterruptedException;
+
+ /**
+ * Retrieves and removes the last element of this deque, waiting
+ * up to the specified wait time if necessary for an element to
+ * become available.
+ *
+ * @param timeout how long to wait before giving up, in units of
+ * unit
+ * @param unit a TimeUnit determining how to interpret the
+ * timeout parameter
+ * @return the tail of this deque, or null if the specified
+ * waiting time elapses before an element is available
+ * @throws InterruptedException if interrupted while waiting
+ */
+ E pollLast(long timeout, TimeUnit unit)
+ throws InterruptedException;
+
+ /**
+ * Removes the first occurrence of the specified element from this deque.
+ * If the deque does not contain the element, it is unchanged.
+ * More formally, removes the first element e such that
+ * o.equals(e) (if such an element exists).
+ * Returns true if this deque contained the specified element
+ * (or equivalently, if this deque changed as a result of the call).
+ *
+ * @param o element to be removed from this deque, if present
+ * @return true if an element was removed as a result of this call
+ * @throws ClassCastException if the class of the specified element
+ * is incompatible with this deque (optional)
+ * @throws NullPointerException if the specified element is null (optional)
+ */
+ boolean removeFirstOccurrence(Object o);
+
+ /**
+ * Removes the last occurrence of the specified element from this deque.
+ * If the deque does not contain the element, it is unchanged.
+ * More formally, removes the last element e such that
+ * o.equals(e) (if such an element exists).
+ * Returns true if this deque contained the specified element
+ * (or equivalently, if this deque changed as a result of the call).
+ *
+ * @param o element to be removed from this deque, if present
+ * @return true if an element was removed as a result of this call
+ * @throws ClassCastException if the class of the specified element
+ * is incompatible with this deque (optional)
+ * @throws NullPointerException if the specified element is null (optional)
+ */
+ boolean removeLastOccurrence(Object o);
+
+ // *** BlockingQueue methods ***
+
+ /**
+ * Inserts the specified element into the queue represented by this deque
+ * (in other words, at the tail of this deque) if it is possible to do so
+ * immediately without violating capacity restrictions, returning
+ * true upon success and throwing an
+ * IllegalStateException if no space is currently available.
+ * When using a capacity-restricted deque, it is generally preferable to
+ * use {@link #offer offer}.
+ *
+ * This method is equivalent to {@link #addLast addLast}.
+ *
+ * @param e the element to add
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ boolean add(E e);
+
+ /**
+ * Inserts the specified element into the queue represented by this deque
+ * (in other words, at the tail of this deque) if it is possible to do so
+ * immediately without violating capacity restrictions, returning
+ * true upon success and false if no space is currently
+ * available. When using a capacity-restricted deque, this method is
+ * generally preferable to the {@link #add} method, which can fail to
+ * insert an element only by throwing an exception.
+ *
+ * This method is equivalent to {@link #offerLast offerLast}.
+ *
+ * @param e the element to add
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ boolean offer(E e);
+
+ /**
+ * Inserts the specified element into the queue represented by this deque
+ * (in other words, at the tail of this deque), waiting if necessary for
+ * space to become available.
+ *
+ * This method is equivalent to {@link #putLast putLast}.
+ *
+ * @param e the element to add
+ * @throws InterruptedException {@inheritDoc}
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ void put(E e) throws InterruptedException;
+
+ /**
+ * Inserts the specified element into the queue represented by this deque
+ * (in other words, at the tail of this deque), waiting up to the
+ * specified wait time if necessary for space to become available.
+ *
+ * This method is equivalent to
+ * {@link #offerLast offerLast}.
+ *
+ * @param e the element to add
+ * @return true if the element was added to this deque, else
+ * false
+ * @throws InterruptedException {@inheritDoc}
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ boolean offer(E e, long timeout, TimeUnit unit)
+ throws InterruptedException;
+
+ /**
+ * Retrieves and removes the head of the queue represented by this deque
+ * (in other words, the first element of this deque).
+ * This method differs from {@link #poll poll} only in that it
+ * throws an exception if this deque is empty.
+ *
+ * This method is equivalent to {@link #removeFirst() removeFirst}.
+ *
+ * @return the head of the queue represented by this deque
+ * @throws NoSuchElementException if this deque is empty
+ */
+ E remove();
+
+ /**
+ * Retrieves and removes the head of the queue represented by this deque
+ * (in other words, the first element of this deque), or returns
+ * null if this deque is empty.
+ *
+ * This method is equivalent to {@link #pollFirst()}.
+ *
+ * @return the head of this deque, or null if this deque is empty
+ */
+ E poll();
+
+ /**
+ * Retrieves and removes the head of the queue represented by this deque
+ * (in other words, the first element of this deque), waiting if
+ * necessary until an element becomes available.
+ *
+ * This method is equivalent to {@link #takeFirst() takeFirst}.
+ *
+ * @return the head of this deque
+ * @throws InterruptedException if interrupted while waiting
+ */
+ E take() throws InterruptedException;
+
+ /**
+ * Retrieves and removes the head of the queue represented by this deque
+ * (in other words, the first element of this deque), waiting up to the
+ * specified wait time if necessary for an element to become available.
+ *
+ * This method is equivalent to
+ * {@link #pollFirst(long, TimeUnit) pollFirst}.
+ *
+ * @return the head of this deque, or null if the
+ * specified waiting time elapses before an element is available
+ * @throws InterruptedException if interrupted while waiting
+ */
+ E poll(long timeout, TimeUnit unit)
+ throws InterruptedException;
+
+ /**
+ * Retrieves, but does not remove, the head of the queue represented by
+ * this deque (in other words, the first element of this deque).
+ * This method differs from {@link #peek peek} only in that it throws an
+ * exception if this deque is empty.
+ *
+ * This method is equivalent to {@link #getFirst() getFirst}.
+ *
+ * @return the head of this deque
+ * @throws NoSuchElementException if this deque is empty
+ */
+ E element();
+
+ /**
+ * Retrieves, but does not remove, the head of the queue represented by
+ * this deque (in other words, the first element of this deque), or
+ * returns null if this deque is empty.
+ *
+ * This method is equivalent to {@link #peekFirst() peekFirst}.
+ *
+ * @return the head of this deque, or null if this deque is empty
+ */
+ E peek();
+
+ /**
+ * Removes the first occurrence of the specified element from this deque.
+ * If the deque does not contain the element, it is unchanged.
+ * More formally, removes the first element e such that
+ * o.equals(e) (if such an element exists).
+ * Returns true if this deque contained the specified element
+ * (or equivalently, if this deque changed as a result of the call).
+ *
+ * This method is equivalent to
+ * {@link #removeFirstOccurrence removeFirstOccurrence}.
+ *
+ * @param o element to be removed from this deque, if present
+ * @return true if this deque changed as a result of the call
+ * @throws ClassCastException if the class of the specified element
+ * is incompatible with this deque (optional)
+ * @throws NullPointerException if the specified element is null (optional)
+ */
+ boolean remove(Object o);
+
+ /**
+ * Returns true if this deque contains the specified element.
+ * More formally, returns true if and only if this deque contains
+ * at least one element e such that o.equals(e).
+ *
+ * @param o object to be checked for containment in this deque
+ * @return true if this deque contains the specified element
+ * @throws ClassCastException if the class of the specified element
+ * is incompatible with this deque (optional)
+ * @throws NullPointerException if the specified element is null (optional)
+ */
+ public boolean contains(Object o);
+
+ /**
+ * Returns the number of elements in this deque.
+ *
+ * @return the number of elements in this deque
+ */
+ public int size();
+
+ /**
+ * Returns an iterator over the elements in this deque in proper sequence.
+ * The elements will be returned in order from first (head) to last (tail).
+ *
+ * @return an iterator over the elements in this deque in proper sequence
+ */
+ Iterator iterator();
+
+ // *** Stack methods ***
+
+ /**
+ * Pushes an element onto the stack represented by this deque. In other
+ * words, inserts the element at the front of this deque unless it would
+ * violate capacity restrictions.
+ *
+ * This method is equivalent to {@link #addFirst addFirst}.
+ *
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws ClassCastException {@inheritDoc}
+ * @throws NullPointerException if the specified element is null
+ * @throws IllegalArgumentException {@inheritDoc}
+ */
+ void push(E e);
+}
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/deque/Deque.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/Deque.java
similarity index 69%
rename from library/src/com/nostra13/universalimageloader/core/assist/deque/Deque.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/Deque.java
index 1b359ffdf..e30665b02 100644
--- a/library/src/com/nostra13/universalimageloader/core/assist/deque/Deque.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/Deque.java
@@ -1,550 +1,550 @@
-/*
- * Written by Doug Lea and Josh Bloch with assistance from members of
- * JCP JSR-166 Expert Group and released to the public domain, as explained
- * at http://creativecommons.org/licenses/publicdomain
- */
-
-package com.nostra13.universalimageloader.core.assist.deque;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Queue;
-import java.util.Stack;
-
-/**
- * A linear collection that supports element insertion and removal at
- * both ends. The name deque is short for "double ended queue"
- * and is usually pronounced "deck". Most Deque
- * implementations place no fixed limits on the number of elements
- * they may contain, but this interface supports capacity-restricted
- * deques as well as those with no fixed size limit.
- *
- *
This interface defines methods to access the elements at both
- * ends of the deque. Methods are provided to insert, remove, and
- * examine the element. Each of these methods exists in two forms:
- * one throws an exception if the operation fails, the other returns a
- * special value (either null or false, depending on
- * the operation). The latter form of the insert operation is
- * designed specifically for use with capacity-restricted
- * Deque implementations; in most implementations, insert
- * operations cannot fail.
- *
- *
The twelve methods described above are summarized in the
- * following table:
- *
- *
- *
- *
- * |
- * First Element (Head) |
- * Last Element (Tail) |
- *
- *
- * |
- * Throws exception |
- * Special value |
- * Throws exception |
- * Special value |
- *
- *
- * Insert |
- * {@link #addFirst addFirst(e)} |
- * {@link #offerFirst offerFirst(e)} |
- * {@link #addLast addLast(e)} |
- * {@link #offerLast offerLast(e)} |
- *
- *
- * Remove |
- * {@link #removeFirst removeFirst()} |
- * {@link #pollFirst pollFirst()} |
- * {@link #removeLast removeLast()} |
- * {@link #pollLast pollLast()} |
- *
- *
- * Examine |
- * {@link #getFirst getFirst()} |
- * {@link #peekFirst peekFirst()} |
- * {@link #getLast getLast()} |
- * {@link #peekLast peekLast()} |
- *
- *
- *
- * This interface extends the {@link Queue} interface. When a deque is
- * used as a queue, FIFO (First-In-First-Out) behavior results. Elements are
- * added at the end of the deque and removed from the beginning. The methods
- * inherited from the Queue interface are precisely equivalent to
- * Deque methods as indicated in the following table:
- *
- *
- *
- *
- * Queue Method |
- * Equivalent Deque Method |
- *
- *
- * {@link java.util.Queue#add add(e)} |
- * {@link #addLast addLast(e)} |
- *
- *
- * {@link java.util.Queue#offer offer(e)} |
- * {@link #offerLast offerLast(e)} |
- *
- *
- * {@link java.util.Queue#remove remove()} |
- * {@link #removeFirst removeFirst()} |
- *
- *
- * {@link java.util.Queue#poll poll()} |
- * {@link #pollFirst pollFirst()} |
- *
- *
- * {@link java.util.Queue#element element()} |
- * {@link #getFirst getFirst()} |
- *
- *
- * {@link java.util.Queue#peek peek()} |
- * {@link #peek peekFirst()} |
- *
- *
- *
- * Deques can also be used as LIFO (Last-In-First-Out) stacks. This
- * interface should be used in preference to the legacy {@link Stack} class.
- * When a deque is used as a stack, elements are pushed and popped from the
- * beginning of the deque. Stack methods are precisely equivalent to
- * Deque methods as indicated in the table below:
- *
- *
- *
- *
- * Stack Method |
- * Equivalent Deque Method |
- *
- *
- * {@link #push push(e)} |
- * {@link #addFirst addFirst(e)} |
- *
- *
- * {@link #pop pop()} |
- * {@link #removeFirst removeFirst()} |
- *
- *
- * {@link #peek peek()} |
- * {@link #peekFirst peekFirst()} |
- *
- *
- *
- * Note that the {@link #peek peek} method works equally well when
- * a deque is used as a queue or a stack; in either case, elements are
- * drawn from the beginning of the deque.
- *
- *
This interface provides two methods to remove interior
- * elements, {@link #removeFirstOccurrence removeFirstOccurrence} and
- * {@link #removeLastOccurrence removeLastOccurrence}.
- *
- *
Unlike the {@link List} interface, this interface does not
- * provide support for indexed access to elements.
- *
- *
While Deque implementations are not strictly required
- * to prohibit the insertion of null elements, they are strongly
- * encouraged to do so. Users of any Deque implementations
- * that do allow null elements are strongly encouraged not to
- * take advantage of the ability to insert nulls. This is so because
- * null is used as a special return value by various methods
- * to indicated that the deque is empty.
- *
- *
Deque implementations generally do not define
- * element-based versions of the equals and hashCode
- * methods, but instead inherit the identity-based versions from class
- * Object.
- *
- * @author Doug Lea
- * @author Josh Bloch
- * @since 1.6
- * @param the type of elements held in this collection
- */
-
-public interface Deque extends Queue {
- /**
- * Inserts the specified element at the front of this deque if it is
- * possible to do so immediately without violating capacity restrictions.
- * When using a capacity-restricted deque, it is generally preferable to
- * use method {@link #offerFirst}.
- *
- * @param e the element to add
- * @throws IllegalStateException if the element cannot be added at this
- * time due to capacity restrictions
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- void addFirst(E e);
-
- /**
- * Inserts the specified element at the end of this deque if it is
- * possible to do so immediately without violating capacity restrictions.
- * When using a capacity-restricted deque, it is generally preferable to
- * use method {@link #offerLast}.
- *
- * This method is equivalent to {@link #add}.
- *
- * @param e the element to add
- * @throws IllegalStateException if the element cannot be added at this
- * time due to capacity restrictions
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- void addLast(E e);
-
- /**
- * Inserts the specified element at the front of this deque unless it would
- * violate capacity restrictions. When using a capacity-restricted deque,
- * this method is generally preferable to the {@link #addFirst} method,
- * which can fail to insert an element only by throwing an exception.
- *
- * @param e the element to add
- * @return true if the element was added to this deque, else
- * false
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- boolean offerFirst(E e);
-
- /**
- * Inserts the specified element at the end of this deque unless it would
- * violate capacity restrictions. When using a capacity-restricted deque,
- * this method is generally preferable to the {@link #addLast} method,
- * which can fail to insert an element only by throwing an exception.
- *
- * @param e the element to add
- * @return true if the element was added to this deque, else
- * false
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- boolean offerLast(E e);
-
- /**
- * Retrieves and removes the first element of this deque. This method
- * differs from {@link #pollFirst pollFirst} only in that it throws an
- * exception if this deque is empty.
- *
- * @return the head of this deque
- * @throws NoSuchElementException if this deque is empty
- */
- E removeFirst();
-
- /**
- * Retrieves and removes the last element of this deque. This method
- * differs from {@link #pollLast pollLast} only in that it throws an
- * exception if this deque is empty.
- *
- * @return the tail of this deque
- * @throws NoSuchElementException if this deque is empty
- */
- E removeLast();
-
- /**
- * Retrieves and removes the first element of this deque,
- * or returns null if this deque is empty.
- *
- * @return the head of this deque, or null if this deque is empty
- */
- E pollFirst();
-
- /**
- * Retrieves and removes the last element of this deque,
- * or returns null if this deque is empty.
- *
- * @return the tail of this deque, or null if this deque is empty
- */
- E pollLast();
-
- /**
- * Retrieves, but does not remove, the first element of this deque.
- *
- * This method differs from {@link #peekFirst peekFirst} only in that it
- * throws an exception if this deque is empty.
- *
- * @return the head of this deque
- * @throws NoSuchElementException if this deque is empty
- */
- E getFirst();
-
- /**
- * Retrieves, but does not remove, the last element of this deque.
- * This method differs from {@link #peekLast peekLast} only in that it
- * throws an exception if this deque is empty.
- *
- * @return the tail of this deque
- * @throws NoSuchElementException if this deque is empty
- */
- E getLast();
-
- /**
- * Retrieves, but does not remove, the first element of this deque,
- * or returns null if this deque is empty.
- *
- * @return the head of this deque, or null if this deque is empty
- */
- E peekFirst();
-
- /**
- * Retrieves, but does not remove, the last element of this deque,
- * or returns null if this deque is empty.
- *
- * @return the tail of this deque, or null if this deque is empty
- */
- E peekLast();
-
- /**
- * Removes the first occurrence of the specified element from this deque.
- * If the deque does not contain the element, it is unchanged.
- * More formally, removes the first element e such that
- * (o==null ? e==null : o.equals(e))
- * (if such an element exists).
- * Returns true if this deque contained the specified element
- * (or equivalently, if this deque changed as a result of the call).
- *
- * @param o element to be removed from this deque, if present
- * @return true if an element was removed as a result of this call
- * @throws ClassCastException if the class of the specified element
- * is incompatible with this deque (optional)
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements (optional)
- */
- boolean removeFirstOccurrence(Object o);
-
- /**
- * Removes the last occurrence of the specified element from this deque.
- * If the deque does not contain the element, it is unchanged.
- * More formally, removes the last element e such that
- * (o==null ? e==null : o.equals(e))
- * (if such an element exists).
- * Returns true if this deque contained the specified element
- * (or equivalently, if this deque changed as a result of the call).
- *
- * @param o element to be removed from this deque, if present
- * @return true if an element was removed as a result of this call
- * @throws ClassCastException if the class of the specified element
- * is incompatible with this deque (optional)
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements (optional)
- */
- boolean removeLastOccurrence(Object o);
-
- // *** Queue methods ***
-
- /**
- * Inserts the specified element into the queue represented by this deque
- * (in other words, at the tail of this deque) if it is possible to do so
- * immediately without violating capacity restrictions, returning
- * true upon success and throwing an
- * IllegalStateException if no space is currently available.
- * When using a capacity-restricted deque, it is generally preferable to
- * use {@link #offer offer}.
- *
- *
This method is equivalent to {@link #addLast}.
- *
- * @param e the element to add
- * @return true (as specified by {@link Collection#add})
- * @throws IllegalStateException if the element cannot be added at this
- * time due to capacity restrictions
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- boolean add(E e);
-
- /**
- * Inserts the specified element into the queue represented by this deque
- * (in other words, at the tail of this deque) if it is possible to do so
- * immediately without violating capacity restrictions, returning
- * true upon success and false if no space is currently
- * available. When using a capacity-restricted deque, this method is
- * generally preferable to the {@link #add} method, which can fail to
- * insert an element only by throwing an exception.
- *
- *
This method is equivalent to {@link #offerLast}.
- *
- * @param e the element to add
- * @return true if the element was added to this deque, else
- * false
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- boolean offer(E e);
-
- /**
- * Retrieves and removes the head of the queue represented by this deque
- * (in other words, the first element of this deque).
- * This method differs from {@link #poll poll} only in that it throws an
- * exception if this deque is empty.
- *
- *
This method is equivalent to {@link #removeFirst()}.
- *
- * @return the head of the queue represented by this deque
- * @throws NoSuchElementException if this deque is empty
- */
- E remove();
-
- /**
- * Retrieves and removes the head of the queue represented by this deque
- * (in other words, the first element of this deque), or returns
- * null if this deque is empty.
- *
- *
This method is equivalent to {@link #pollFirst()}.
- *
- * @return the first element of this deque, or null if
- * this deque is empty
- */
- E poll();
-
- /**
- * Retrieves, but does not remove, the head of the queue represented by
- * this deque (in other words, the first element of this deque).
- * This method differs from {@link #peek peek} only in that it throws an
- * exception if this deque is empty.
- *
- *
This method is equivalent to {@link #getFirst()}.
- *
- * @return the head of the queue represented by this deque
- * @throws NoSuchElementException if this deque is empty
- */
- E element();
-
- /**
- * Retrieves, but does not remove, the head of the queue represented by
- * this deque (in other words, the first element of this deque), or
- * returns null if this deque is empty.
- *
- *
This method is equivalent to {@link #peekFirst()}.
- *
- * @return the head of the queue represented by this deque, or
- * null if this deque is empty
- */
- E peek();
-
-
- // *** Stack methods ***
-
- /**
- * Pushes an element onto the stack represented by this deque (in other
- * words, at the head of this deque) if it is possible to do so
- * immediately without violating capacity restrictions, returning
- * true upon success and throwing an
- * IllegalStateException if no space is currently available.
- *
- *
This method is equivalent to {@link #addFirst}.
- *
- * @param e the element to push
- * @throws IllegalStateException if the element cannot be added at this
- * time due to capacity restrictions
- * @throws ClassCastException if the class of the specified element
- * prevents it from being added to this deque
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements
- * @throws IllegalArgumentException if some property of the specified
- * element prevents it from being added to this deque
- */
- void push(E e);
-
- /**
- * Pops an element from the stack represented by this deque. In other
- * words, removes and returns the first element of this deque.
- *
- *
This method is equivalent to {@link #removeFirst()}.
- *
- * @return the element at the front of this deque (which is the top
- * of the stack represented by this deque)
- * @throws NoSuchElementException if this deque is empty
- */
- E pop();
-
-
- // *** Collection methods ***
-
- /**
- * Removes the first occurrence of the specified element from this deque.
- * If the deque does not contain the element, it is unchanged.
- * More formally, removes the first element e such that
- * (o==null ? e==null : o.equals(e))
- * (if such an element exists).
- * Returns true if this deque contained the specified element
- * (or equivalently, if this deque changed as a result of the call).
- *
- *
This method is equivalent to {@link #removeFirstOccurrence}.
- *
- * @param o element to be removed from this deque, if present
- * @return true if an element was removed as a result of this call
- * @throws ClassCastException if the class of the specified element
- * is incompatible with this deque (optional)
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements (optional)
- */
- boolean remove(Object o);
-
- /**
- * Returns true if this deque contains the specified element.
- * More formally, returns true if and only if this deque contains
- * at least one element e such that
- * (o==null ? e==null : o.equals(e)).
- *
- * @param o element whose presence in this deque is to be tested
- * @return true if this deque contains the specified element
- * @throws ClassCastException if the type of the specified element
- * is incompatible with this deque (optional)
- * @throws NullPointerException if the specified element is null and this
- * deque does not permit null elements (optional)
- */
- boolean contains(Object o);
-
- /**
- * Returns the number of elements in this deque.
- *
- * @return the number of elements in this deque
- */
- public int size();
-
- /**
- * Returns an iterator over the elements in this deque in proper sequence.
- * The elements will be returned in order from first (head) to last (tail).
- *
- * @return an iterator over the elements in this deque in proper sequence
- */
- Iterator iterator();
-
- /**
- * Returns an iterator over the elements in this deque in reverse
- * sequential order. The elements will be returned in order from
- * last (tail) to first (head).
- *
- * @return an iterator over the elements in this deque in reverse
- * sequence
- */
- Iterator descendingIterator();
-
-}
+/*
+ * Written by Doug Lea and Josh Bloch with assistance from members of
+ * JCP JSR-166 Expert Group and released to the public domain, as explained
+ * at http://creativecommons.org/licenses/publicdomain
+ */
+
+package com.nostra13.universalimageloader.core.assist.deque;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+import java.util.Stack;
+
+/**
+ * A linear collection that supports element insertion and removal at
+ * both ends. The name deque is short for "double ended queue"
+ * and is usually pronounced "deck". Most Deque
+ * implementations place no fixed limits on the number of elements
+ * they may contain, but this interface supports capacity-restricted
+ * deques as well as those with no fixed size limit.
+ *
+ * This interface defines methods to access the elements at both
+ * ends of the deque. Methods are provided to insert, remove, and
+ * examine the element. Each of these methods exists in two forms:
+ * one throws an exception if the operation fails, the other returns a
+ * special value (either null or false, depending on
+ * the operation). The latter form of the insert operation is
+ * designed specifically for use with capacity-restricted
+ * Deque implementations; in most implementations, insert
+ * operations cannot fail.
+ *
+ * The twelve methods described above are summarized in the
+ * following table:
+ *
+ *
+ *
+ *
+ * |
+ * First Element (Head) |
+ * Last Element (Tail) |
+ *
+ *
+ * |
+ * Throws exception |
+ * Special value |
+ * Throws exception |
+ * Special value |
+ *
+ *
+ * Insert |
+ * {@link #addFirst addFirst(e)} |
+ * {@link #offerFirst offerFirst(e)} |
+ * {@link #addLast addLast(e)} |
+ * {@link #offerLast offerLast(e)} |
+ *
+ *
+ * Remove |
+ * {@link #removeFirst removeFirst()} |
+ * {@link #pollFirst pollFirst()} |
+ * {@link #removeLast removeLast()} |
+ * {@link #pollLast pollLast()} |
+ *
+ *
+ * Examine |
+ * {@link #getFirst getFirst()} |
+ * {@link #peekFirst peekFirst()} |
+ * {@link #getLast getLast()} |
+ * {@link #peekLast peekLast()} |
+ *
+ *
+ *
+ * This interface extends the {@link Queue} interface. When a deque is
+ * used as a queue, FIFO (First-In-First-Out) behavior results. Elements are
+ * added at the end of the deque and removed from the beginning. The methods
+ * inherited from the Queue interface are precisely equivalent to
+ * Deque methods as indicated in the following table:
+ *
+ *
+ *
+ *
+ * Queue Method |
+ * Equivalent Deque Method |
+ *
+ *
+ * {@link java.util.Queue#add add(e)} |
+ * {@link #addLast addLast(e)} |
+ *
+ *
+ * {@link java.util.Queue#offer offer(e)} |
+ * {@link #offerLast offerLast(e)} |
+ *
+ *
+ * {@link java.util.Queue#remove remove()} |
+ * {@link #removeFirst removeFirst()} |
+ *
+ *
+ * {@link java.util.Queue#poll poll()} |
+ * {@link #pollFirst pollFirst()} |
+ *
+ *
+ * {@link java.util.Queue#element element()} |
+ * {@link #getFirst getFirst()} |
+ *
+ *
+ * {@link java.util.Queue#peek peek()} |
+ * {@link #peek peekFirst()} |
+ *
+ *
+ *
+ * Deques can also be used as LIFO (Last-In-First-Out) stacks. This
+ * interface should be used in preference to the legacy {@link Stack} class.
+ * When a deque is used as a stack, elements are pushed and popped from the
+ * beginning of the deque. Stack methods are precisely equivalent to
+ * Deque methods as indicated in the table below:
+ *
+ *
+ *
+ *
+ * Stack Method |
+ * Equivalent Deque Method |
+ *
+ *
+ * {@link #push push(e)} |
+ * {@link #addFirst addFirst(e)} |
+ *
+ *
+ * {@link #pop pop()} |
+ * {@link #removeFirst removeFirst()} |
+ *
+ *
+ * {@link #peek peek()} |
+ * {@link #peekFirst peekFirst()} |
+ *
+ *
+ *
+ * Note that the {@link #peek peek} method works equally well when
+ * a deque is used as a queue or a stack; in either case, elements are
+ * drawn from the beginning of the deque.
+ *
+ * This interface provides two methods to remove interior
+ * elements, {@link #removeFirstOccurrence removeFirstOccurrence} and
+ * {@link #removeLastOccurrence removeLastOccurrence}.
+ *
+ * Unlike the {@link List} interface, this interface does not
+ * provide support for indexed access to elements.
+ *
+ * While Deque implementations are not strictly required
+ * to prohibit the insertion of null elements, they are strongly
+ * encouraged to do so. Users of any Deque implementations
+ * that do allow null elements are strongly encouraged not to
+ * take advantage of the ability to insert nulls. This is so because
+ * null is used as a special return value by various methods
+ * to indicated that the deque is empty.
+ *
+ * Deque implementations generally do not define
+ * element-based versions of the equals and hashCode
+ * methods, but instead inherit the identity-based versions from class
+ * Object.
+ *
+ * @param the type of elements held in this collection
+ * @author Doug Lea
+ * @author Josh Bloch
+ * @since 1.6
+ */
+
+public interface Deque extends Queue {
+ /**
+ * Inserts the specified element at the front of this deque if it is
+ * possible to do so immediately without violating capacity restrictions.
+ * When using a capacity-restricted deque, it is generally preferable to
+ * use method {@link #offerFirst}.
+ *
+ * @param e the element to add
+ * @throws IllegalStateException if the element cannot be added at this
+ * time due to capacity restrictions
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ void addFirst(E e);
+
+ /**
+ * Inserts the specified element at the end of this deque if it is
+ * possible to do so immediately without violating capacity restrictions.
+ * When using a capacity-restricted deque, it is generally preferable to
+ * use method {@link #offerLast}.
+ *
+ * This method is equivalent to {@link #add}.
+ *
+ * @param e the element to add
+ * @throws IllegalStateException if the element cannot be added at this
+ * time due to capacity restrictions
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ void addLast(E e);
+
+ /**
+ * Inserts the specified element at the front of this deque unless it would
+ * violate capacity restrictions. When using a capacity-restricted deque,
+ * this method is generally preferable to the {@link #addFirst} method,
+ * which can fail to insert an element only by throwing an exception.
+ *
+ * @param e the element to add
+ * @return true if the element was added to this deque, else
+ * false
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ boolean offerFirst(E e);
+
+ /**
+ * Inserts the specified element at the end of this deque unless it would
+ * violate capacity restrictions. When using a capacity-restricted deque,
+ * this method is generally preferable to the {@link #addLast} method,
+ * which can fail to insert an element only by throwing an exception.
+ *
+ * @param e the element to add
+ * @return true if the element was added to this deque, else
+ * false
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ boolean offerLast(E e);
+
+ /**
+ * Retrieves and removes the first element of this deque. This method
+ * differs from {@link #pollFirst pollFirst} only in that it throws an
+ * exception if this deque is empty.
+ *
+ * @return the head of this deque
+ * @throws NoSuchElementException if this deque is empty
+ */
+ E removeFirst();
+
+ /**
+ * Retrieves and removes the last element of this deque. This method
+ * differs from {@link #pollLast pollLast} only in that it throws an
+ * exception if this deque is empty.
+ *
+ * @return the tail of this deque
+ * @throws NoSuchElementException if this deque is empty
+ */
+ E removeLast();
+
+ /**
+ * Retrieves and removes the first element of this deque,
+ * or returns null if this deque is empty.
+ *
+ * @return the head of this deque, or null if this deque is empty
+ */
+ E pollFirst();
+
+ /**
+ * Retrieves and removes the last element of this deque,
+ * or returns null if this deque is empty.
+ *
+ * @return the tail of this deque, or null if this deque is empty
+ */
+ E pollLast();
+
+ /**
+ * Retrieves, but does not remove, the first element of this deque.
+ *
+ * This method differs from {@link #peekFirst peekFirst} only in that it
+ * throws an exception if this deque is empty.
+ *
+ * @return the head of this deque
+ * @throws NoSuchElementException if this deque is empty
+ */
+ E getFirst();
+
+ /**
+ * Retrieves, but does not remove, the last element of this deque.
+ * This method differs from {@link #peekLast peekLast} only in that it
+ * throws an exception if this deque is empty.
+ *
+ * @return the tail of this deque
+ * @throws NoSuchElementException if this deque is empty
+ */
+ E getLast();
+
+ /**
+ * Retrieves, but does not remove, the first element of this deque,
+ * or returns null if this deque is empty.
+ *
+ * @return the head of this deque, or null if this deque is empty
+ */
+ E peekFirst();
+
+ /**
+ * Retrieves, but does not remove, the last element of this deque,
+ * or returns null if this deque is empty.
+ *
+ * @return the tail of this deque, or null if this deque is empty
+ */
+ E peekLast();
+
+ /**
+ * Removes the first occurrence of the specified element from this deque.
+ * If the deque does not contain the element, it is unchanged.
+ * More formally, removes the first element e such that
+ * (o==null ? e==null : o.equals(e))
+ * (if such an element exists).
+ * Returns true if this deque contained the specified element
+ * (or equivalently, if this deque changed as a result of the call).
+ *
+ * @param o element to be removed from this deque, if present
+ * @return true if an element was removed as a result of this call
+ * @throws ClassCastException if the class of the specified element
+ * is incompatible with this deque (optional)
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements (optional)
+ */
+ boolean removeFirstOccurrence(Object o);
+
+ /**
+ * Removes the last occurrence of the specified element from this deque.
+ * If the deque does not contain the element, it is unchanged.
+ * More formally, removes the last element e such that
+ * (o==null ? e==null : o.equals(e))
+ * (if such an element exists).
+ * Returns true if this deque contained the specified element
+ * (or equivalently, if this deque changed as a result of the call).
+ *
+ * @param o element to be removed from this deque, if present
+ * @return true if an element was removed as a result of this call
+ * @throws ClassCastException if the class of the specified element
+ * is incompatible with this deque (optional)
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements (optional)
+ */
+ boolean removeLastOccurrence(Object o);
+
+ // *** Queue methods ***
+
+ /**
+ * Inserts the specified element into the queue represented by this deque
+ * (in other words, at the tail of this deque) if it is possible to do so
+ * immediately without violating capacity restrictions, returning
+ * true upon success and throwing an
+ * IllegalStateException if no space is currently available.
+ * When using a capacity-restricted deque, it is generally preferable to
+ * use {@link #offer offer}.
+ *
+ * This method is equivalent to {@link #addLast}.
+ *
+ * @param e the element to add
+ * @return true (as specified by {@link Collection#add})
+ * @throws IllegalStateException if the element cannot be added at this
+ * time due to capacity restrictions
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ boolean add(E e);
+
+ /**
+ * Inserts the specified element into the queue represented by this deque
+ * (in other words, at the tail of this deque) if it is possible to do so
+ * immediately without violating capacity restrictions, returning
+ * true upon success and false if no space is currently
+ * available. When using a capacity-restricted deque, this method is
+ * generally preferable to the {@link #add} method, which can fail to
+ * insert an element only by throwing an exception.
+ *
+ * This method is equivalent to {@link #offerLast}.
+ *
+ * @param e the element to add
+ * @return true if the element was added to this deque, else
+ * false
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ boolean offer(E e);
+
+ /**
+ * Retrieves and removes the head of the queue represented by this deque
+ * (in other words, the first element of this deque).
+ * This method differs from {@link #poll poll} only in that it throws an
+ * exception if this deque is empty.
+ *
+ * This method is equivalent to {@link #removeFirst()}.
+ *
+ * @return the head of the queue represented by this deque
+ * @throws NoSuchElementException if this deque is empty
+ */
+ E remove();
+
+ /**
+ * Retrieves and removes the head of the queue represented by this deque
+ * (in other words, the first element of this deque), or returns
+ * null if this deque is empty.
+ *
+ * This method is equivalent to {@link #pollFirst()}.
+ *
+ * @return the first element of this deque, or null if
+ * this deque is empty
+ */
+ E poll();
+
+ /**
+ * Retrieves, but does not remove, the head of the queue represented by
+ * this deque (in other words, the first element of this deque).
+ * This method differs from {@link #peek peek} only in that it throws an
+ * exception if this deque is empty.
+ *
+ * This method is equivalent to {@link #getFirst()}.
+ *
+ * @return the head of the queue represented by this deque
+ * @throws NoSuchElementException if this deque is empty
+ */
+ E element();
+
+ /**
+ * Retrieves, but does not remove, the head of the queue represented by
+ * this deque (in other words, the first element of this deque), or
+ * returns null if this deque is empty.
+ *
+ * This method is equivalent to {@link #peekFirst()}.
+ *
+ * @return the head of the queue represented by this deque, or
+ * null if this deque is empty
+ */
+ E peek();
+
+
+ // *** Stack methods ***
+
+ /**
+ * Pushes an element onto the stack represented by this deque (in other
+ * words, at the head of this deque) if it is possible to do so
+ * immediately without violating capacity restrictions, returning
+ * true upon success and throwing an
+ * IllegalStateException if no space is currently available.
+ *
+ * This method is equivalent to {@link #addFirst}.
+ *
+ * @param e the element to push
+ * @throws IllegalStateException if the element cannot be added at this
+ * time due to capacity restrictions
+ * @throws ClassCastException if the class of the specified element
+ * prevents it from being added to this deque
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements
+ * @throws IllegalArgumentException if some property of the specified
+ * element prevents it from being added to this deque
+ */
+ void push(E e);
+
+ /**
+ * Pops an element from the stack represented by this deque. In other
+ * words, removes and returns the first element of this deque.
+ *
+ * This method is equivalent to {@link #removeFirst()}.
+ *
+ * @return the element at the front of this deque (which is the top
+ * of the stack represented by this deque)
+ * @throws NoSuchElementException if this deque is empty
+ */
+ E pop();
+
+
+ // *** Collection methods ***
+
+ /**
+ * Removes the first occurrence of the specified element from this deque.
+ * If the deque does not contain the element, it is unchanged.
+ * More formally, removes the first element e such that
+ * (o==null ? e==null : o.equals(e))
+ * (if such an element exists).
+ * Returns true if this deque contained the specified element
+ * (or equivalently, if this deque changed as a result of the call).
+ *
+ * This method is equivalent to {@link #removeFirstOccurrence}.
+ *
+ * @param o element to be removed from this deque, if present
+ * @return true if an element was removed as a result of this call
+ * @throws ClassCastException if the class of the specified element
+ * is incompatible with this deque (optional)
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements (optional)
+ */
+ boolean remove(Object o);
+
+ /**
+ * Returns true if this deque contains the specified element.
+ * More formally, returns true if and only if this deque contains
+ * at least one element e such that
+ * (o==null ? e==null : o.equals(e)).
+ *
+ * @param o element whose presence in this deque is to be tested
+ * @return true if this deque contains the specified element
+ * @throws ClassCastException if the type of the specified element
+ * is incompatible with this deque (optional)
+ * @throws NullPointerException if the specified element is null and this
+ * deque does not permit null elements (optional)
+ */
+ boolean contains(Object o);
+
+ /**
+ * Returns the number of elements in this deque.
+ *
+ * @return the number of elements in this deque
+ */
+ public int size();
+
+ /**
+ * Returns an iterator over the elements in this deque in proper sequence.
+ * The elements will be returned in order from first (head) to last (tail).
+ *
+ * @return an iterator over the elements in this deque in proper sequence
+ */
+ Iterator iterator();
+
+ /**
+ * Returns an iterator over the elements in this deque in reverse
+ * sequential order. The elements will be returned in order from
+ * last (tail) to first (head).
+ *
+ * @return an iterator over the elements in this deque in reverse
+ * sequence
+ */
+ Iterator descendingIterator();
+
+}
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/deque/LIFOLinkedBlockingDeque.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/LIFOLinkedBlockingDeque.java
similarity index 100%
rename from library/src/com/nostra13/universalimageloader/core/assist/deque/LIFOLinkedBlockingDeque.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/LIFOLinkedBlockingDeque.java
diff --git a/library/src/com/nostra13/universalimageloader/core/assist/deque/LinkedBlockingDeque.java b/library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/LinkedBlockingDeque.java
similarity index 91%
rename from library/src/com/nostra13/universalimageloader/core/assist/deque/LinkedBlockingDeque.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/LinkedBlockingDeque.java
index 75d6994fa..9b24c4766 100644
--- a/library/src/com/nostra13/universalimageloader/core/assist/deque/LinkedBlockingDeque.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/assist/deque/LinkedBlockingDeque.java
@@ -1,1169 +1,1197 @@
-/*
- * Written by Doug Lea with assistance from members of JCP JSR-166
- * Expert Group and released to the public domain, as explained at
- * http://creativecommons.org/licenses/publicdomain
- */
-
-package com.nostra13.universalimageloader.core.assist.deque;
-
-import java.util.AbstractQueue;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * An optionally-bounded {@linkplain BlockingDeque blocking deque} based on
- * linked nodes.
- *
- * The optional capacity bound constructor argument serves as a
- * way to prevent excessive expansion. The capacity, if unspecified,
- * is equal to {@link Integer#MAX_VALUE}. Linked nodes are
- * dynamically created upon each insertion unless this would bring the
- * deque above capacity.
- *
- *
Most operations run in constant time (ignoring time spent
- * blocking). Exceptions include {@link #remove(Object) remove},
- * {@link #removeFirstOccurrence removeFirstOccurrence}, {@link
- * #removeLastOccurrence removeLastOccurrence}, {@link #contains
- * contains}, {@link #iterator iterator.remove()}, and the bulk
- * operations, all of which run in linear time.
- *
- *
This class and its iterator implement all of the
- * optional methods of the {@link Collection} and {@link
- * Iterator} interfaces.
- *
- *
This class is a member of the
- *
- * Java Collections Framework.
- *
- * @since 1.6
- * @author Doug Lea
- * @param the type of elements held in this collection
- */
-public class LinkedBlockingDeque
- extends AbstractQueue
- implements BlockingDeque, java.io.Serializable {
-
- /*
- * Implemented as a simple doubly-linked list protected by a
- * single lock and using conditions to manage blocking.
- *
- * To implement weakly consistent iterators, it appears we need to
- * keep all Nodes GC-reachable from a predecessor dequeued Node.
- * That would cause two problems:
- * - allow a rogue Iterator to cause unbounded memory retention
- * - cause cross-generational linking of old Nodes to new Nodes if
- * a Node was tenured while live, which generational GCs have a
- * hard time dealing with, causing repeated major collections.
- * However, only non-deleted Nodes need to be reachable from
- * dequeued Nodes, and reachability does not necessarily have to
- * be of the kind understood by the GC. We use the trick of
- * linking a Node that has just been dequeued to itself. Such a
- * self-link implicitly means to jump to "first" (for next links)
- * or "last" (for prev links).
- */
-
- /*
- * We have "diamond" multiple interface/abstract class inheritance
- * here, and that introduces ambiguities. Often we want the
- * BlockingDeque javadoc combined with the AbstractQueue
- * implementation, so a lot of method specs are duplicated here.
- */
-
- private static final long serialVersionUID = -387911632671998426L;
-
- /** Doubly-linked list node class */
- static final class Node {
- /**
- * The item, or null if this node has been removed.
- */
- E item;
-
- /**
- * One of:
- * - the real predecessor Node
- * - this Node, meaning the predecessor is tail
- * - null, meaning there is no predecessor
- */
- Node prev;
-
- /**
- * One of:
- * - the real successor Node
- * - this Node, meaning the successor is head
- * - null, meaning there is no successor
- */
- Node next;
-
- Node(E x) {
- item = x;
- }
- }
-
- /**
- * Pointer to first node.
- * Invariant: (first == null && last == null) ||
- * (first.prev == null && first.item != null)
- */
- transient Node first;
-
- /**
- * Pointer to last node.
- * Invariant: (first == null && last == null) ||
- * (last.next == null && last.item != null)
- */
- transient Node last;
-
- /** Number of items in the deque */
- private transient int count;
-
- /** Maximum number of items in the deque */
- private final int capacity;
-
- /** Main lock guarding all access */
- final ReentrantLock lock = new ReentrantLock();
-
- /** Condition for waiting takes */
- private final Condition notEmpty = lock.newCondition();
-
- /** Condition for waiting puts */
- private final Condition notFull = lock.newCondition();
-
- /**
- * Creates a {@code LinkedBlockingDeque} with a capacity of
- * {@link Integer#MAX_VALUE}.
- */
- public LinkedBlockingDeque() {
- this(Integer.MAX_VALUE);
- }
-
- /**
- * Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity.
- *
- * @param capacity the capacity of this deque
- * @throws IllegalArgumentException if {@code capacity} is less than 1
- */
- public LinkedBlockingDeque(int capacity) {
- if (capacity <= 0) throw new IllegalArgumentException();
- this.capacity = capacity;
- }
-
- /**
- * Creates a {@code LinkedBlockingDeque} with a capacity of
- * {@link Integer#MAX_VALUE}, initially containing the elements of
- * the given collection, added in traversal order of the
- * collection's iterator.
- *
- * @param c the collection of elements to initially contain
- * @throws NullPointerException if the specified collection or any
- * of its elements are null
- */
- public LinkedBlockingDeque(Collection extends E> c) {
- this(Integer.MAX_VALUE);
- final ReentrantLock lock = this.lock;
- lock.lock(); // Never contended, but necessary for visibility
- try {
- for (E e : c) {
- if (e == null)
- throw new NullPointerException();
- if (!linkLast(new Node(e)))
- throw new IllegalStateException("Deque full");
- }
- } finally {
- lock.unlock();
- }
- }
-
-
- // Basic linking and unlinking operations, called only while holding lock
-
- /**
- * Links node as first element, or returns false if full.
- */
- private boolean linkFirst(Node node) {
- // assert lock.isHeldByCurrentThread();
- if (count >= capacity)
- return false;
- Node f = first;
- node.next = f;
- first = node;
- if (last == null)
- last = node;
- else
- f.prev = node;
- ++count;
- notEmpty.signal();
- return true;
- }
-
- /**
- * Links node as last element, or returns false if full.
- */
- private boolean linkLast(Node node) {
- // assert lock.isHeldByCurrentThread();
- if (count >= capacity)
- return false;
- Node l = last;
- node.prev = l;
- last = node;
- if (first == null)
- first = node;
- else
- l.next = node;
- ++count;
- notEmpty.signal();
- return true;
- }
-
- /**
- * Removes and returns first element, or null if empty.
- */
- private E unlinkFirst() {
- // assert lock.isHeldByCurrentThread();
- Node f = first;
- if (f == null)
- return null;
- Node n = f.next;
- E item = f.item;
- f.item = null;
- f.next = f; // help GC
- first = n;
- if (n == null)
- last = null;
- else
- n.prev = null;
- --count;
- notFull.signal();
- return item;
- }
-
- /**
- * Removes and returns last element, or null if empty.
- */
- private E unlinkLast() {
- // assert lock.isHeldByCurrentThread();
- Node l = last;
- if (l == null)
- return null;
- Node p = l.prev;
- E item = l.item;
- l.item = null;
- l.prev = l; // help GC
- last = p;
- if (p == null)
- first = null;
- else
- p.next = null;
- --count;
- notFull.signal();
- return item;
- }
-
- /**
- * Unlinks x.
- */
- void unlink(Node x) {
- // assert lock.isHeldByCurrentThread();
- Node p = x.prev;
- Node n = x.next;
- if (p == null) {
- unlinkFirst();
- } else if (n == null) {
- unlinkLast();
- } else {
- p.next = n;
- n.prev = p;
- x.item = null;
- // Don't mess with x's links. They may still be in use by
- // an iterator.
- --count;
- notFull.signal();
- }
- }
-
- // BlockingDeque methods
-
- /**
- * @throws IllegalStateException {@inheritDoc}
- * @throws NullPointerException {@inheritDoc}
- */
- public void addFirst(E e) {
- if (!offerFirst(e))
- throw new IllegalStateException("Deque full");
- }
-
- /**
- * @throws IllegalStateException {@inheritDoc}
- * @throws NullPointerException {@inheritDoc}
- */
- public void addLast(E e) {
- if (!offerLast(e))
- throw new IllegalStateException("Deque full");
- }
-
- /**
- * @throws NullPointerException {@inheritDoc}
- */
- public boolean offerFirst(E e) {
- if (e == null) throw new NullPointerException();
- Node node = new Node(e);
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- return linkFirst(node);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * @throws NullPointerException {@inheritDoc}
- */
- public boolean offerLast(E e) {
- if (e == null) throw new NullPointerException();
- Node node = new Node(e);
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- return linkLast(node);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * @throws NullPointerException {@inheritDoc}
- * @throws InterruptedException {@inheritDoc}
- */
- public void putFirst(E e) throws InterruptedException {
- if (e == null) throw new NullPointerException();
- Node node = new Node(e);
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- while (!linkFirst(node))
- notFull.await();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * @throws NullPointerException {@inheritDoc}
- * @throws InterruptedException {@inheritDoc}
- */
- public void putLast(E e) throws InterruptedException {
- if (e == null) throw new NullPointerException();
- Node node = new Node(e);
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- while (!linkLast(node))
- notFull.await();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * @throws NullPointerException {@inheritDoc}
- * @throws InterruptedException {@inheritDoc}
- */
- public boolean offerFirst(E e, long timeout, TimeUnit unit)
- throws InterruptedException {
- if (e == null) throw new NullPointerException();
- Node node = new Node(e);
- long nanos = unit.toNanos(timeout);
- final ReentrantLock lock = this.lock;
- lock.lockInterruptibly();
- try {
- while (!linkFirst(node)) {
- if (nanos <= 0)
- return false;
- nanos = notFull.awaitNanos(nanos);
- }
- return true;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * @throws NullPointerException {@inheritDoc}
- * @throws InterruptedException {@inheritDoc}
- */
- public boolean offerLast(E e, long timeout, TimeUnit unit)
- throws InterruptedException {
- if (e == null) throw new NullPointerException();
- Node node = new Node(e);
- long nanos = unit.toNanos(timeout);
- final ReentrantLock lock = this.lock;
- lock.lockInterruptibly();
- try {
- while (!linkLast(node)) {
- if (nanos <= 0)
- return false;
- nanos = notFull.awaitNanos(nanos);
- }
- return true;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * @throws NoSuchElementException {@inheritDoc}
- */
- public E removeFirst() {
- E x = pollFirst();
- if (x == null) throw new NoSuchElementException();
- return x;
- }
-
- /**
- * @throws NoSuchElementException {@inheritDoc}
- */
- public E removeLast() {
- E x = pollLast();
- if (x == null) throw new NoSuchElementException();
- return x;
- }
-
- public E pollFirst() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- return unlinkFirst();
- } finally {
- lock.unlock();
- }
- }
-
- public E pollLast() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- return unlinkLast();
- } finally {
- lock.unlock();
- }
- }
-
- public E takeFirst() throws InterruptedException {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- E x;
- while ( (x = unlinkFirst()) == null)
- notEmpty.await();
- return x;
- } finally {
- lock.unlock();
- }
- }
-
- public E takeLast() throws InterruptedException {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- E x;
- while ( (x = unlinkLast()) == null)
- notEmpty.await();
- return x;
- } finally {
- lock.unlock();
- }
- }
-
- public E pollFirst(long timeout, TimeUnit unit)
- throws InterruptedException {
- long nanos = unit.toNanos(timeout);
- final ReentrantLock lock = this.lock;
- lock.lockInterruptibly();
- try {
- E x;
- while ( (x = unlinkFirst()) == null) {
- if (nanos <= 0)
- return null;
- nanos = notEmpty.awaitNanos(nanos);
- }
- return x;
- } finally {
- lock.unlock();
- }
- }
-
- public E pollLast(long timeout, TimeUnit unit)
- throws InterruptedException {
- long nanos = unit.toNanos(timeout);
- final ReentrantLock lock = this.lock;
- lock.lockInterruptibly();
- try {
- E x;
- while ( (x = unlinkLast()) == null) {
- if (nanos <= 0)
- return null;
- nanos = notEmpty.awaitNanos(nanos);
- }
- return x;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * @throws NoSuchElementException {@inheritDoc}
- */
- public E getFirst() {
- E x = peekFirst();
- if (x == null) throw new NoSuchElementException();
- return x;
- }
-
- /**
- * @throws NoSuchElementException {@inheritDoc}
- */
- public E getLast() {
- E x = peekLast();
- if (x == null) throw new NoSuchElementException();
- return x;
- }
-
- public E peekFirst() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- return (first == null) ? null : first.item;
- } finally {
- lock.unlock();
- }
- }
-
- public E peekLast() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- return (last == null) ? null : last.item;
- } finally {
- lock.unlock();
- }
- }
-
- public boolean removeFirstOccurrence(Object o) {
- if (o == null) return false;
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- for (Node p = first; p != null; p = p.next) {
- if (o.equals(p.item)) {
- unlink(p);
- return true;
- }
- }
- return false;
- } finally {
- lock.unlock();
- }
- }
-
- public boolean removeLastOccurrence(Object o) {
- if (o == null) return false;
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- for (Node p = last; p != null; p = p.prev) {
- if (o.equals(p.item)) {
- unlink(p);
- return true;
- }
- }
- return false;
- } finally {
- lock.unlock();
- }
- }
-
- // BlockingQueue methods
-
- /**
- * Inserts the specified element at the end of this deque unless it would
- * violate capacity restrictions. When using a capacity-restricted deque,
- * it is generally preferable to use method {@link #offer offer}.
- *
- * This method is equivalent to {@link #addLast}.
- *
- * @throws IllegalStateException if the element cannot be added at this
- * time due to capacity restrictions
- * @throws NullPointerException if the specified element is null
- */
- public boolean add(E e) {
- addLast(e);
- return true;
- }
-
- /**
- * @throws NullPointerException if the specified element is null
- */
- public boolean offer(E e) {
- return offerLast(e);
- }
-
- /**
- * @throws NullPointerException {@inheritDoc}
- * @throws InterruptedException {@inheritDoc}
- */
- public void put(E e) throws InterruptedException {
- putLast(e);
- }
-
- /**
- * @throws NullPointerException {@inheritDoc}
- * @throws InterruptedException {@inheritDoc}
- */
- public boolean offer(E e, long timeout, TimeUnit unit)
- throws InterruptedException {
- return offerLast(e, timeout, unit);
- }
-
- /**
- * Retrieves and removes the head of the queue represented by this deque.
- * This method differs from {@link #poll poll} only in that it throws an
- * exception if this deque is empty.
- *
- *
This method is equivalent to {@link #removeFirst() removeFirst}.
- *
- * @return the head of the queue represented by this deque
- * @throws NoSuchElementException if this deque is empty
- */
- public E remove() {
- return removeFirst();
- }
-
- public E poll() {
- return pollFirst();
- }
-
- public E take() throws InterruptedException {
- return takeFirst();
- }
-
- public E poll(long timeout, TimeUnit unit) throws InterruptedException {
- return pollFirst(timeout, unit);
- }
-
- /**
- * Retrieves, but does not remove, the head of the queue represented by
- * this deque. This method differs from {@link #peek peek} only in that
- * it throws an exception if this deque is empty.
- *
- *
This method is equivalent to {@link #getFirst() getFirst}.
- *
- * @return the head of the queue represented by this deque
- * @throws NoSuchElementException if this deque is empty
- */
- public E element() {
- return getFirst();
- }
-
- public E peek() {
- return peekFirst();
- }
-
- /**
- * Returns the number of additional elements that this deque can ideally
- * (in the absence of memory or resource constraints) accept without
- * blocking. This is always equal to the initial capacity of this deque
- * less the current {@code size} of this deque.
- *
- *
Note that you cannot always tell if an attempt to insert
- * an element will succeed by inspecting {@code remainingCapacity}
- * because it may be the case that another thread is about to
- * insert or remove an element.
- */
- public int remainingCapacity() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- return capacity - count;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * @throws UnsupportedOperationException {@inheritDoc}
- * @throws ClassCastException {@inheritDoc}
- * @throws NullPointerException {@inheritDoc}
- * @throws IllegalArgumentException {@inheritDoc}
- */
- public int drainTo(Collection super E> c) {
- return drainTo(c, Integer.MAX_VALUE);
- }
-
- /**
- * @throws UnsupportedOperationException {@inheritDoc}
- * @throws ClassCastException {@inheritDoc}
- * @throws NullPointerException {@inheritDoc}
- * @throws IllegalArgumentException {@inheritDoc}
- */
- public int drainTo(Collection super E> c, int maxElements) {
- if (c == null)
- throw new NullPointerException();
- if (c == this)
- throw new IllegalArgumentException();
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- int n = Math.min(maxElements, count);
- for (int i = 0; i < n; i++) {
- c.add(first.item); // In this order, in case add() throws.
- unlinkFirst();
- }
- return n;
- } finally {
- lock.unlock();
- }
- }
-
- // Stack methods
-
- /**
- * @throws IllegalStateException {@inheritDoc}
- * @throws NullPointerException {@inheritDoc}
- */
- public void push(E e) {
- addFirst(e);
- }
-
- /**
- * @throws NoSuchElementException {@inheritDoc}
- */
- public E pop() {
- return removeFirst();
- }
-
- // Collection methods
-
- /**
- * Removes the first occurrence of the specified element from this deque.
- * If the deque does not contain the element, it is unchanged.
- * More formally, removes the first element {@code e} such that
- * {@code o.equals(e)} (if such an element exists).
- * Returns {@code true} if this deque contained the specified element
- * (or equivalently, if this deque changed as a result of the call).
- *
- *
This method is equivalent to
- * {@link #removeFirstOccurrence(Object) removeFirstOccurrence}.
- *
- * @param o element to be removed from this deque, if present
- * @return {@code true} if this deque changed as a result of the call
- */
- public boolean remove(Object o) {
- return removeFirstOccurrence(o);
- }
-
- /**
- * Returns the number of elements in this deque.
- *
- * @return the number of elements in this deque
- */
- public int size() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- return count;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Returns {@code true} if this deque contains the specified element.
- * More formally, returns {@code true} if and only if this deque contains
- * at least one element {@code e} such that {@code o.equals(e)}.
- *
- * @param o object to be checked for containment in this deque
- * @return {@code true} if this deque contains the specified element
- */
- public boolean contains(Object o) {
- if (o == null) return false;
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- for (Node p = first; p != null; p = p.next)
- if (o.equals(p.item))
- return true;
- return false;
- } finally {
- lock.unlock();
- }
- }
-
- /*
- * TODO: Add support for more efficient bulk operations.
- *
- * We don't want to acquire the lock for every iteration, but we
- * also want other threads a chance to interact with the
- * collection, especially when count is close to capacity.
- */
-
-// /**
-// * Adds all of the elements in the specified collection to this
-// * queue. Attempts to addAll of a queue to itself result in
-// * {@code IllegalArgumentException}. Further, the behavior of
-// * this operation is undefined if the specified collection is
-// * modified while the operation is in progress.
-// *
-// * @param c collection containing elements to be added to this queue
-// * @return {@code true} if this queue changed as a result of the call
-// * @throws ClassCastException {@inheritDoc}
-// * @throws NullPointerException {@inheritDoc}
-// * @throws IllegalArgumentException {@inheritDoc}
-// * @throws IllegalStateException {@inheritDoc}
-// * @see #add(Object)
-// */
-// public boolean addAll(Collection extends E> c) {
-// if (c == null)
-// throw new NullPointerException();
-// if (c == this)
-// throw new IllegalArgumentException();
-// final ReentrantLock lock = this.lock;
-// lock.lock();
-// try {
-// boolean modified = false;
-// for (E e : c)
-// if (linkLast(e))
-// modified = true;
-// return modified;
-// } finally {
-// lock.unlock();
-// }
-// }
-
- /**
- * Returns an array containing all of the elements in this deque, in
- * proper sequence (from first to last element).
- *
- * The returned array will be "safe" in that no references to it are
- * maintained by this deque. (In other words, this method must allocate
- * a new array). The caller is thus free to modify the returned array.
- *
- *
This method acts as bridge between array-based and collection-based
- * APIs.
- *
- * @return an array containing all of the elements in this deque
- */
- public Object[] toArray() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- Object[] a = new Object[count];
- int k = 0;
- for (Node p = first; p != null; p = p.next)
- a[k++] = p.item;
- return a;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Returns an array containing all of the elements in this deque, in
- * proper sequence; the runtime type of the returned array is that of
- * the specified array. If the deque fits in the specified array, it
- * is returned therein. Otherwise, a new array is allocated with the
- * runtime type of the specified array and the size of this deque.
- *
- * If this deque fits in the specified array with room to spare
- * (i.e., the array has more elements than this deque), the element in
- * the array immediately following the end of the deque is set to
- * {@code null}.
- *
- *
Like the {@link #toArray()} method, this method acts as bridge between
- * array-based and collection-based APIs. Further, this method allows
- * precise control over the runtime type of the output array, and may,
- * under certain circumstances, be used to save allocation costs.
- *
- *
Suppose {@code x} is a deque known to contain only strings.
- * The following code can be used to dump the deque into a newly
- * allocated array of {@code String}:
- *
- *
- * String[] y = x.toArray(new String[0]);
- *
- * Note that {@code toArray(new Object[0])} is identical in function to
- * {@code toArray()}.
- *
- * @param a the array into which the elements of the deque are to
- * be stored, if it is big enough; otherwise, a new array of the
- * same runtime type is allocated for this purpose
- * @return an array containing all of the elements in this deque
- * @throws ArrayStoreException if the runtime type of the specified array
- * is not a supertype of the runtime type of every element in
- * this deque
- * @throws NullPointerException if the specified array is null
- */
- @SuppressWarnings("unchecked")
- public T[] toArray(T[] a) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- if (a.length < count)
- a = (T[])java.lang.reflect.Array.newInstance
- (a.getClass().getComponentType(), count);
-
- int k = 0;
- for (Node p = first; p != null; p = p.next)
- a[k++] = (T)p.item;
- if (a.length > k)
- a[k] = null;
- return a;
- } finally {
- lock.unlock();
- }
- }
-
- public String toString() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- Node p = first;
- if (p == null)
- return "[]";
-
- StringBuilder sb = new StringBuilder();
- sb.append('[');
- for (;;) {
- E e = p.item;
- sb.append(e == this ? "(this Collection)" : e);
- p = p.next;
- if (p == null)
- return sb.append(']').toString();
- sb.append(',').append(' ');
- }
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Atomically removes all of the elements from this deque.
- * The deque will be empty after this call returns.
- */
- public void clear() {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- for (Node f = first; f != null; ) {
- f.item = null;
- Node n = f.next;
- f.prev = null;
- f.next = null;
- f = n;
- }
- first = last = null;
- count = 0;
- notFull.signalAll();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Returns an iterator over the elements in this deque in proper sequence.
- * The elements will be returned in order from first (head) to last (tail).
- *
- * The returned iterator is a "weakly consistent" iterator that
- * will never throw {@link java.util.ConcurrentModificationException
- * ConcurrentModificationException}, and guarantees to traverse
- * elements as they existed upon construction of the iterator, and
- * may (but is not guaranteed to) reflect any modifications
- * subsequent to construction.
- *
- * @return an iterator over the elements in this deque in proper sequence
- */
- public Iterator iterator() {
- return new Itr();
- }
-
- /**
- * Returns an iterator over the elements in this deque in reverse
- * sequential order. The elements will be returned in order from
- * last (tail) to first (head).
- *
- * The returned iterator is a "weakly consistent" iterator that
- * will never throw {@link java.util.ConcurrentModificationException
- * ConcurrentModificationException}, and guarantees to traverse
- * elements as they existed upon construction of the iterator, and
- * may (but is not guaranteed to) reflect any modifications
- * subsequent to construction.
- *
- * @return an iterator over the elements in this deque in reverse order
- */
- public Iterator descendingIterator() {
- return new DescendingItr();
- }
-
- /**
- * Base class for Iterators for LinkedBlockingDeque
- */
- private abstract class AbstractItr implements Iterator {
- /**
- * The next node to return in next()
- */
- Node next;
-
- /**
- * nextItem holds on to item fields because once we claim that
- * an element exists in hasNext(), we must return item read
- * under lock (in advance()) even if it was in the process of
- * being removed when hasNext() was called.
- */
- E nextItem;
-
- /**
- * Node returned by most recent call to next. Needed by remove.
- * Reset to null if this element is deleted by a call to remove.
- */
- private Node lastRet;
-
- abstract Node firstNode();
- abstract Node nextNode(Node n);
-
- AbstractItr() {
- // set to initial position
- final ReentrantLock lock = LinkedBlockingDeque.this.lock;
- lock.lock();
- try {
- next = firstNode();
- nextItem = (next == null) ? null : next.item;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Returns the successor node of the given non-null, but
- * possibly previously deleted, node.
- */
- private Node succ(Node n) {
- // Chains of deleted nodes ending in null or self-links
- // are possible if multiple interior nodes are removed.
- for (;;) {
- Node s = nextNode(n);
- if (s == null)
- return null;
- else if (s.item != null)
- return s;
- else if (s == n)
- return firstNode();
- else
- n = s;
- }
- }
-
- /**
- * Advances next.
- */
- void advance() {
- final ReentrantLock lock = LinkedBlockingDeque.this.lock;
- lock.lock();
- try {
- // assert next != null;
- next = succ(next);
- nextItem = (next == null) ? null : next.item;
- } finally {
- lock.unlock();
- }
- }
-
- public boolean hasNext() {
- return next != null;
- }
-
- public E next() {
- if (next == null)
- throw new NoSuchElementException();
- lastRet = next;
- E x = nextItem;
- advance();
- return x;
- }
-
- public void remove() {
- Node n = lastRet;
- if (n == null)
- throw new IllegalStateException();
- lastRet = null;
- final ReentrantLock lock = LinkedBlockingDeque.this.lock;
- lock.lock();
- try {
- if (n.item != null)
- unlink(n);
- } finally {
- lock.unlock();
- }
- }
- }
-
- /** Forward iterator */
- private class Itr extends AbstractItr {
- Node firstNode() { return first; }
- Node nextNode(Node n) { return n.next; }
- }
-
- /** Descending iterator */
- private class DescendingItr extends AbstractItr {
- Node firstNode() { return last; }
- Node nextNode(Node n) { return n.prev; }
- }
-
- /**
- * Save the state of this deque to a stream (that is, serialize it).
- *
- * @serialData The capacity (int), followed by elements (each an
- * {@code Object}) in the proper order, followed by a null
- * @param s the stream
- */
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- // Write out capacity and any hidden stuff
- s.defaultWriteObject();
- // Write out all elements in the proper order.
- for (Node p = first; p != null; p = p.next)
- s.writeObject(p.item);
- // Use trailing null as sentinel
- s.writeObject(null);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Reconstitute this deque from a stream (that is,
- * deserialize it).
- * @param s the stream
- */
- private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- s.defaultReadObject();
- count = 0;
- first = null;
- last = null;
- // Read in all elements and place in queue
- for (;;) {
- @SuppressWarnings("unchecked")
- E item = (E)s.readObject();
- if (item == null)
- break;
- add(item);
- }
- }
-
-}
+/*
+ * Written by Doug Lea with assistance from members of JCP JSR-166
+ * Expert Group and released to the public domain, as explained at
+ * http://creativecommons.org/licenses/publicdomain
+ */
+
+package com.nostra13.universalimageloader.core.assist.deque;
+
+import java.util.AbstractQueue;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * An optionally-bounded {@linkplain BlockingDeque blocking deque} based on
+ * linked nodes.
+ *
+ * The optional capacity bound constructor argument serves as a
+ * way to prevent excessive expansion. The capacity, if unspecified,
+ * is equal to {@link Integer#MAX_VALUE}. Linked nodes are
+ * dynamically created upon each insertion unless this would bring the
+ * deque above capacity.
+ *
+ * Most operations run in constant time (ignoring time spent
+ * blocking). Exceptions include {@link #remove(Object) remove},
+ * {@link #removeFirstOccurrence removeFirstOccurrence}, {@link
+ * #removeLastOccurrence removeLastOccurrence}, {@link #contains
+ * contains}, {@link #iterator iterator.remove()}, and the bulk
+ * operations, all of which run in linear time.
+ *
+ * This class and its iterator implement all of the
+ * optional methods of the {@link Collection} and {@link
+ * Iterator} interfaces.
+ *
+ * This class is a member of the
+ *
+ * Java Collections Framework.
+ *
+ * @param the type of elements held in this collection
+ * @author Doug Lea
+ * @since 1.6
+ */
+public class LinkedBlockingDeque
+ extends AbstractQueue
+ implements BlockingDeque, java.io.Serializable {
+
+ /*
+ * Implemented as a simple doubly-linked list protected by a
+ * single lock and using conditions to manage blocking.
+ *
+ * To implement weakly consistent iterators, it appears we need to
+ * keep all Nodes GC-reachable from a predecessor dequeued Node.
+ * That would cause two problems:
+ * - allow a rogue Iterator to cause unbounded memory retention
+ * - cause cross-generational linking of old Nodes to new Nodes if
+ * a Node was tenured while live, which generational GCs have a
+ * hard time dealing with, causing repeated major collections.
+ * However, only non-deleted Nodes need to be reachable from
+ * dequeued Nodes, and reachability does not necessarily have to
+ * be of the kind understood by the GC. We use the trick of
+ * linking a Node that has just been dequeued to itself. Such a
+ * self-link implicitly means to jump to "first" (for next links)
+ * or "last" (for prev links).
+ */
+
+ /*
+ * We have "diamond" multiple interface/abstract class inheritance
+ * here, and that introduces ambiguities. Often we want the
+ * BlockingDeque javadoc combined with the AbstractQueue
+ * implementation, so a lot of method specs are duplicated here.
+ */
+
+ private static final long serialVersionUID = -387911632671998426L;
+
+ /**
+ * Doubly-linked list node class
+ */
+ static final class Node {
+ /**
+ * The item, or null if this node has been removed.
+ */
+ E item;
+
+ /**
+ * One of:
+ * - the real predecessor Node
+ * - this Node, meaning the predecessor is tail
+ * - null, meaning there is no predecessor
+ */
+ Node prev;
+
+ /**
+ * One of:
+ * - the real successor Node
+ * - this Node, meaning the successor is head
+ * - null, meaning there is no successor
+ */
+ Node next;
+
+ Node(E x) {
+ item = x;
+ }
+ }
+
+ /**
+ * Pointer to first node.
+ * Invariant: (first == null && last == null) ||
+ * (first.prev == null && first.item != null)
+ */
+ transient Node first;
+
+ /**
+ * Pointer to last node.
+ * Invariant: (first == null && last == null) ||
+ * (last.next == null && last.item != null)
+ */
+ transient Node last;
+
+ /**
+ * Number of items in the deque
+ */
+ private transient int count;
+
+ /**
+ * Maximum number of items in the deque
+ */
+ private final int capacity;
+
+ /**
+ * Main lock guarding all access
+ */
+ final ReentrantLock lock = new ReentrantLock();
+
+ /**
+ * Condition for waiting takes
+ */
+ private final Condition notEmpty = lock.newCondition();
+
+ /**
+ * Condition for waiting puts
+ */
+ private final Condition notFull = lock.newCondition();
+
+ /**
+ * Creates a {@code LinkedBlockingDeque} with a capacity of
+ * {@link Integer#MAX_VALUE}.
+ */
+ public LinkedBlockingDeque() {
+ this(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity.
+ *
+ * @param capacity the capacity of this deque
+ * @throws IllegalArgumentException if {@code capacity} is less than 1
+ */
+ public LinkedBlockingDeque(int capacity) {
+ if (capacity <= 0) throw new IllegalArgumentException();
+ this.capacity = capacity;
+ }
+
+ /**
+ * Creates a {@code LinkedBlockingDeque} with a capacity of
+ * {@link Integer#MAX_VALUE}, initially containing the elements of
+ * the given collection, added in traversal order of the
+ * collection's iterator.
+ *
+ * @param c the collection of elements to initially contain
+ * @throws NullPointerException if the specified collection or any
+ * of its elements are null
+ */
+ public LinkedBlockingDeque(Collection extends E> c) {
+ this(Integer.MAX_VALUE);
+ final ReentrantLock lock = this.lock;
+ lock.lock(); // Never contended, but necessary for visibility
+ try {
+ for (E e : c) {
+ if (e == null)
+ throw new NullPointerException();
+ if (!linkLast(new Node(e)))
+ throw new IllegalStateException("Deque full");
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+
+ // Basic linking and unlinking operations, called only while holding lock
+
+ /**
+ * Links node as first element, or returns false if full.
+ */
+ private boolean linkFirst(Node node) {
+ // assert lock.isHeldByCurrentThread();
+ if (count >= capacity)
+ return false;
+ Node f = first;
+ node.next = f;
+ first = node;
+ if (last == null)
+ last = node;
+ else
+ f.prev = node;
+ ++count;
+ notEmpty.signal();
+ return true;
+ }
+
+ /**
+ * Links node as last element, or returns false if full.
+ */
+ private boolean linkLast(Node node) {
+ // assert lock.isHeldByCurrentThread();
+ if (count >= capacity)
+ return false;
+ Node l = last;
+ node.prev = l;
+ last = node;
+ if (first == null)
+ first = node;
+ else
+ l.next = node;
+ ++count;
+ notEmpty.signal();
+ return true;
+ }
+
+ /**
+ * Removes and returns first element, or null if empty.
+ */
+ private E unlinkFirst() {
+ // assert lock.isHeldByCurrentThread();
+ Node f = first;
+ if (f == null)
+ return null;
+ Node n = f.next;
+ E item = f.item;
+ f.item = null;
+ f.next = f; // help GC
+ first = n;
+ if (n == null)
+ last = null;
+ else
+ n.prev = null;
+ --count;
+ notFull.signal();
+ return item;
+ }
+
+ /**
+ * Removes and returns last element, or null if empty.
+ */
+ private E unlinkLast() {
+ // assert lock.isHeldByCurrentThread();
+ Node l = last;
+ if (l == null)
+ return null;
+ Node p = l.prev;
+ E item = l.item;
+ l.item = null;
+ l.prev = l; // help GC
+ last = p;
+ if (p == null)
+ first = null;
+ else
+ p.next = null;
+ --count;
+ notFull.signal();
+ return item;
+ }
+
+ /**
+ * Unlinks x.
+ */
+ void unlink(Node x) {
+ // assert lock.isHeldByCurrentThread();
+ Node p = x.prev;
+ Node n = x.next;
+ if (p == null) {
+ unlinkFirst();
+ } else if (n == null) {
+ unlinkLast();
+ } else {
+ p.next = n;
+ n.prev = p;
+ x.item = null;
+ // Don't mess with x's links. They may still be in use by
+ // an iterator.
+ --count;
+ notFull.signal();
+ }
+ }
+
+ // BlockingDeque methods
+
+ /**
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws NullPointerException {@inheritDoc}
+ */
+ public void addFirst(E e) {
+ if (!offerFirst(e))
+ throw new IllegalStateException("Deque full");
+ }
+
+ /**
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws NullPointerException {@inheritDoc}
+ */
+ public void addLast(E e) {
+ if (!offerLast(e))
+ throw new IllegalStateException("Deque full");
+ }
+
+ /**
+ * @throws NullPointerException {@inheritDoc}
+ */
+ public boolean offerFirst(E e) {
+ if (e == null) throw new NullPointerException();
+ Node node = new Node(e);
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ return linkFirst(node);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @throws NullPointerException {@inheritDoc}
+ */
+ public boolean offerLast(E e) {
+ if (e == null) throw new NullPointerException();
+ Node node = new Node(e);
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ return linkLast(node);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @throws NullPointerException {@inheritDoc}
+ * @throws InterruptedException {@inheritDoc}
+ */
+ public void putFirst(E e) throws InterruptedException {
+ if (e == null) throw new NullPointerException();
+ Node node = new Node(e);
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ while (!linkFirst(node))
+ notFull.await();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @throws NullPointerException {@inheritDoc}
+ * @throws InterruptedException {@inheritDoc}
+ */
+ public void putLast(E e) throws InterruptedException {
+ if (e == null) throw new NullPointerException();
+ Node node = new Node(e);
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ while (!linkLast(node))
+ notFull.await();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @throws NullPointerException {@inheritDoc}
+ * @throws InterruptedException {@inheritDoc}
+ */
+ public boolean offerFirst(E e, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ if (e == null) throw new NullPointerException();
+ Node node = new Node(e);
+ long nanos = unit.toNanos(timeout);
+ final ReentrantLock lock = this.lock;
+ lock.lockInterruptibly();
+ try {
+ while (!linkFirst(node)) {
+ if (nanos <= 0)
+ return false;
+ nanos = notFull.awaitNanos(nanos);
+ }
+ return true;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @throws NullPointerException {@inheritDoc}
+ * @throws InterruptedException {@inheritDoc}
+ */
+ public boolean offerLast(E e, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ if (e == null) throw new NullPointerException();
+ Node node = new Node(e);
+ long nanos = unit.toNanos(timeout);
+ final ReentrantLock lock = this.lock;
+ lock.lockInterruptibly();
+ try {
+ while (!linkLast(node)) {
+ if (nanos <= 0)
+ return false;
+ nanos = notFull.awaitNanos(nanos);
+ }
+ return true;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @throws NoSuchElementException {@inheritDoc}
+ */
+ public E removeFirst() {
+ E x = pollFirst();
+ if (x == null) throw new NoSuchElementException();
+ return x;
+ }
+
+ /**
+ * @throws NoSuchElementException {@inheritDoc}
+ */
+ public E removeLast() {
+ E x = pollLast();
+ if (x == null) throw new NoSuchElementException();
+ return x;
+ }
+
+ public E pollFirst() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ return unlinkFirst();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public E pollLast() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ return unlinkLast();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public E takeFirst() throws InterruptedException {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ E x;
+ while ((x = unlinkFirst()) == null)
+ notEmpty.await();
+ return x;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public E takeLast() throws InterruptedException {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ E x;
+ while ((x = unlinkLast()) == null)
+ notEmpty.await();
+ return x;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public E pollFirst(long timeout, TimeUnit unit)
+ throws InterruptedException {
+ long nanos = unit.toNanos(timeout);
+ final ReentrantLock lock = this.lock;
+ lock.lockInterruptibly();
+ try {
+ E x;
+ while ((x = unlinkFirst()) == null) {
+ if (nanos <= 0)
+ return null;
+ nanos = notEmpty.awaitNanos(nanos);
+ }
+ return x;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public E pollLast(long timeout, TimeUnit unit)
+ throws InterruptedException {
+ long nanos = unit.toNanos(timeout);
+ final ReentrantLock lock = this.lock;
+ lock.lockInterruptibly();
+ try {
+ E x;
+ while ((x = unlinkLast()) == null) {
+ if (nanos <= 0)
+ return null;
+ nanos = notEmpty.awaitNanos(nanos);
+ }
+ return x;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @throws NoSuchElementException {@inheritDoc}
+ */
+ public E getFirst() {
+ E x = peekFirst();
+ if (x == null) throw new NoSuchElementException();
+ return x;
+ }
+
+ /**
+ * @throws NoSuchElementException {@inheritDoc}
+ */
+ public E getLast() {
+ E x = peekLast();
+ if (x == null) throw new NoSuchElementException();
+ return x;
+ }
+
+ public E peekFirst() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ return (first == null) ? null : first.item;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public E peekLast() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ return (last == null) ? null : last.item;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean removeFirstOccurrence(Object o) {
+ if (o == null) return false;
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ for (Node p = first; p != null; p = p.next) {
+ if (o.equals(p.item)) {
+ unlink(p);
+ return true;
+ }
+ }
+ return false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean removeLastOccurrence(Object o) {
+ if (o == null) return false;
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ for (Node p = last; p != null; p = p.prev) {
+ if (o.equals(p.item)) {
+ unlink(p);
+ return true;
+ }
+ }
+ return false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ // BlockingQueue methods
+
+ /**
+ * Inserts the specified element at the end of this deque unless it would
+ * violate capacity restrictions. When using a capacity-restricted deque,
+ * it is generally preferable to use method {@link #offer offer}.
+ *
+ * This method is equivalent to {@link #addLast}.
+ *
+ * @throws IllegalStateException if the element cannot be added at this
+ * time due to capacity restrictions
+ * @throws NullPointerException if the specified element is null
+ */
+ public boolean add(E e) {
+ addLast(e);
+ return true;
+ }
+
+ /**
+ * @throws NullPointerException if the specified element is null
+ */
+ public boolean offer(E e) {
+ return offerLast(e);
+ }
+
+ /**
+ * @throws NullPointerException {@inheritDoc}
+ * @throws InterruptedException {@inheritDoc}
+ */
+ public void put(E e) throws InterruptedException {
+ putLast(e);
+ }
+
+ /**
+ * @throws NullPointerException {@inheritDoc}
+ * @throws InterruptedException {@inheritDoc}
+ */
+ public boolean offer(E e, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return offerLast(e, timeout, unit);
+ }
+
+ /**
+ * Retrieves and removes the head of the queue represented by this deque.
+ * This method differs from {@link #poll poll} only in that it throws an
+ * exception if this deque is empty.
+ *
+ * This method is equivalent to {@link #removeFirst() removeFirst}.
+ *
+ * @return the head of the queue represented by this deque
+ * @throws NoSuchElementException if this deque is empty
+ */
+ public E remove() {
+ return removeFirst();
+ }
+
+ public E poll() {
+ return pollFirst();
+ }
+
+ public E take() throws InterruptedException {
+ return takeFirst();
+ }
+
+ public E poll(long timeout, TimeUnit unit) throws InterruptedException {
+ return pollFirst(timeout, unit);
+ }
+
+ /**
+ * Retrieves, but does not remove, the head of the queue represented by
+ * this deque. This method differs from {@link #peek peek} only in that
+ * it throws an exception if this deque is empty.
+ *
+ * This method is equivalent to {@link #getFirst() getFirst}.
+ *
+ * @return the head of the queue represented by this deque
+ * @throws NoSuchElementException if this deque is empty
+ */
+ public E element() {
+ return getFirst();
+ }
+
+ public E peek() {
+ return peekFirst();
+ }
+
+ /**
+ * Returns the number of additional elements that this deque can ideally
+ * (in the absence of memory or resource constraints) accept without
+ * blocking. This is always equal to the initial capacity of this deque
+ * less the current {@code size} of this deque.
+ *
+ * Note that you cannot always tell if an attempt to insert
+ * an element will succeed by inspecting {@code remainingCapacity}
+ * because it may be the case that another thread is about to
+ * insert or remove an element.
+ */
+ public int remainingCapacity() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ return capacity - count;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @throws UnsupportedOperationException {@inheritDoc}
+ * @throws ClassCastException {@inheritDoc}
+ * @throws NullPointerException {@inheritDoc}
+ * @throws IllegalArgumentException {@inheritDoc}
+ */
+ public int drainTo(Collection super E> c) {
+ return drainTo(c, Integer.MAX_VALUE);
+ }
+
+ /**
+ * @throws UnsupportedOperationException {@inheritDoc}
+ * @throws ClassCastException {@inheritDoc}
+ * @throws NullPointerException {@inheritDoc}
+ * @throws IllegalArgumentException {@inheritDoc}
+ */
+ public int drainTo(Collection super E> c, int maxElements) {
+ if (c == null)
+ throw new NullPointerException();
+ if (c == this)
+ throw new IllegalArgumentException();
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ int n = Math.min(maxElements, count);
+ for (int i = 0; i < n; i++) {
+ c.add(first.item); // In this order, in case add() throws.
+ unlinkFirst();
+ }
+ return n;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ // Stack methods
+
+ /**
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws NullPointerException {@inheritDoc}
+ */
+ public void push(E e) {
+ addFirst(e);
+ }
+
+ /**
+ * @throws NoSuchElementException {@inheritDoc}
+ */
+ public E pop() {
+ return removeFirst();
+ }
+
+ // Collection methods
+
+ /**
+ * Removes the first occurrence of the specified element from this deque.
+ * If the deque does not contain the element, it is unchanged.
+ * More formally, removes the first element {@code e} such that
+ * {@code o.equals(e)} (if such an element exists).
+ * Returns {@code true} if this deque contained the specified element
+ * (or equivalently, if this deque changed as a result of the call).
+ *
+ * This method is equivalent to
+ * {@link #removeFirstOccurrence(Object) removeFirstOccurrence}.
+ *
+ * @param o element to be removed from this deque, if present
+ * @return {@code true} if this deque changed as a result of the call
+ */
+ public boolean remove(Object o) {
+ return removeFirstOccurrence(o);
+ }
+
+ /**
+ * Returns the number of elements in this deque.
+ *
+ * @return the number of elements in this deque
+ */
+ public int size() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ return count;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns {@code true} if this deque contains the specified element.
+ * More formally, returns {@code true} if and only if this deque contains
+ * at least one element {@code e} such that {@code o.equals(e)}.
+ *
+ * @param o object to be checked for containment in this deque
+ * @return {@code true} if this deque contains the specified element
+ */
+ public boolean contains(Object o) {
+ if (o == null) return false;
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ for (Node p = first; p != null; p = p.next)
+ if (o.equals(p.item))
+ return true;
+ return false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /*
+ * TODO: Add support for more efficient bulk operations.
+ *
+ * We don't want to acquire the lock for every iteration, but we
+ * also want other threads a chance to interact with the
+ * collection, especially when count is close to capacity.
+ */
+
+// /**
+// * Adds all of the elements in the specified collection to this
+// * queue. Attempts to addAll of a queue to itself result in
+// * {@code IllegalArgumentException}. Further, the behavior of
+// * this operation is undefined if the specified collection is
+// * modified while the operation is in progress.
+// *
+// * @param c collection containing elements to be added to this queue
+// * @return {@code true} if this queue changed as a result of the call
+// * @throws ClassCastException {@inheritDoc}
+// * @throws NullPointerException {@inheritDoc}
+// * @throws IllegalArgumentException {@inheritDoc}
+// * @throws IllegalStateException {@inheritDoc}
+// * @see #add(Object)
+// */
+// public boolean addAll(Collection extends E> c) {
+// if (c == null)
+// throw new NullPointerException();
+// if (c == this)
+// throw new IllegalArgumentException();
+// final ReentrantLock lock = this.lock;
+// lock.lock();
+// try {
+// boolean modified = false;
+// for (E e : c)
+// if (linkLast(e))
+// modified = true;
+// return modified;
+// } finally {
+// lock.unlock();
+// }
+// }
+
+ /**
+ * Returns an array containing all of the elements in this deque, in
+ * proper sequence (from first to last element).
+ *
+ * The returned array will be "safe" in that no references to it are
+ * maintained by this deque. (In other words, this method must allocate
+ * a new array). The caller is thus free to modify the returned array.
+ *
+ * This method acts as bridge between array-based and collection-based
+ * APIs.
+ *
+ * @return an array containing all of the elements in this deque
+ */
+ public Object[] toArray() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ Object[] a = new Object[count];
+ int k = 0;
+ for (Node p = first; p != null; p = p.next)
+ a[k++] = p.item;
+ return a;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns an array containing all of the elements in this deque, in
+ * proper sequence; the runtime type of the returned array is that of
+ * the specified array. If the deque fits in the specified array, it
+ * is returned therein. Otherwise, a new array is allocated with the
+ * runtime type of the specified array and the size of this deque.
+ *
+ * If this deque fits in the specified array with room to spare
+ * (i.e., the array has more elements than this deque), the element in
+ * the array immediately following the end of the deque is set to
+ * {@code null}.
+ *
+ * Like the {@link #toArray()} method, this method acts as bridge between
+ * array-based and collection-based APIs. Further, this method allows
+ * precise control over the runtime type of the output array, and may,
+ * under certain circumstances, be used to save allocation costs.
+ *
+ * Suppose {@code x} is a deque known to contain only strings.
+ * The following code can be used to dump the deque into a newly
+ * allocated array of {@code String}:
+ *
+ *
+ * String[] y = x.toArray(new String[0]);
+ *
+ * Note that {@code toArray(new Object[0])} is identical in function to
+ * {@code toArray()}.
+ *
+ * @param a the array into which the elements of the deque are to
+ * be stored, if it is big enough; otherwise, a new array of the
+ * same runtime type is allocated for this purpose
+ * @return an array containing all of the elements in this deque
+ * @throws ArrayStoreException if the runtime type of the specified array
+ * is not a supertype of the runtime type of every element in
+ * this deque
+ * @throws NullPointerException if the specified array is null
+ */
+ @SuppressWarnings("unchecked")
+ public T[] toArray(T[] a) {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ if (a.length < count)
+ a = (T[]) java.lang.reflect.Array.newInstance
+ (a.getClass().getComponentType(), count);
+
+ int k = 0;
+ for (Node p = first; p != null; p = p.next)
+ a[k++] = (T) p.item;
+ if (a.length > k)
+ a[k] = null;
+ return a;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public String toString() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ Node p = first;
+ if (p == null)
+ return "[]";
+
+ StringBuilder sb = new StringBuilder();
+ sb.append('[');
+ for (; ; ) {
+ E e = p.item;
+ sb.append(e == this ? "(this Collection)" : e);
+ p = p.next;
+ if (p == null)
+ return sb.append(']').toString();
+ sb.append(',').append(' ');
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Atomically removes all of the elements from this deque.
+ * The deque will be empty after this call returns.
+ */
+ public void clear() {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ for (Node f = first; f != null; ) {
+ f.item = null;
+ Node n = f.next;
+ f.prev = null;
+ f.next = null;
+ f = n;
+ }
+ first = last = null;
+ count = 0;
+ notFull.signalAll();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns an iterator over the elements in this deque in proper sequence.
+ * The elements will be returned in order from first (head) to last (tail).
+ *
+ * The returned iterator is a "weakly consistent" iterator that
+ * will never throw {@link java.util.ConcurrentModificationException
+ * ConcurrentModificationException}, and guarantees to traverse
+ * elements as they existed upon construction of the iterator, and
+ * may (but is not guaranteed to) reflect any modifications
+ * subsequent to construction.
+ *
+ * @return an iterator over the elements in this deque in proper sequence
+ */
+ public Iterator iterator() {
+ return new Itr();
+ }
+
+ /**
+ * Returns an iterator over the elements in this deque in reverse
+ * sequential order. The elements will be returned in order from
+ * last (tail) to first (head).
+ *
+ * The returned iterator is a "weakly consistent" iterator that
+ * will never throw {@link java.util.ConcurrentModificationException
+ * ConcurrentModificationException}, and guarantees to traverse
+ * elements as they existed upon construction of the iterator, and
+ * may (but is not guaranteed to) reflect any modifications
+ * subsequent to construction.
+ *
+ * @return an iterator over the elements in this deque in reverse order
+ */
+ public Iterator descendingIterator() {
+ return new DescendingItr();
+ }
+
+ /**
+ * Base class for Iterators for LinkedBlockingDeque
+ */
+ private abstract class AbstractItr implements Iterator {
+ /**
+ * The next node to return in next()
+ */
+ Node next;
+
+ /**
+ * nextItem holds on to item fields because once we claim that
+ * an element exists in hasNext(), we must return item read
+ * under lock (in advance()) even if it was in the process of
+ * being removed when hasNext() was called.
+ */
+ E nextItem;
+
+ /**
+ * Node returned by most recent call to next. Needed by remove.
+ * Reset to null if this element is deleted by a call to remove.
+ */
+ private Node lastRet;
+
+ abstract Node firstNode();
+
+ abstract Node nextNode(Node n);
+
+ AbstractItr() {
+ // set to initial position
+ final ReentrantLock lock = LinkedBlockingDeque.this.lock;
+ lock.lock();
+ try {
+ next = firstNode();
+ nextItem = (next == null) ? null : next.item;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns the successor node of the given non-null, but
+ * possibly previously deleted, node.
+ */
+ private Node succ(Node n) {
+ // Chains of deleted nodes ending in null or self-links
+ // are possible if multiple interior nodes are removed.
+ for (; ; ) {
+ Node s = nextNode(n);
+ if (s == null)
+ return null;
+ else if (s.item != null)
+ return s;
+ else if (s == n)
+ return firstNode();
+ else
+ n = s;
+ }
+ }
+
+ /**
+ * Advances next.
+ */
+ void advance() {
+ final ReentrantLock lock = LinkedBlockingDeque.this.lock;
+ lock.lock();
+ try {
+ // assert next != null;
+ next = succ(next);
+ nextItem = (next == null) ? null : next.item;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ public E next() {
+ if (next == null)
+ throw new NoSuchElementException();
+ lastRet = next;
+ E x = nextItem;
+ advance();
+ return x;
+ }
+
+ public void remove() {
+ Node n = lastRet;
+ if (n == null)
+ throw new IllegalStateException();
+ lastRet = null;
+ final ReentrantLock lock = LinkedBlockingDeque.this.lock;
+ lock.lock();
+ try {
+ if (n.item != null)
+ unlink(n);
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ /**
+ * Forward iterator
+ */
+ private class Itr extends AbstractItr {
+ Node firstNode() {
+ return first;
+ }
+
+ Node nextNode(Node n) {
+ return n.next;
+ }
+ }
+
+ /**
+ * Descending iterator
+ */
+ private class DescendingItr extends AbstractItr {
+ Node firstNode() {
+ return last;
+ }
+
+ Node nextNode(Node n) {
+ return n.prev;
+ }
+ }
+
+ /**
+ * Save the state of this deque to a stream (that is, serialize it).
+ *
+ * @param s the stream
+ * @serialData The capacity (int), followed by elements (each an
+ * {@code Object}) in the proper order, followed by a null
+ */
+ private void writeObject(java.io.ObjectOutputStream s)
+ throws java.io.IOException {
+ final ReentrantLock lock = this.lock;
+ lock.lock();
+ try {
+ // Write out capacity and any hidden stuff
+ s.defaultWriteObject();
+ // Write out all elements in the proper order.
+ for (Node p = first; p != null; p = p.next)
+ s.writeObject(p.item);
+ // Use trailing null as sentinel
+ s.writeObject(null);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Reconstitute this deque from a stream (that is,
+ * deserialize it).
+ *
+ * @param s the stream
+ */
+ private void readObject(java.io.ObjectInputStream s)
+ throws java.io.IOException, ClassNotFoundException {
+ s.defaultReadObject();
+ count = 0;
+ first = null;
+ last = null;
+ // Read in all elements and place in queue
+ for (; ; ) {
+ @SuppressWarnings("unchecked")
+ E item = (E) s.readObject();
+ if (item == null)
+ break;
+ add(item);
+ }
+ }
+
+}
diff --git a/library/src/com/nostra13/universalimageloader/core/decode/BaseImageDecoder.java b/library/src/main/java/com/nostra13/universalimageloader/core/decode/BaseImageDecoder.java
similarity index 59%
rename from library/src/com/nostra13/universalimageloader/core/decode/BaseImageDecoder.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/decode/BaseImageDecoder.java
index 55ed127d2..8ff05559a 100644
--- a/library/src/com/nostra13/universalimageloader/core/decode/BaseImageDecoder.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/decode/BaseImageDecoder.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@
import android.graphics.BitmapFactory.Options;
import android.graphics.Matrix;
import android.media.ExifInterface;
-import android.os.Build;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.assist.ImageSize;
import com.nostra13.universalimageloader.core.download.ImageDownloader.Scheme;
@@ -40,16 +39,19 @@
*/
public class BaseImageDecoder implements ImageDecoder {
- protected static final String LOG_SABSAMPLE_IMAGE = "Subsample original image (%1$s) to %2$s (scale = %3$d) [%4$s]";
+ protected static final String LOG_SUBSAMPLE_IMAGE = "Subsample original image (%1$s) to %2$s (scale = %3$d) [%4$s]";
protected static final String LOG_SCALE_IMAGE = "Scale subsampled image (%1$s) to %2$s (scale = %3$.5f) [%4$s]";
protected static final String LOG_ROTATE_IMAGE = "Rotate image on %1$d\u00B0 [%2$s]";
protected static final String LOG_FLIP_IMAGE = "Flip image horizontally [%s]";
+ protected static final String ERROR_NO_IMAGE_STREAM = "No stream for image [%s]";
protected static final String ERROR_CANT_DECODE_IMAGE = "Image can't be decoded [%s]";
+
protected final boolean loggingEnabled;
/**
- * @param loggingEnabled Whether debug logs will be written to LogCat.
- * Usually should match {@link com.nostra13.universalimageloader.core.ImageLoaderConfiguration.Builder#writeDebugLogs() ImageLoaderConfiguration.writeDebugLogs()}
+ * @param loggingEnabled Whether debug logs will be written to LogCat. Usually should match {@link
+ * com.nostra13.universalimageloader.core.ImageLoaderConfiguration.Builder#writeDebugLogs()
+ * ImageLoaderConfiguration.writeDebugLogs()}
*/
public BaseImageDecoder(boolean loggingEnabled) {
this.loggingEnabled = loggingEnabled;
@@ -64,16 +66,30 @@ public BaseImageDecoder(boolean loggingEnabled) {
* @throws IOException if some I/O exception occurs during image reading
* @throws UnsupportedOperationException if image URI has unsupported scheme(protocol)
*/
+ @Override
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
+ Bitmap decodedBitmap;
+ ImageFileInfo imageInfo;
+
InputStream imageStream = getImageStream(decodingInfo);
- ImageFileInfo imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo.getImageUri());
- Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
- imageStream = getImageStream(decodingInfo);
- Bitmap decodedBitmap = decodeStream(imageStream, decodingOptions);
+ if (imageStream == null) {
+ L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
+ return null;
+ }
+ try {
+ imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
+ imageStream = resetStream(imageStream, decodingInfo);
+ Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
+ decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
+ } finally {
+ IoUtils.closeSilently(imageStream);
+ }
+
if (decodedBitmap == null) {
L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
} else {
- decodedBitmap = considerExactScaleAndOrientaiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation, imageInfo.exif.flipHorizontal);
+ decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
+ imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
@@ -82,94 +98,108 @@ protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOEx
return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
}
- protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, String imageUri) throws IOException {
+ protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)
+ throws IOException {
Options options = new Options();
options.inJustDecodeBounds = true;
- try {
- BitmapFactory.decodeStream(imageStream, null, options);
- } finally {
- IoUtils.closeSilently(imageStream);
- }
+ BitmapFactory.decodeStream(imageStream, null, options);
ExifInfo exif;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
- exif = defineExifOrientation(imageUri, options.outMimeType);
+ String imageUri = decodingInfo.getImageUri();
+ if (decodingInfo.shouldConsiderExifParams() && canDefineExifParams(imageUri, options.outMimeType)) {
+ exif = defineExifOrientation(imageUri);
} else {
exif = new ExifInfo();
}
return new ImageFileInfo(new ImageSize(options.outWidth, options.outHeight, exif.rotation), exif);
}
- protected ExifInfo defineExifOrientation(String imageUri, String mimeType) {
+ private boolean canDefineExifParams(String imageUri, String mimeType) {
+ return "image/jpeg".equalsIgnoreCase(mimeType) && (Scheme.ofUri(imageUri) == Scheme.FILE);
+ }
+
+ protected ExifInfo defineExifOrientation(String imageUri) {
int rotation = 0;
boolean flip = false;
- if ("image/jpeg".equalsIgnoreCase(mimeType) && Scheme.ofUri(imageUri) == Scheme.FILE) {
- try {
- ExifInterface exif = new ExifInterface(Scheme.FILE.crop(imageUri));
- int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
- switch (exifOrientation) {
- case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
- flip = true;
- case ExifInterface.ORIENTATION_NORMAL:
- rotation = 0;
- break;
- case ExifInterface.ORIENTATION_TRANSVERSE:
- flip = true;
- case ExifInterface.ORIENTATION_ROTATE_90:
- rotation = 90;
- break;
- case ExifInterface.ORIENTATION_FLIP_VERTICAL:
- flip = true;
- case ExifInterface.ORIENTATION_ROTATE_180:
- rotation = 180;
- break;
- case ExifInterface.ORIENTATION_TRANSPOSE:
- flip = true;
- case ExifInterface.ORIENTATION_ROTATE_270:
- rotation = 270;
- break;
- }
- } catch (IOException e) {
- L.w("Can't read EXIF tags from file [%s]", imageUri);
+ try {
+ ExifInterface exif = new ExifInterface(Scheme.FILE.crop(imageUri));
+ int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
+ switch (exifOrientation) {
+ case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
+ flip = true;
+ case ExifInterface.ORIENTATION_NORMAL:
+ rotation = 0;
+ break;
+ case ExifInterface.ORIENTATION_TRANSVERSE:
+ flip = true;
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ rotation = 90;
+ break;
+ case ExifInterface.ORIENTATION_FLIP_VERTICAL:
+ flip = true;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ rotation = 180;
+ break;
+ case ExifInterface.ORIENTATION_TRANSPOSE:
+ flip = true;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ rotation = 270;
+ break;
}
+ } catch (IOException e) {
+ L.w("Can't read EXIF tags from file [%s]", imageUri);
}
return new ExifInfo(rotation, flip);
}
protected Options prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo) {
ImageScaleType scaleType = decodingInfo.getImageScaleType();
- ImageSize targetSize = decodingInfo.getTargetSize();
- int scale = 1;
- if (scaleType != ImageScaleType.NONE) {
+ int scale;
+ if (scaleType == ImageScaleType.NONE) {
+ scale = 1;
+ } else if (scaleType == ImageScaleType.NONE_SAFE) {
+ scale = ImageSizeUtils.computeMinImageSampleSize(imageSize);
+ } else {
+ ImageSize targetSize = decodingInfo.getTargetSize();
boolean powerOf2 = scaleType == ImageScaleType.IN_SAMPLE_POWER_OF_2;
scale = ImageSizeUtils.computeImageSampleSize(imageSize, targetSize, decodingInfo.getViewScaleType(), powerOf2);
-
- if (loggingEnabled) L.d(LOG_SABSAMPLE_IMAGE, imageSize, imageSize.scaleDown(scale), scale, decodingInfo.getImageKey());
}
+ if (scale > 1 && loggingEnabled) {
+ L.d(LOG_SUBSAMPLE_IMAGE, imageSize, imageSize.scaleDown(scale), scale, decodingInfo.getImageKey());
+ }
+
Options decodingOptions = decodingInfo.getDecodingOptions();
decodingOptions.inSampleSize = scale;
return decodingOptions;
}
- protected Bitmap decodeStream(InputStream imageStream, Options decodingOptions) throws IOException {
- try {
- return BitmapFactory.decodeStream(imageStream, null, decodingOptions);
- } finally {
- IoUtils.closeSilently(imageStream);
+ protected InputStream resetStream(InputStream imageStream, ImageDecodingInfo decodingInfo) throws IOException {
+ if (imageStream.markSupported()) {
+ try {
+ imageStream.reset();
+ return imageStream;
+ } catch (IOException ignored) {
+ }
}
+ IoUtils.closeSilently(imageStream);
+ return getImageStream(decodingInfo);
}
- protected Bitmap considerExactScaleAndOrientaiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo, int rotation, boolean flipHorizontal) {
+ protected Bitmap considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo,
+ int rotation, boolean flipHorizontal) {
Matrix m = new Matrix();
// Scale to exact size if need
ImageScaleType scaleType = decodingInfo.getImageScaleType();
if (scaleType == ImageScaleType.EXACTLY || scaleType == ImageScaleType.EXACTLY_STRETCHED) {
ImageSize srcSize = new ImageSize(subsampledBitmap.getWidth(), subsampledBitmap.getHeight(), rotation);
- float scale = ImageSizeUtils.computeImageScale(srcSize, decodingInfo.getTargetSize(), decodingInfo.getViewScaleType(), scaleType == ImageScaleType.EXACTLY_STRETCHED);
+ float scale = ImageSizeUtils.computeImageScale(srcSize, decodingInfo.getTargetSize(), decodingInfo
+ .getViewScaleType(), scaleType == ImageScaleType.EXACTLY_STRETCHED);
if (Float.compare(scale, 1f) != 0) {
m.setScale(scale, scale);
- if (loggingEnabled) L.d(LOG_SCALE_IMAGE, srcSize, srcSize.scale(scale), scale, decodingInfo.getImageKey());
+ if (loggingEnabled) {
+ L.d(LOG_SCALE_IMAGE, srcSize, srcSize.scale(scale), scale, decodingInfo.getImageKey());
+ }
}
}
// Flip bitmap if need
@@ -185,7 +215,8 @@ protected Bitmap considerExactScaleAndOrientaiton(Bitmap subsampledBitmap, Image
if (loggingEnabled) L.d(LOG_ROTATE_IMAGE, rotation, decodingInfo.getImageKey());
}
- Bitmap finalBitmap = Bitmap.createBitmap(subsampledBitmap, 0, 0, subsampledBitmap.getWidth(), subsampledBitmap.getHeight(), m, true);
+ Bitmap finalBitmap = Bitmap.createBitmap(subsampledBitmap, 0, 0, subsampledBitmap.getWidth(), subsampledBitmap
+ .getHeight(), m, true);
if (finalBitmap != subsampledBitmap) {
subsampledBitmap.recycle();
}
@@ -194,8 +225,8 @@ protected Bitmap considerExactScaleAndOrientaiton(Bitmap subsampledBitmap, Image
protected static class ExifInfo {
- protected final int rotation;
- protected final boolean flipHorizontal;
+ public final int rotation;
+ public final boolean flipHorizontal;
protected ExifInfo() {
this.rotation = 0;
@@ -210,12 +241,12 @@ protected ExifInfo(int rotation, boolean flipHorizontal) {
protected static class ImageFileInfo {
- protected final ImageSize imageSize;
- protected final ExifInfo exif;
+ public final ImageSize imageSize;
+ public final ExifInfo exif;
protected ImageFileInfo(ImageSize imageSize, ExifInfo exif) {
this.imageSize = imageSize;
this.exif = exif;
}
}
-}
\ No newline at end of file
+}
diff --git a/library/src/com/nostra13/universalimageloader/core/decode/ImageDecoder.java b/library/src/main/java/com/nostra13/universalimageloader/core/decode/ImageDecoder.java
similarity index 100%
rename from library/src/com/nostra13/universalimageloader/core/decode/ImageDecoder.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/decode/ImageDecoder.java
diff --git a/library/src/com/nostra13/universalimageloader/core/decode/ImageDecodingInfo.java b/library/src/main/java/com/nostra13/universalimageloader/core/decode/ImageDecodingInfo.java
similarity index 81%
rename from library/src/com/nostra13/universalimageloader/core/decode/ImageDecodingInfo.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/decode/ImageDecodingInfo.java
index fd46c3c19..9a9ca04a1 100644
--- a/library/src/com/nostra13/universalimageloader/core/decode/ImageDecodingInfo.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/decode/ImageDecodingInfo.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2013 Sergey Tarasevich
+ * Copyright 2013-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,10 +18,10 @@
import android.annotation.TargetApi;
import android.graphics.BitmapFactory.Options;
import android.os.Build;
+
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.assist.ImageSize;
-import com.nostra13.universalimageloader.core.assist.MemoryCacheUtil;
import com.nostra13.universalimageloader.core.assist.ViewScaleType;
import com.nostra13.universalimageloader.core.download.ImageDownloader;
@@ -35,6 +35,7 @@ public class ImageDecodingInfo {
private final String imageKey;
private final String imageUri;
+ private final String originalImageUri;
private final ImageSize targetSize;
private final ImageScaleType imageScaleType;
@@ -43,11 +44,14 @@ public class ImageDecodingInfo {
private final ImageDownloader downloader;
private final Object extraForDownloader;
+ private final boolean considerExifParams;
private final Options decodingOptions;
- public ImageDecodingInfo(String imageKey, String imageUri, ImageSize targetSize, ViewScaleType viewScaleType, ImageDownloader downloader, DisplayImageOptions displayOptions) {
+ public ImageDecodingInfo(String imageKey, String imageUri, String originalImageUri, ImageSize targetSize, ViewScaleType viewScaleType,
+ ImageDownloader downloader, DisplayImageOptions displayOptions) {
this.imageKey = imageKey;
this.imageUri = imageUri;
+ this.originalImageUri = originalImageUri;
this.targetSize = targetSize;
this.imageScaleType = displayOptions.getImageScaleType();
@@ -56,6 +60,7 @@ public ImageDecodingInfo(String imageKey, String imageUri, ImageSize targetSize,
this.downloader = downloader;
this.extraForDownloader = displayOptions.getExtraForDownloader();
+ considerExifParams = displayOptions.isConsiderExifParams();
decodingOptions = new Options();
copyOptions(displayOptions.getDecodingOptions(), decodingOptions);
}
@@ -87,19 +92,24 @@ private void copyOptions11(Options srcOptions, Options destOptions) {
destOptions.inMutable = srcOptions.inMutable;
}
- /** @return Original {@linkplain MemoryCacheUtil#generateKey(String, ImageSize) image key} (used in memory cache). */
+ /** @return Original {@linkplain com.nostra13.universalimageloader.utils.MemoryCacheUtils#generateKey(String, ImageSize) image key} (used in memory cache). */
public String getImageKey() {
return imageKey;
}
- /** @return Image URI for decoding (usually image from disc cache) */
+ /** @return Image URI for decoding (usually image from disk cache) */
public String getImageUri() {
return imageUri;
}
+ /** @return The original image URI which was passed to ImageLoader */
+ public String getOriginalImageUri() {
+ return originalImageUri;
+ }
+
/**
* @return Target size for image. Decoded bitmap should close to this size according to {@linkplain ImageScaleType
- * image scale type} and {@linkplain ViewScaleType view scale type}.
+ * image scale type} and {@linkplain ViewScaleType view scale type}.
*/
public ImageSize getTargetSize() {
return targetSize;
@@ -107,7 +117,7 @@ public ImageSize getTargetSize() {
/**
* @return {@linkplain ImageScaleType Scale type for image sampling and scaling}. This parameter affects result size
- * of decoded bitmap.
+ * of decoded bitmap.
*/
public ImageScaleType getImageScaleType() {
return imageScaleType;
@@ -128,6 +138,11 @@ public Object getExtraForDownloader() {
return extraForDownloader;
}
+ /** @return true - if EXIF params of image should be considered; false - otherwise */
+ public boolean shouldConsiderExifParams() {
+ return considerExifParams;
+ }
+
/** @return Decoding options */
public Options getDecodingOptions() {
return decodingOptions;
diff --git a/library/src/com/nostra13/universalimageloader/core/display/BitmapDisplayer.java b/library/src/main/java/com/nostra13/universalimageloader/core/display/BitmapDisplayer.java
similarity index 62%
rename from library/src/com/nostra13/universalimageloader/core/display/BitmapDisplayer.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/display/BitmapDisplayer.java
index f98842084..1149111ec 100644
--- a/library/src/com/nostra13/universalimageloader/core/display/BitmapDisplayer.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/display/BitmapDisplayer.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,26 +16,28 @@
package com.nostra13.universalimageloader.core.display;
import android.graphics.Bitmap;
-import android.widget.ImageView;
import com.nostra13.universalimageloader.core.assist.LoadedFrom;
+import com.nostra13.universalimageloader.core.imageaware.ImageAware;
/**
- * Displays {@link Bitmap} in {@link ImageView}. Implementations can apply some changes to Bitmap or any animation for
- * displaying Bitmap.
+ * Displays {@link Bitmap} in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}. Implementations can
+ * apply some changes to Bitmap or any animation for displaying Bitmap.
* Implementations have to be thread-safe.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @see com.nostra13.universalimageloader.core.imageaware.ImageAware
+ * @see com.nostra13.universalimageloader.core.assist.LoadedFrom
* @since 1.5.6
*/
public interface BitmapDisplayer {
/**
- * Display bitmap in {@link ImageView}. Displayed bitmap should be returned.
+ * Displays bitmap in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}.
* NOTE: This method is called on UI thread so it's strongly recommended not to do any heavy work in it.
*
* @param bitmap Source bitmap
- * @param imageView {@linkplain ImageView Image view} to display Bitmap
+ * @param imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view} to
+ * display Bitmap
* @param loadedFrom Source of loaded image
- * @return Bitmap which was displayed in {@link ImageView}
*/
- Bitmap display(Bitmap bitmap, ImageView imageView, LoadedFrom loadedFrom);
+ void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom);
}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/display/CircleBitmapDisplayer.java b/library/src/main/java/com/nostra13/universalimageloader/core/display/CircleBitmapDisplayer.java
new file mode 100644
index 000000000..96ae88078
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/display/CircleBitmapDisplayer.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright 2015 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core.display;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+
+import com.nostra13.universalimageloader.core.assist.LoadedFrom;
+import com.nostra13.universalimageloader.core.imageaware.ImageAware;
+import com.nostra13.universalimageloader.core.imageaware.ImageViewAware;
+
+/**
+ * Can display bitmap cropped by a circle. This implementation works only with ImageViews wrapped
+ * in ImageViewAware.
+ *
+ * If this implementation doesn't meet your needs then consider
+ * RoundedImageView or
+ * CircularImageView projects for usage.
+ *
+ * @author Qualtagh, Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.9.5
+ */
+public class CircleBitmapDisplayer implements BitmapDisplayer {
+
+ protected final Integer strokeColor;
+ protected final float strokeWidth;
+
+ public CircleBitmapDisplayer() {
+ this(null);
+ }
+
+ public CircleBitmapDisplayer(Integer strokeColor) {
+ this(strokeColor, 0);
+ }
+
+ public CircleBitmapDisplayer(Integer strokeColor, float strokeWidth) {
+ this.strokeColor = strokeColor;
+ this.strokeWidth = strokeWidth;
+ }
+
+ @Override
+ public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
+ if (!(imageAware instanceof ImageViewAware)) {
+ throw new IllegalArgumentException("ImageAware should wrap ImageView. ImageViewAware is expected.");
+ }
+
+ imageAware.setImageDrawable(new CircleDrawable(bitmap, strokeColor, strokeWidth));
+ }
+
+ public static class CircleDrawable extends Drawable {
+
+ protected float radius;
+
+ protected final RectF mRect = new RectF();
+ protected final RectF mBitmapRect;
+ protected final BitmapShader bitmapShader;
+ protected final Paint paint;
+ protected final Paint strokePaint;
+ protected final float strokeWidth;
+ protected float strokeRadius;
+
+ public CircleDrawable(Bitmap bitmap, Integer strokeColor, float strokeWidth) {
+ int diameter = Math.min(bitmap.getWidth(), bitmap.getHeight());
+ radius = diameter / 2f;
+
+ bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+
+ float left = (bitmap.getWidth() - diameter) / 2f;
+ float top = (bitmap.getHeight() - diameter) / 2f;
+ mBitmapRect = new RectF(left, top, diameter, diameter);
+
+ paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setShader(bitmapShader);
+ paint.setFilterBitmap(true);
+ paint.setDither(true);
+
+ if (strokeColor == null) {
+ strokePaint = null;
+ } else {
+ strokePaint = new Paint();
+ strokePaint.setStyle(Paint.Style.STROKE);
+ strokePaint.setColor(strokeColor);
+ strokePaint.setStrokeWidth(strokeWidth);
+ strokePaint.setAntiAlias(true);
+ }
+ this.strokeWidth = strokeWidth;
+ strokeRadius = radius - strokeWidth / 2;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mRect.set(0, 0, bounds.width(), bounds.height());
+ radius = Math.min(bounds.width(), bounds.height()) / 2;
+ strokeRadius = radius - strokeWidth / 2;
+
+ // Resize the original bitmap to fit the new bound
+ Matrix shaderMatrix = new Matrix();
+ shaderMatrix.setRectToRect(mBitmapRect, mRect, Matrix.ScaleToFit.FILL);
+ bitmapShader.setLocalMatrix(shaderMatrix);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawCircle(radius, radius, radius, paint);
+ if (strokePaint != null) {
+ canvas.drawCircle(radius, radius, strokeRadius, strokePaint);
+ }
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ paint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ paint.setColorFilter(cf);
+ }
+ }
+}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/display/FadeInBitmapDisplayer.java b/library/src/main/java/com/nostra13/universalimageloader/core/display/FadeInBitmapDisplayer.java
new file mode 100644
index 000000000..686f0fd1f
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/display/FadeInBitmapDisplayer.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright 2011-2014 Sergey Tarasevich, Daniel Martí
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core.display;
+
+import android.graphics.Bitmap;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import com.nostra13.universalimageloader.core.assist.LoadedFrom;
+import com.nostra13.universalimageloader.core.imageaware.ImageAware;
+
+/**
+ * Displays image with "fade in" animation
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com), Daniel Martí
+ * @since 1.6.4
+ */
+public class FadeInBitmapDisplayer implements BitmapDisplayer {
+
+ private final int durationMillis;
+
+ private final boolean animateFromNetwork;
+ private final boolean animateFromDisk;
+ private final boolean animateFromMemory;
+
+ /**
+ * @param durationMillis Duration of "fade-in" animation (in milliseconds)
+ */
+ public FadeInBitmapDisplayer(int durationMillis) {
+ this(durationMillis, true, true, true);
+ }
+
+ /**
+ * @param durationMillis Duration of "fade-in" animation (in milliseconds)
+ * @param animateFromNetwork Whether animation should be played if image is loaded from network
+ * @param animateFromDisk Whether animation should be played if image is loaded from disk cache
+ * @param animateFromMemory Whether animation should be played if image is loaded from memory cache
+ */
+ public FadeInBitmapDisplayer(int durationMillis, boolean animateFromNetwork, boolean animateFromDisk,
+ boolean animateFromMemory) {
+ this.durationMillis = durationMillis;
+ this.animateFromNetwork = animateFromNetwork;
+ this.animateFromDisk = animateFromDisk;
+ this.animateFromMemory = animateFromMemory;
+ }
+
+ @Override
+ public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
+ imageAware.setImageBitmap(bitmap);
+
+ if ((animateFromNetwork && loadedFrom == LoadedFrom.NETWORK) ||
+ (animateFromDisk && loadedFrom == LoadedFrom.DISC_CACHE) ||
+ (animateFromMemory && loadedFrom == LoadedFrom.MEMORY_CACHE)) {
+ animate(imageAware.getWrappedView(), durationMillis);
+ }
+ }
+
+ /**
+ * Animates {@link ImageView} with "fade-in" effect
+ *
+ * @param imageView {@link ImageView} which display image in
+ * @param durationMillis The length of the animation in milliseconds
+ */
+ public static void animate(View imageView, int durationMillis) {
+ if (imageView != null) {
+ AlphaAnimation fadeImage = new AlphaAnimation(0, 1);
+ fadeImage.setDuration(durationMillis);
+ fadeImage.setInterpolator(new DecelerateInterpolator());
+ imageView.startAnimation(fadeImage);
+ }
+ }
+}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/display/RoundedBitmapDisplayer.java b/library/src/main/java/com/nostra13/universalimageloader/core/display/RoundedBitmapDisplayer.java
new file mode 100644
index 000000000..f905cd886
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/display/RoundedBitmapDisplayer.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Copyright 2011-2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core.display;
+
+import android.graphics.*;
+import android.graphics.drawable.Drawable;
+
+import com.nostra13.universalimageloader.core.assist.LoadedFrom;
+import com.nostra13.universalimageloader.core.imageaware.ImageAware;
+import com.nostra13.universalimageloader.core.imageaware.ImageViewAware;
+
+/**
+ * Can display bitmap with rounded corners. This implementation works only with ImageViews wrapped
+ * in ImageViewAware.
+ *
+ * This implementation is inspired by
+ *
+ * Romain Guy's article. It rounds images using custom drawable drawing. Original bitmap isn't changed.
+ *
+ *
+ * If this implementation doesn't meet your needs then consider
+ * RoundedImageView or
+ * CircularImageView projects for usage.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.5.6
+ */
+public class RoundedBitmapDisplayer implements BitmapDisplayer {
+
+ protected final int cornerRadius;
+ protected final int margin;
+
+ public RoundedBitmapDisplayer(int cornerRadiusPixels) {
+ this(cornerRadiusPixels, 0);
+ }
+
+ public RoundedBitmapDisplayer(int cornerRadiusPixels, int marginPixels) {
+ this.cornerRadius = cornerRadiusPixels;
+ this.margin = marginPixels;
+ }
+
+ @Override
+ public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
+ if (!(imageAware instanceof ImageViewAware)) {
+ throw new IllegalArgumentException("ImageAware should wrap ImageView. ImageViewAware is expected.");
+ }
+
+ imageAware.setImageDrawable(new RoundedDrawable(bitmap, cornerRadius, margin));
+ }
+
+ public static class RoundedDrawable extends Drawable {
+
+ protected final float cornerRadius;
+ protected final int margin;
+
+ protected final RectF mRect = new RectF(),
+ mBitmapRect;
+ protected final BitmapShader bitmapShader;
+ protected final Paint paint;
+
+ public RoundedDrawable(Bitmap bitmap, int cornerRadius, int margin) {
+ this.cornerRadius = cornerRadius;
+ this.margin = margin;
+
+ bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ mBitmapRect = new RectF (margin, margin, bitmap.getWidth() - margin, bitmap.getHeight() - margin);
+
+ paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setShader(bitmapShader);
+ paint.setFilterBitmap(true);
+ paint.setDither(true);
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mRect.set(margin, margin, bounds.width() - margin, bounds.height() - margin);
+
+ // Resize the original bitmap to fit the new bound
+ Matrix shaderMatrix = new Matrix();
+ shaderMatrix.setRectToRect(mBitmapRect, mRect, Matrix.ScaleToFit.FILL);
+ bitmapShader.setLocalMatrix(shaderMatrix);
+
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawRoundRect(mRect, cornerRadius, cornerRadius, paint);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ paint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ paint.setColorFilter(cf);
+ }
+ }
+}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/display/RoundedVignetteBitmapDisplayer.java b/library/src/main/java/com/nostra13/universalimageloader/core/display/RoundedVignetteBitmapDisplayer.java
new file mode 100644
index 000000000..bea59d354
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/display/RoundedVignetteBitmapDisplayer.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright 2013 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core.display;
+
+import android.graphics.*;
+import com.nostra13.universalimageloader.core.assist.LoadedFrom;
+import com.nostra13.universalimageloader.core.imageaware.ImageAware;
+import com.nostra13.universalimageloader.core.imageaware.ImageViewAware;
+
+/**
+ * Can display bitmap with rounded corners and vignette effect. This implementation works only with ImageViews wrapped
+ * in ImageViewAware.
+ *
+ * This implementation is inspired by
+ *
+ * Romain Guy's article. It rounds images using custom drawable drawing. Original bitmap isn't changed.
+ *
+ *
+ * If this implementation doesn't meet your needs then consider
+ * this project for usage.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.9.1
+ */
+public class RoundedVignetteBitmapDisplayer extends RoundedBitmapDisplayer {
+
+ public RoundedVignetteBitmapDisplayer(int cornerRadiusPixels, int marginPixels) {
+ super(cornerRadiusPixels, marginPixels);
+ }
+
+ @Override
+ public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
+ if (!(imageAware instanceof ImageViewAware)) {
+ throw new IllegalArgumentException("ImageAware should wrap ImageView. ImageViewAware is expected.");
+ }
+
+ imageAware.setImageDrawable(new RoundedVignetteDrawable(bitmap, cornerRadius, margin));
+ }
+
+ protected static class RoundedVignetteDrawable extends RoundedDrawable {
+
+ RoundedVignetteDrawable(Bitmap bitmap, int cornerRadius, int margin) {
+ super(bitmap, cornerRadius, margin);
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ RadialGradient vignette = new RadialGradient(
+ mRect.centerX(), mRect.centerY() * 1.0f / 0.7f, mRect.centerX() * 1.3f,
+ new int[]{0, 0, 0x7f000000}, new float[]{0.0f, 0.7f, 1.0f},
+ Shader.TileMode.CLAMP);
+
+ Matrix oval = new Matrix();
+ oval.setScale(1.0f, 0.7f);
+ vignette.setLocalMatrix(oval);
+
+ paint.setShader(new ComposeShader(bitmapShader, vignette, PorterDuff.Mode.SRC_OVER));
+ }
+ }
+}
diff --git a/library/src/com/nostra13/universalimageloader/core/display/SimpleBitmapDisplayer.java b/library/src/main/java/com/nostra13/universalimageloader/core/display/SimpleBitmapDisplayer.java
similarity index 80%
rename from library/src/com/nostra13/universalimageloader/core/display/SimpleBitmapDisplayer.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/display/SimpleBitmapDisplayer.java
index d5746ca93..8aae7de6b 100644
--- a/library/src/com/nostra13/universalimageloader/core/display/SimpleBitmapDisplayer.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/display/SimpleBitmapDisplayer.java
@@ -16,19 +16,18 @@
package com.nostra13.universalimageloader.core.display;
import android.graphics.Bitmap;
-import android.widget.ImageView;
import com.nostra13.universalimageloader.core.assist.LoadedFrom;
+import com.nostra13.universalimageloader.core.imageaware.ImageAware;
/**
- * Just displays {@link Bitmap} in {@link ImageView}
+ * Just displays {@link Bitmap} in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.5.6
*/
public final class SimpleBitmapDisplayer implements BitmapDisplayer {
@Override
- public Bitmap display(Bitmap bitmap, ImageView imageView, LoadedFrom loadedFrom) {
- imageView.setImageBitmap(bitmap);
- return bitmap;
+ public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
+ imageAware.setImageBitmap(bitmap);
}
}
\ No newline at end of file
diff --git a/library/src/com/nostra13/universalimageloader/core/download/BaseImageDownloader.java b/library/src/main/java/com/nostra13/universalimageloader/core/download/BaseImageDownloader.java
similarity index 61%
rename from library/src/com/nostra13/universalimageloader/core/download/BaseImageDownloader.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/download/BaseImageDownloader.java
index 2ea6708ea..b94e4f133 100644
--- a/library/src/com/nostra13/universalimageloader/core/download/BaseImageDownloader.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/download/BaseImageDownloader.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright 2011-2013 Sergey Tarasevich
+ * Copyright 2011-2014 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,15 +15,29 @@
*******************************************************************************/
package com.nostra13.universalimageloader.core.download;
+import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
-import android.graphics.drawable.BitmapDrawable;
+import android.media.ThumbnailUtils;
import android.net.Uri;
+import android.os.Build;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.webkit.MimeTypeMap;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
+import com.nostra13.universalimageloader.core.assist.ContentLengthInputStream;
+import com.nostra13.universalimageloader.utils.IoUtils;
-import java.io.*;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
@@ -33,7 +47,6 @@
* {@link URLConnection} is used to retrieve image stream from network.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
- * @see HttpClientImageDownloader
* @since 1.8.0
*/
public class BaseImageDownloader implements ImageDownloader {
@@ -49,17 +62,16 @@ public class BaseImageDownloader implements ImageDownloader {
protected static final int MAX_REDIRECT_COUNT = 5;
- private static final String ERROR_UNSUPPORTED_SCHEME = "UIL doesn't support scheme(protocol) by default [%s]. "
- + "You should implement this support yourself (BaseImageDownloader.getStreamFromOtherSource(...))";
+ protected static final String CONTENT_CONTACTS_URI_PREFIX = "content://com.android.contacts/";
+
+ private static final String ERROR_UNSUPPORTED_SCHEME = "UIL doesn't support scheme(protocol) by default [%s]. " + "You should implement this support yourself (BaseImageDownloader.getStreamFromOtherSource(...))";
protected final Context context;
protected final int connectTimeout;
protected final int readTimeout;
public BaseImageDownloader(Context context) {
- this.context = context.getApplicationContext();
- this.connectTimeout = DEFAULT_HTTP_CONNECT_TIMEOUT;
- this.readTimeout = DEFAULT_HTTP_READ_TIMEOUT;
+ this(context, DEFAULT_HTTP_CONNECT_TIMEOUT, DEFAULT_HTTP_READ_TIMEOUT);
}
public BaseImageDownloader(Context context, int connectTimeout, int readTimeout) {
@@ -99,27 +111,51 @@ public InputStream getStream(String imageUri, Object extra) throws IOException {
* URL.
*/
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
- HttpURLConnection conn = createConnection(imageUri);
+ HttpURLConnection conn = createConnection(imageUri, extra);
int redirectCount = 0;
while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
- conn = createConnection(conn.getHeaderField("Location"));
+ conn = createConnection(conn.getHeaderField("Location"), extra);
redirectCount++;
}
- return new BufferedInputStream(conn.getInputStream(), BUFFER_SIZE);
+ InputStream imageStream;
+ try {
+ imageStream = conn.getInputStream();
+ } catch (IOException e) {
+ // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
+ IoUtils.readAndCloseStream(conn.getErrorStream());
+ throw e;
+ }
+ if (!shouldBeProcessed(conn)) {
+ IoUtils.closeSilently(imageStream);
+ throw new IOException("Image request failed with response code " + conn.getResponseCode());
+ }
+
+ return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
+ }
+
+ /**
+ * @param conn Opened request connection (response code is available)
+ * @return true - if data from connection is correct and should be read and processed;
+ * false - if response contains irrelevant data and shouldn't be processed
+ * @throws IOException
+ */
+ protected boolean shouldBeProcessed(HttpURLConnection conn) throws IOException {
+ return conn.getResponseCode() == 200;
}
/**
* Create {@linkplain HttpURLConnection HTTP connection} for incoming URL
*
- * @param url URL to connect to
- * @return {@linkplain HttpURLConnection Connection} for incoming URL. Connection isn't established so it still
- * configurable.
+ * @param url URL to connect to
+ * @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object)
+ * DisplayImageOptions.extraForDownloader(Object)}; can be null
+ * @return {@linkplain HttpURLConnection Connection} for incoming URL. Connection isn't established so it still configurable.
* @throws IOException if some I/O error occurs during network request or if no InputStream could be created for
* URL.
*/
- protected HttpURLConnection createConnection(String url) throws IOException {
+ protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
conn.setConnectTimeout(connectTimeout);
@@ -138,7 +174,26 @@ protected HttpURLConnection createConnection(String url) throws IOException {
*/
protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {
String filePath = Scheme.FILE.crop(imageUri);
- return new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE);
+ if (isVideoFileUri(imageUri)) {
+ return getVideoThumbnailStream(filePath);
+ } else {
+ BufferedInputStream imageStream = new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE);
+ return new ContentLengthInputStream(imageStream, (int) new File(filePath).length());
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.FROYO)
+ private InputStream getVideoThumbnailStream(String filePath) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
+ Bitmap bitmap = ThumbnailUtils
+ .createVideoThumbnail(filePath, MediaStore.Images.Thumbnails.FULL_SCREEN_KIND);
+ if (bitmap != null) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ bitmap.compress(CompressFormat.PNG, 0, bos);
+ return new ByteArrayInputStream(bos.toByteArray());
+ }
+ }
+ return null;
}
/**
@@ -152,10 +207,34 @@ protected InputStream getStreamFromFile(String imageUri, Object extra) throws IO
*/
protected InputStream getStreamFromContent(String imageUri, Object extra) throws FileNotFoundException {
ContentResolver res = context.getContentResolver();
+
Uri uri = Uri.parse(imageUri);
+ if (isVideoContentUri(uri)) { // video thumbnail
+ Long origId = Long.valueOf(uri.getLastPathSegment());
+ Bitmap bitmap = MediaStore.Video.Thumbnails
+ .getThumbnail(res, origId, MediaStore.Images.Thumbnails.MINI_KIND, null);
+ if (bitmap != null) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ bitmap.compress(CompressFormat.PNG, 0, bos);
+ return new ByteArrayInputStream(bos.toByteArray());
+ }
+ } else if (imageUri.startsWith(CONTENT_CONTACTS_URI_PREFIX)) { // contacts photo
+ return getContactPhotoStream(uri);
+ }
+
return res.openInputStream(uri);
}
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ protected InputStream getContactPhotoStream(Uri uri) {
+ ContentResolver res = context.getContentResolver();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return ContactsContract.Contacts.openContactPhotoInputStream(res, uri, true);
+ } else {
+ return ContactsContract.Contacts.openContactPhotoInputStream(res, uri);
+ }
+ }
+
/**
* Retrieves {@link InputStream} of image by URI (image is located in assets of application).
*
@@ -181,12 +260,7 @@ protected InputStream getStreamFromAssets(String imageUri, Object extra) throws
protected InputStream getStreamFromDrawable(String imageUri, Object extra) {
String drawableIdString = Scheme.DRAWABLE.crop(imageUri);
int drawableId = Integer.parseInt(drawableIdString);
- BitmapDrawable drawable = (BitmapDrawable) context.getResources().getDrawable(drawableId);
- Bitmap bitmap = drawable.getBitmap();
-
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- bitmap.compress(CompressFormat.PNG, 0, os);
- return new ByteArrayInputStream(os.toByteArray());
+ return context.getResources().openRawResource(drawableId);
}
/**
@@ -205,4 +279,15 @@ protected InputStream getStreamFromDrawable(String imageUri, Object extra) {
protected InputStream getStreamFromOtherSource(String imageUri, Object extra) throws IOException {
throw new UnsupportedOperationException(String.format(ERROR_UNSUPPORTED_SCHEME, imageUri));
}
-}
\ No newline at end of file
+
+ private boolean isVideoContentUri(Uri uri) {
+ String mimeType = context.getContentResolver().getType(uri);
+ return mimeType != null && mimeType.startsWith("video/");
+ }
+
+ private boolean isVideoFileUri(String uri) {
+ String extension = MimeTypeMap.getFileExtensionFromUrl(Uri.encode(uri));
+ String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ return mimeType != null && mimeType.startsWith("video/");
+ }
+}
diff --git a/library/src/com/nostra13/universalimageloader/core/download/ImageDownloader.java b/library/src/main/java/com/nostra13/universalimageloader/core/download/ImageDownloader.java
similarity index 97%
rename from library/src/com/nostra13/universalimageloader/core/download/ImageDownloader.java
rename to library/src/main/java/com/nostra13/universalimageloader/core/download/ImageDownloader.java
index 1df75dc5a..3fa0fb5cc 100644
--- a/library/src/com/nostra13/universalimageloader/core/download/ImageDownloader.java
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/download/ImageDownloader.java
@@ -19,6 +19,7 @@
import java.io.IOException;
import java.io.InputStream;
+import java.util.Locale;
/**
* Provides retrieving of {@link InputStream} of image by URI.
@@ -70,7 +71,7 @@ public static Scheme ofUri(String uri) {
}
private boolean belongsTo(String uri) {
- return uri.startsWith(uriPrefix);
+ return uri.toLowerCase(Locale.US).startsWith(uriPrefix);
}
/** Appends scheme to incoming path */
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/ImageAware.java b/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/ImageAware.java
new file mode 100644
index 000000000..ba1f4cb83
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/ImageAware.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright 2013-2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core.imageaware;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import com.nostra13.universalimageloader.core.assist.ViewScaleType;
+
+/**
+ * Represents image aware view which provides all needed properties and behavior for image processing and displaying
+ * through {@link com.nostra13.universalimageloader.core.ImageLoader ImageLoader}.
+ * It can wrap any Android {@link android.view.View View} which can be accessed by {@link #getWrappedView()}. Wrapped
+ * view is returned in {@link com.nostra13.universalimageloader.core.listener.ImageLoadingListener ImageLoadingListener}'s
+ * callbacks.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @see ViewAware
+ * @see ImageViewAware
+ * @see NonViewAware
+ * @since 1.9.0
+ */
+public interface ImageAware {
+ /**
+ * Returns width of image aware view. This value is used to define scale size for original image.
+ * Can return 0 if width is undefined.
+ * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
+ */
+ int getWidth();
+
+ /**
+ * Returns height of image aware view. This value is used to define scale size for original image.
+ * Can return 0 if height is undefined.
+ * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
+ */
+ int getHeight();
+
+ /**
+ * Returns {@linkplain com.nostra13.universalimageloader.core.assist.ViewScaleType scale type} which is used for
+ * scaling image for this image aware view. Must NOT return null.
+ */
+ ViewScaleType getScaleType();
+
+ /**
+ * Returns wrapped Android {@link android.view.View View}. Can return null if no view is wrapped or view was
+ * collected by GC.
+ * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
+ */
+ View getWrappedView();
+
+ /**
+ * Returns a flag whether image aware view is collected by GC or whatsoever. If so then ImageLoader stop processing
+ * of task for this image aware view and fires
+ * {@link com.nostra13.universalimageloader.core.listener.ImageLoadingListener#onLoadingCancelled(String,
+ * android.view.View) ImageLoadingListener#onLoadingCancelled(String, View)} callback.
+ * Mey be called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
+ *
+ * @return true - if view is collected by GC and ImageLoader should stop processing this image aware view;
+ * false - otherwise
+ */
+ boolean isCollected();
+
+ /**
+ * Returns ID of image aware view. Point of ID is similar to Object's hashCode. This ID should be unique for every
+ * image view instance and should be the same for same instances. This ID identifies processing task in ImageLoader
+ * so ImageLoader won't process two image aware views with the same ID in one time. When ImageLoader get new task
+ * it cancels old task with this ID (if any) and starts new task.
+ *
+ * It's reasonable to return hash code of wrapped view (if any) to prevent displaying non-actual images in view
+ * because of view re-using.
+ */
+ int getId();
+
+ /**
+ * Sets image drawable into this image aware view.
+ * Displays drawable in this image aware view
+ * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageForEmptyUri(
+ *android.graphics.drawable.Drawable) for empty Uri},
+ * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageOnLoading(
+ *android.graphics.drawable.Drawable) on loading} or
+ * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageOnFail(
+ *android.graphics.drawable.Drawable) on loading fail}. These drawables can be specified in
+ * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions display options}.
+ * Also can be called in {@link com.nostra13.universalimageloader.core.display.BitmapDisplayer BitmapDisplayer}.< br />
+ * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
+ *
+ * @return true if drawable was set successfully; false - otherwise
+ */
+ boolean setImageDrawable(Drawable drawable);
+
+ /**
+ * Sets image bitmap into this image aware view.
+ * Displays loaded and decoded image {@link android.graphics.Bitmap} in this image view aware.
+ * Actually it's used only in
+ * {@link com.nostra13.universalimageloader.core.display.BitmapDisplayer BitmapDisplayer}.< br />
+ * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
+ *
+ * @return true if bitmap was set successfully; false - otherwise
+ */
+ boolean setImageBitmap(Bitmap bitmap);
+}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/ImageViewAware.java b/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/ImageViewAware.java
new file mode 100644
index 000000000..77a312a2b
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/ImageViewAware.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright 2013-2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core.imageaware;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.ImageView;
+import com.nostra13.universalimageloader.core.assist.ViewScaleType;
+import com.nostra13.universalimageloader.utils.L;
+
+import java.lang.reflect.Field;
+
+/**
+ * Wrapper for Android {@link android.widget.ImageView ImageView}. Keeps weak reference of ImageView to prevent memory
+ * leaks.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.9.0
+ */
+public class ImageViewAware extends ViewAware {
+
+ /**
+ * Constructor.
+ * References {@link #ImageViewAware(android.widget.ImageView, boolean) ImageViewAware(imageView, true)}.
+ *
+ * @param imageView {@link android.widget.ImageView ImageView} to work with
+ */
+ public ImageViewAware(ImageView imageView) {
+ super(imageView);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param imageView {@link android.widget.ImageView ImageView} to work with
+ * @param checkActualViewSize true - then {@link #getWidth()} and {@link #getHeight()} will check actual
+ * size of ImageView. It can cause known issues like
+ * this.
+ * But it helps to save memory because memory cache keeps bitmaps of actual (less in
+ * general) size.
+ *
+ * false - then {@link #getWidth()} and {@link #getHeight()} will NOT
+ * consider actual size of ImageView, just layout parameters.
If you set 'false'
+ * it's recommended 'android:layout_width' and 'android:layout_height' (or
+ * 'android:maxWidth' and 'android:maxHeight') are set with concrete values. It helps to
+ * save memory.
+ *
+ */
+ public ImageViewAware(ImageView imageView, boolean checkActualViewSize) {
+ super(imageView, checkActualViewSize);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * 3) Get maxWidth.
+ */
+ @Override
+ public int getWidth() {
+ int width = super.getWidth();
+ if (width <= 0) {
+ ImageView imageView = (ImageView) viewRef.get();
+ if (imageView != null) {
+ width = imageView.getMaxWidth();
+ }
+ }
+ return width;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * 3) Get maxHeight
+ */
+ @Override
+ public int getHeight() {
+ int height = super.getHeight();
+ if (height <= 0) {
+ ImageView imageView = (ImageView) viewRef.get();
+ if (imageView != null) {
+ height = imageView.getMaxHeight();
+ }
+ }
+ return height;
+ }
+
+ @Override
+ public ViewScaleType getScaleType() {
+ ImageView imageView = (ImageView) viewRef.get();
+ if (imageView != null) {
+ return ViewScaleType.fromImageView(imageView);
+ }
+ return super.getScaleType();
+ }
+
+ @Override
+ public ImageView getWrappedView() {
+ return (ImageView) super.getWrappedView();
+ }
+
+ @Override
+ protected void setImageDrawableInto(Drawable drawable, View view) {
+ ((ImageView) view).setImageDrawable(drawable);
+ if (drawable instanceof AnimationDrawable) {
+ ((AnimationDrawable)drawable).start();
+ }
+ }
+
+ @Override
+ protected void setImageBitmapInto(Bitmap bitmap, View view) {
+ ((ImageView) view).setImageBitmap(bitmap);
+ }
+}
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/NonViewAware.java b/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/NonViewAware.java
new file mode 100644
index 000000000..1975bdfa3
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/NonViewAware.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright 2013-2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core.imageaware;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.view.View;
+import com.nostra13.universalimageloader.core.assist.ImageSize;
+import com.nostra13.universalimageloader.core.assist.ViewScaleType;
+
+/**
+ * ImageAware which provides needed info for processing of original image but do nothing for displaying image. It's
+ * used when user need just load and decode image and get it in {@linkplain
+ * com.nostra13.universalimageloader.core.listener.ImageLoadingListener#onLoadingComplete(String, android.view.View,
+ * android.graphics.Bitmap) callback}.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.9.0
+ */
+public class NonViewAware implements ImageAware {
+
+ protected final String imageUri;
+ protected final ImageSize imageSize;
+ protected final ViewScaleType scaleType;
+
+ public NonViewAware(ImageSize imageSize, ViewScaleType scaleType) {
+ this(null, imageSize, scaleType);
+ }
+
+ public NonViewAware(String imageUri, ImageSize imageSize, ViewScaleType scaleType) {
+ if (imageSize == null) throw new IllegalArgumentException("imageSize must not be null");
+ if (scaleType == null) throw new IllegalArgumentException("scaleType must not be null");
+
+ this.imageUri = imageUri;
+ this.imageSize = imageSize;
+ this.scaleType = scaleType;
+ }
+
+ @Override
+ public int getWidth() {
+ return imageSize.getWidth();
+ }
+
+ @Override
+ public int getHeight() {
+ return imageSize.getHeight();
+ }
+
+ @Override
+ public ViewScaleType getScaleType() {
+ return scaleType;
+ }
+
+ @Override
+ public View getWrappedView() {
+ return null;
+ }
+
+ @Override
+ public boolean isCollected() {
+ return false;
+ }
+
+ @Override
+ public int getId() {
+ return TextUtils.isEmpty(imageUri) ? super.hashCode() : imageUri.hashCode();
+ }
+
+ @Override
+ public boolean setImageDrawable(Drawable drawable) { // Do nothing
+ return true;
+ }
+
+ @Override
+ public boolean setImageBitmap(Bitmap bitmap) { // Do nothing
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/ViewAware.java b/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/ViewAware.java
new file mode 100644
index 000000000..af6871531
--- /dev/null
+++ b/library/src/main/java/com/nostra13/universalimageloader/core/imageaware/ViewAware.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright 2014 Sergey Tarasevich
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.nostra13.universalimageloader.core.imageaware;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Looper;
+import android.view.View;
+import android.view.ViewGroup;
+import com.nostra13.universalimageloader.core.assist.ViewScaleType;
+import com.nostra13.universalimageloader.utils.L;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+
+/**
+ * Wrapper for Android {@link android.view.View View}. Keeps weak reference of View to prevent memory leaks.
+ *
+ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
+ * @since 1.9.2
+ */
+public abstract class ViewAware implements ImageAware {
+
+ public static final String WARN_CANT_SET_DRAWABLE = "Can't set a drawable into view. You should call ImageLoader on UI thread for it.";
+ public static final String WARN_CANT_SET_BITMAP = "Can't set a bitmap into view. You should call ImageLoader on UI thread for it.";
+
+ protected Reference viewRef;
+ protected boolean checkActualViewSize;
+
+ /**
+ * Constructor.