diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 000000000..405a2b306 --- /dev/null +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -0,0 +1,10 @@ +name: "Validate Gradle Wrapper" +on: [push, pull_request] + +jobs: + validation: + name: "Validation" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 diff --git a/.gitignore b/.gitignore index 32b867240..7abf0aabe 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,11 @@ release.properties out/ # Mac -.DS_Store \ No newline at end of file +.DS_Store + +# Ignore gradle files +.gradle/ +build/ + +# Private +signing.properties \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index ddc75efeb..d91b3d727 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,9 @@ -language: java -before_install: - # fix the 64bit box - - sudo apt-get update -qq - - if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs ia32-libs-multiarch; fi - # download the latest android sdk and unzip - - wget http://dl.google.com/android/android-sdk_r21.1-linux.tgz - - tar -zxf android-sdk_r21.1-linux.tgz - # setup your ANDROID_HOME and PATH environment variables - # use ~/builds/[Github username]/[project]/android-sdk-linux - - export ANDROID_HOME=`pwd`/android-sdk-linux - - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools - # Download the android sdk we need - # Android Platform tools, required to compile - - TOOLS=$(android list sdk --no-ui | grep "Android SDK Platform-tools" | cut -d"-" -f1) - - android update sdk --filter "$TOOLS" --no-ui --force - # API 16 stuff which needs to be provided - - SDK=$(android list sdk --no-ui | grep ", API 16," | cut -d"-" -f1) - - android update sdk --filter "$SDK" --no-ui --force \ No newline at end of file +sudo: false +language: android +android: + components: + - platform-tools + - tools + - build-tools-28.0.3 + - android-28 + - extra diff --git a/CHANGELOG.md b/CHANGELOG.md index a8b68afdf..3e2ed348a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,94 @@ Change Log === +v1.9.5 *(27.11.2015)* +--- + * **New API:** + * `ImageLoader.displayImage(..., ImageSize targetImageSize, ...)` + * CircleBitmapDisplayer + * Better rendering of scaled images. + * Fixed bugs: + * inPurgeable and inInputShareable causes file descriptor leak on KitKat ([#1020](https://github.com/nostra13/Android-Universal-Image-Loader/issues/1020)) + * markSupported() ([#1026](https://github.com/nostra13/Android-Universal-Image-Loader/issues/1026)) + +v1.9.4 *(29.05.2015)* +--- + * **New API:** + * `ImageLoader.setDefaultLoadingListener(ImageLoadingListener)` + * "Disc -> Disk" migration (deleted DiscCacheAware, MemoryCacheAware) + * Video thumbnails support (`file://...`) + * Fixed 0-length files problem + +v1.9.3 *(06.09.2014)* +--- + * Introduced `ImageScaleType.NONE_SAFE` + * Video thumbnails support (`content://...`) + * Animated drawables support (for `.showImageOnLoading()`, `.showImageOnFail()`, `.showImageForEmptyUri()`) + * Fixed bugs: + * `loadImageSync(...)` bug ([#636](https://github.com/nostra13/Android-Universal-Image-Loader/issues/636)) + * NPE if no free space while init disk cache + * "Bitmap too large ..." for all ImageScaleTypes + * contacts photo considering + +v1.9.2 *(24.05.2014)* +--- + * New Disk cache API (preparing renaming `disc` -> `disk`) + * ImageLoader can be called out of the Main thread. Callback will be delivered on separate thread. + * Prevented broken image files (#511) + * Interrupt non-actual tasks + * `LruDiscCache` is default limited cache + * Renaming: `ImageNonViewAware` -> `NonViewAware`. Extracted `ViewAware` from `ImageViewAware`. + * Introduced `DiskCache` and `MemoryCache` interfaces instead of deprecated `DiscCacheAware` and `MemoryCacheAware`. + * Removed `LimitedDiscCache`, `TotalSizeLimitedDiscCache`, `FileCountLimitedDiscCache`. Use `LruDisckCache` instead. + +v1.9.1 *(27.12.2013)* +--- + * **Changed API:** + * `BitmapDisplayer.display(...) : Bitmap` -> `BitmapDisplayer.display(...) : void` + * **New API:** + * Added possibility to listen image loading progress by listener - `ImageLoadingProgressListener` + * Non-actual downloads are interrupted (if loaded less than 75%) + * Re-designed `RoundedBitmapDisplayer`. Added `RoundedVignetteBitmapDisplayer`. + **NOTE:** New `RoundedBitmapDisplayer`'s behaviour can vary from old one. Also consider ["RoundedImageView" project](https://github.com/vinc3m1/RoundedImageView) for usage if new `RoundedBitmapDisplayer` doesn't work for you. + * Maximum GL texture size is considered while decode images ([#281](https://github.com/nostra13/Android-Universal-Image-Loader/issues/281)) + * `loadImage(...)` call cancels previous task for the same image URI ([#475](https://github.com/nostra13/Android-Universal-Image-Loader/issues/475)) + * Fixed StrictMode warning `Explicit termination method 'close' not called` ([#482](https://github.com/nostra13/Android-Universal-Image-Loader/issues/482)) + * `LruMemoryCache` is default memory cache for Android < 2.3 too. + +v1.9.0 *(27.11.2013)* +--- + * **Changed API:** + * `BitmapDisplayer.display(..., ImageView, ...)` -> `BitmapDisplayer.display(..., ImageAware, ...)` + * **New API:** + * `ImageAware` + * `ImageLoader.displayImage(..., ImageAware, ...)` + * `ImageLoader.loadImageSync(...) : Bitmap` for synchronous loading of image + * `DisplayImageOptions.considerExifParams(boolean)` + * `DisplayImageOptions.showImageOnLoading(...)` (instead of `.showStubImage(...)`) + * `ImageLoader` can process any view (or any other object) which implements `ImageAware` interface. E.g. `ImageViewAware` is adapter of `ImageView` for `ImageAware` + * EXIF parameters of image are not considered by default anymore. Use new API to enable it. + * Optimized image loading (prevented double-request on image loading, reuse existing stream) + * Fixed bugs: + * `loadImage(...)` bug (frequent `onLoadingCancelled()`) ([#356](https://github.com/nostra13/Android-Universal-Image-Loader/issues/356)) + * Prevented NPE if `Context.getCacheDir()` returns `null` ([#392](https://github.com/nostra13/Android-Universal-Image-Loader/issues/392)) + +v1.8.6 *(25.07.2013)* +--- + * **Changed API:** `ImageLoaderConfiguration.enableLogging()` -> `ImageLoaderConfiguration.writeDebugLogs()` + * **Fixed memory leak** ([#263](https://github.com/nostra13/Android-Universal-Image-Loader/issues/263)) + * Added the bug of `loadImage(...)` method (`onLoadingCancelled()` is fired always) :) + v1.8.5 *(30.06.2013)* --- * **Changed API:** `ImageLoaderConfiguration.discCacheExtraOptions(...)` -> `ImageLoaderConfiguration.discCacheExtraOptions(..., BitmapProcessor)` ([#314](https://github.com/nostra13/Android-Universal-Image-Loader/issues/314)) - * Introduce `ImageLoaderConfiguration.memoryCacheSizePercentage(int)` ([#279](https://github.com/nostra13/Android-Universal-Image-Loader/issues/279)) - * Introduced `DisplayImageOptions.cacheInMemory(boolean)`, `.cacheOnDisc(boolean)`, `.resetViewBeforeLoading(boolean)` ([#252](https://github.com/nostra13/Android-Universal-Image-Loader/issues/252)) + * **New API:** + * `ImageLoaderConfiguration.memoryCacheSizePercentage(int)` ([#279](https://github.com/nostra13/Android-Universal-Image-Loader/issues/279)) + * `DisplayImageOptions.cacheInMemory(boolean)`, `.cacheOnDisc(boolean)`, `.resetViewBeforeLoading(boolean)` ([#252](https://github.com/nostra13/Android-Universal-Image-Loader/issues/252)) * Added `LoadedFrom` flag to `BitmapDisplayer.display(..., LoadedFrom)` about image source ([#149](https://github.com/nostra13/Android-Universal-Image-Loader/issues/149), [#239](https://github.com/nostra13/Android-Universal-Image-Loader/issues/239)) * Added `L.disableLogging()` and `L.enableLogging()` to off/on logs completely ([#270](https://github.com/nostra13/Android-Universal-Image-Loader/issues/270)) * Prevent image decoding if image is reused ([#247](https://github.com/nostra13/Android-Universal-Image-Loader/issues/247)) * Not set cache dir on SD card if no appropriate permission ([#311](https://github.com/nostra13/Android-Universal-Image-Loader/issues/311)) - * Increased buffer size for image downlaods (8 KB -> 32 KB) ([#249](https://github.com/nostra13/Android-Universal-Image-Loader/issues/249)) + * Increased buffer size for image downloads (8 KB -> 32 KB) ([#249](https://github.com/nostra13/Android-Universal-Image-Loader/issues/249)) * Fixed bugs: * Prevent recycling of cached in memory images ([#259](https://github.com/nostra13/Android-Universal-Image-Loader/issues/259)) * ConcurrentModificationException in `LruMemoryCache` ([#265](https://github.com/nostra13/Android-Universal-Image-Loader/issues/265)) @@ -23,7 +101,7 @@ v1.8.5 *(30.06.2013)* v1.8.4 *(13.04.2013)* --- * Travis CI, added Unit tests ([#189](https://github.com/nostra13/Android-Universal-Image-Loader/issues/189)) - * Introduced `DisplayImageOptions.handler(Handler)` ([#231](https://github.com/nostra13/Android-Universal-Image-Loader/issues/231)) + * **New API:** `DisplayImageOptions.handler(Handler)` ([#231](https://github.com/nostra13/Android-Universal-Image-Loader/issues/231)) * Fixed bugs: * `ConcurrentModificationException` in `BaseMemoryCache` ([#229](https://github.com/nostra13/Android-Universal-Image-Loader/issues/229)) * `NullPointerException` in `LimitedDiscCache` ([#234](https://github.com/nostra13/Android-Universal-Image-Loader/issues/234)) @@ -34,8 +112,9 @@ v1.8.3 *(31.03.2013)* --- * Android 2.0+ support * Added EXIF orientation support ([#172](https://github.com/nostra13/Android-Universal-Image-Loader/issues/172)) - * Introduced `ImageLoaderConfiguration.imageDecoder(ImageDecoder)` - * Introduced `DisplayImageOptions.decodingOptions(BitmapFactory.Options)` + * **New API:** + * `ImageLoaderConfiguration.imageDecoder(ImageDecoder)` + * `DisplayImageOptions.decodingOptions(BitmapFactory.Options)` * Handled disc cache non-availability * Use `LruMemoryCache` as default memory cache for API >= 9, `LRULimitedMemoryCache` - for API < 9. Default memory cache size - 1/8 of available app memory. * Improved `LimitedDiscCache` and `FuzzyKeyMemoryCache` performance @@ -49,8 +128,9 @@ v1.8.2 *(13.03.2013)* * `ImageDownloader.getStream***(URI, ...)` -> `ImageDownloader.getStream***(String, ...)` * Made `FailReason` as a class instead of enum. Can be used in switches: `FailReason.getType()` * Removed `ImageLoader.offOutOfMemoryHandling()`. ImageLoader doesn't handle OutOfMemoryError by default anymore (but still catches it for callbacks). - * Introduced `ImageLoader.taskExecutor(Executor)` and `ImageLoader.taskExecutorForCachedImages(Executor)` ([#187](https://github.com/nostra13/Android-Universal-Image-Loader/issues/187)) - * Introduced `ImageLoader.destroy()` + * **New API:** + * `ImageLoader.taskExecutor(Executor)` and `ImageLoader.taskExecutorForCachedImages(Executor)` ([#187](https://github.com/nostra13/Android-Universal-Image-Loader/issues/187)) + * `ImageLoader.destroy()` * Handled SD card unmount ([#170](https://github.com/nostra13/Android-Universal-Image-Loader/issues/170)) * Added `Scheme` class * Fixed bugs: @@ -64,8 +144,9 @@ v1.8.1 *(08.03.2013)* * **Changed API:** * `ImageLoader.denyNetworkDownloads()` -> `ImageLoader.denyNetworkDownloads(true)` * `ImageLoader.allowNetworkDownloads()` -> `ImageLoader.denyNetworkDownloads(false)` - * Introduced `ImageLoader.denyNetworkDownloads(boolean)` - * Introduced `ImageLoader.handleSlowNetwork(boolean)`. `FlushedInsputStream` isn't used for downloads by default. + * **New API:** + * `ImageLoader.denyNetworkDownloads(boolean)` + * `ImageLoader.handleSlowNetwork(boolean)`. `FlushedInsputStream` isn't used for downloads by default. * Handled HTTP(S) redirects * Added `LruMemoryCache` (based on Android's LruCache), uses only strong references. * Fixed `DisplayImageOptions.cloneFrom(...)` ([#173](https://github.com/nostra13/Android-Universal-Image-Loader/issues/173)) @@ -85,12 +166,13 @@ v1.8.0 *(10.02.2013)* * Renaming: `FileUtil` -> `IoUtil` * Removed deprecated `ImageScaleType.POWER_OF_2` and `ImageScaleType.EXACT` * Support of "content://", "assets://", "drawable://" URI schemes out of the box ([#162](https://github.com/nostra13/Android-Universal-Image-Loader/issues/162)) - * Introduced `DisplayImageOptions.showImageOnFail(int)` - * Introduced `DisplayImageOptions.preProcessor(BitmapProcessor)` and `DisplayImageOptions.postProcessor(BitmapProcessor)` ([#151](https://github.com/nostra13/Android-Universal-Image-Loader/issues/151)) - * Introduced `DisplayImageOptions.extraForDownloader(Object)`, allows to pass auxiliary object which will be passed to `ImageDownloader.getStream(URI, Object)` ([#150](https://github.com/nostra13/Android-Universal-Image-Loader/issues/150)) - * Introduced `ImageLoader.denyNetworkDownloads()` and `ImageLoader.allowNetworkDownloads()` ([#148](https://github.com/nostra13/Android-Universal-Image-Loader/issues/148)) - * Introduced `FailReason.UNSUPPORTED_URI_SCHEME` and `FailReason.NETWORK_DENIED` - * Introduced `ImageScaleType.NONE` + * **New API:** + * `DisplayImageOptions.showImageOnFail(int)` + * `DisplayImageOptions.preProcessor(BitmapProcessor)` and `DisplayImageOptions.postProcessor(BitmapProcessor)` ([#151](https://github.com/nostra13/Android-Universal-Image-Loader/issues/151)) + * `DisplayImageOptions.extraForDownloader(Object)`, allows to pass auxiliary object which will be passed to `ImageDownloader.getStream(URI, Object)` ([#150](https://github.com/nostra13/Android-Universal-Image-Loader/issues/150)) + * `ImageLoader.denyNetworkDownloads()` and `ImageLoader.allowNetworkDownloads()` ([#148](https://github.com/nostra13/Android-Universal-Image-Loader/issues/148)) + * `FailReason.UNSUPPORTED_URI_SCHEME` and `FailReason.NETWORK_DENIED` + * `ImageScaleType.NONE` * Added `DiscCacheUtil` * Prepared ImageLoader to be extandable for creation of multiple instances ([#158](https://github.com/nostra13/Android-Universal-Image-Loader/issues/158)) * Fixed bug "Images aren't loaded after "Clear Cache" in app info" ([#168](https://github.com/nostra13/Android-Universal-Image-Loader/issues/168)) @@ -101,28 +183,30 @@ v1.7.1 *(31.01.2013)* * Avoid I/O operations on the main thread, prevented ANR ([#129](https://github.com/nostra13/Android-Universal-Image-Loader/issues/129), [#154](https://github.com/nostra13/Android-Universal-Image-Loader/issues/154)) * Correctly handled every ImageView's scale type in `RoundedBitmapDisplayer` ([#70](https://github.com/nostra13/Android-Universal-Image-Loader/issues/70)) * Prevented slow precaching modified date in LimitedAgeDiscCache constructor (for large number of images in cache) ([#141](https://github.com/nostra13/Android-Universal-Image-Loader/issues/141)) - * Introduced `ImageLoader.isInited()` method. Throw IllegalStateException on `displayImage(...)`, `loadImage(...)`, `getMemoryCache()`, `clearMemoryCache()`, `getDiscCache()`, `clearDiscCache()` calls if ImageLoader isn't inited with config. + * **New API:** `ImageLoader.isInited()` method. Throw IllegalStateException on `displayImage(...)`, `loadImage(...)`, `getMemoryCache()`, `clearMemoryCache()`, `getDiscCache()`, `clearDiscCache()` calls if ImageLoader isn't inited with config. * Closed OutputStream after Bitmap compressing ([#115](https://github.com/nostra13/Android-Universal-Image-Loader/issues/115)) * Sample: Refactored resources v1.7.0 *(27.11.2012)* --- * Maven support - * Introduced `ImageLoader.pause()` and `ImageLoader.resume()` ([#106](https://github.com/nostra13/Android-Universal-Image-Loader/issues/106)) - * Introduced `PauseOnScrollListener` (instead of `OnScrollSmartOptions`) for convenient pause/resume ImageLoader on scroll/fling in list views ([#106](https://github.com/nostra13/Android-Universal-Image-Loader/issues/106)) + * **New API:** + * `ImageLoader.pause()` and `ImageLoader.resume()` ([#106](https://github.com/nostra13/Android-Universal-Image-Loader/issues/106)) + * `PauseOnScrollListener` (instead of `OnScrollSmartOptions`) for convenient pause/resume ImageLoader on scroll/fling in list views ([#106](https://github.com/nostra13/Android-Universal-Image-Loader/issues/106)) * Prevented consuming of lot of memory by cacheKeysForImageViews ([#108](https://github.com/nostra13/Android-Universal-Image-Loader/issues/108)) v1.6.4 *(20.11.2012)* --- - * Introduced `DisplayImageOptions.bitmapConfig(Bitmap.Config)` ([#101](https://github.com/nostra13/Android-Universal-Image-Loader/issues/101)) - * Introduced `DisplayImageOptions.delayBeforeLoading(int)` ([#103](https://github.com/nostra13/Android-Universal-Image-Loader/issues/103)) - * Introduced `OnScrollSmartOptions` for convenient control of loading delay on fling in ListViews, GridViews + * **New API:** + * `DisplayImageOptions.bitmapConfig(Bitmap.Config)` ([#101](https://github.com/nostra13/Android-Universal-Image-Loader/issues/101)) + * `DisplayImageOptions.delayBeforeLoading(int)` ([#103](https://github.com/nostra13/Android-Universal-Image-Loader/issues/103)) + * `OnScrollSmartOptions` for convenient control of loading delay on fling in ListViews, GridViews * Added `FadeInBitmapDisplayer` * Prevented recycling of using Bitmap ([#101](https://github.com/nostra13/Android-Universal-Image-Loader/issues/101)) v1.6.3 *(03.11.2012)* --- - * Introduced `ImageLoaderConfiguration.tasksProcessingOrder(QueueProcessingType)` ([#89](https://github.com/nostra13/Android-Universal-Image-Loader/issues/89)) + * **New API:** `ImageLoaderConfiguration.tasksProcessingOrder(QueueProcessingType)` ([#89](https://github.com/nostra13/Android-Universal-Image-Loader/issues/89)) * Added `MemoryCacheUtil` for work with memory cache * Fixed calculation of size the original image is needed scale to ([#93](https://github.com/nostra13/Android-Universal-Image-Loader/issues/93)) * Allowed to create multiple `ImageLoader` instances ([#92](https://github.com/nostra13/Android-Universal-Image-Loader/issues/92)) @@ -138,19 +222,19 @@ v1.6.1 *(02.10.2012)* * **Changed API:** Renaming: * `ImageScaleType.POWER_OF_2` -> `IN_SAMPLE_POWER_OF_2` * `ImageScaleType.EXACT` -> `IN_SAMPLE_INT` - * Introduced `ImageScaleType.EXACTLY` and `ImageScaleType.EXACTLY_STRETCHED` + * **New API:** `ImageScaleType.EXACTLY` and `ImageScaleType.EXACTLY_STRETCHED` * Prevented `ImageLoadingListener` callbacks firing and displaying image in view after ImageLoader was stopped * Fixed bug of calculation of original image scale for decoding v1.6.0 *(29.09.2012)* --- - * Introduced `ImageLoader.loadImage(...)` which loads image, decodes it to Bitmap and returns Bitmap in callback ([#51](https://github.com/nostra13/Android-Universal-Image-Loader/issues/51)) + * **New API:** `ImageLoader.loadImage(...)` which loads image, decodes it to Bitmap and returns Bitmap in callback ([#51](https://github.com/nostra13/Android-Universal-Image-Loader/issues/51)) * Avoided unnecessary image downloads ([#44](https://github.com/nostra13/Android-Universal-Image-Loader/issues/44)) v1.5.6 *(20.09.2012)* --- * **Changed API:** Changed `FileNameGenerator` to interface (instead of abstract class) - * Introduced `BitmapDisplayer` and `DisplayImageOptions.displayer(...)` + * **New API:** `BitmapDisplayer` and `DisplayImageOptions.displayer(...)` * Multithread displaying of cached images (instead of single-thread) * Correctly handle UTF-8 symbols and spaces in image URL ([#31](https://github.com/nostra13/Android-Universal-Image-Loader/issues/31)) diff --git a/README.md b/README.md index 4cc53b5b5..9de3571b9 100644 --- a/README.md +++ b/README.md @@ -1,173 +1,64 @@ -# ![Logo](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/sample/res/drawable-mdpi/ic_launcher.png) Universal Image Loader for Android +# ![Logo](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/sample/src/main/res/drawable-mdpi/ic_launcher.png) Universal Image Loader [![Build Status](https://travis-ci.org/nostra13/Android-Universal-Image-Loader.svg?branch=master)](https://travis-ci.org/nostra13/Android-Universal-Image-Loader) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.nostra13.universalimageloader/universal-image-loader/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.nostra13.universalimageloader/universal-image-loader) -This project aims to provide a reusable instrument for asynchronous image loading, caching and displaying. It is originally based on [Fedor Vlasov's project](https://github.com/thest1/LazyList) and has been vastly refactored and improved since then. +The great ancestor of modern image-loading libraries :) +UIL aims to provide a powerful, flexible and highly customizable instrument for image loading, caching and displaying. It provides a lot of configuration options and good control over the image loading and caching process. ![Screenshot](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/UniversalImageLoader.png) -## Features - * Multithread image loading - * Possibility of wide tuning ImageLoader's configuration (thread executors, downlaoder, decoder, memory and disc cache, display image options, and others) - * Possibility of image caching in memory and/or on device's file sysytem (or SD card) - * Possibility to "listen" loading process - * Possibility to customize every display image call with separated options - * Widget support - -Android 2.0+ support - -## Downloads - * **[universal-image-loader-1.8.5.jar](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/downloads/universal-image-loader-1.8.5.jar)** (library; contains *.class files) - * **[universal-image-loader-1.8.5-sources.jar](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/downloads/universal-image-loader-1.8.5-sources.jar)** (sources; contains *.java files) - * **[universal-image-loader-1.8.5-javadoc.jar](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/downloads/universal-image-loader-1.8.5-javadoc.jar)** (Java docs; contains *.html files) - * **[universal-image-loader-1.8.5-with-sources.jar](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/downloads/universal-image-loader-1.8.5-with-sources.jar)** (library with sources inside; contains *.class and *.java files)
_Prefer to use this JAR so you can see Java docs in Eclipse tooltips._ - * **[universal-image-loader-sample-1.8.5.apk](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/downloads/universal-image-loader-sample-1.8.5.apk)** (sample application) - -Latest snapshot of the library - **[here](https://github.com/nostra13/Android-Universal-Image-Loader/tree/master/sample/libs)** - -## Documentation* - * Universal Image Loader. Part 1 - Introduction [[RU](http://nostra13android.blogspot.com/2012/03/4-universal-image-loader-part-1.html) | [EN](http://www.intexsoft.com/blog/item/68-universal-image-loader-part-1.html)] - * Universal Image Loader. Part 2 - Configuration [[RU](http://nostra13android.blogspot.com/2012/03/5-universal-image-loader-part-2.html) | [EN](http://www.intexsoft.com/blog/item/72-universal-image-loader-part-2.html)] - * Universal Image Loader. Part 3 - Usage [[RU](http://nostra13android.blogspot.com/2012/03/6-universal-image-loader-part-3-usage.html) | [EN](http://www.intexsoft.com/blog/item/74-universal-image-loader-part-3.html)] - -(*) a bit outdated - -### [Changelog](https://github.com/nostra13/Android-Universal-Image-Loader/blob/master/CHANGELOG.md) - -### User Support - 1. Look into **[Useful Info](https://github.com/nostra13/Android-Universal-Image-Loader#useful-info)** - 2. Search problem solution on **[StackOverFlow](http://stackoverflow.com/questions/tagged/universal-image-loader)** - 3. Ask your own question on **[StackOverFlow](http://stackoverflow.com/questions/tagged/universal-image-loader)**.
- Be sure to mention following information in your question: - - UIL version (e.g. 1.8.5) - - Android version tested on (e.g. 2.1) - - your configuration (`ImageLoaderConfiguration`) - - display options (`DisplayImageOptions`) - - `getView()` method code of your adapter (if you use it) - - XML layout of your ImageView you load image into - -**Bugs** and **feature requests** put **[here](https://github.com/nostra13/Android-Universal-Image-Loader/issues/new)**.
-If you have some **issues on migration** to newer library version - be sure to ask for help **[here](https://github.com/nostra13/Android-Universal-Image-Loader/issues/169)** - -## Quick Setup - -#### 1. Include library - -**Manual:** - * [Download JAR](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/downloads/universal-image-loader-1.8.5-with-sources.jar) - * Put the JAR in the **libs** subfolder of your Android project - -or - -**Maven dependency:** -``` xml - - com.nostra13.universalimageloader - universal-image-loader - 1.8.5 - -``` +## Project News + * Really have no time for development... so I stop project maintaining since Nov 27 :( + * UIL [27.11.2011 - 27.11.2015] + * Thanks to all developers for your support :) -#### 2. Android Manifest -``` xml - - - - - ... - - ... - - -``` +## Features + * Multi-thread image loading (async or sync) + * Wide customization of ImageLoader's configuration (thread executors, downloader, decoder, memory and disk cache, display image options, etc.) + * Many customization options for every display image call (stub images, caching switch, decoding options, Bitmap processing and displaying, etc.) + * Image caching in memory and/or on disk (device's file system or SD card) + * Listening loading process (including downloading progress) -#### 3. Application class -``` java -public class MyApplication extends Application { - @Override - public void onCreate() { - super.onCreate(); +Android 4.1+ support - // Create global configuration and initialize ImageLoader with this configuration - ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()) - ... - .build(); - ImageLoader.getInstance().init(config); - } -} -``` +## Downloads + * **[universal-image-loader-1.9.5.jar](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/downloads/universal-image-loader-1.9.5.jar)** + * [](https://play.google.com/store/apps/details?id=com.nostra13.universalimageloader.sample) [![QR Code](https://lh3.ggpht.com/csXEddxiLgQ6FxckefjQnP1PVugbaAYOdcuTa3vVtGV1PlWbFu2dYggoH8rI1w2RdEz1=w50)](http://chart.apis.google.com/chart?chs=300x300&cht=qr&chld=|1&chl=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dcom.nostra13.universalimageloader.sample) [](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/downloads/universal-image-loader-sample-1.9.5.apk) + +## [Documentation](https://github.com/nostra13/Android-Universal-Image-Loader/wiki) + * **[Quick Setup](https://github.com/nostra13/Android-Universal-Image-Loader/wiki/Quick-Setup)** + * **[Configuration](https://github.com/nostra13/Android-Universal-Image-Loader/wiki/Configuration)** + * **[Display Options](https://github.com/nostra13/Android-Universal-Image-Loader/wiki/Display-Options)** + * [Useful Info](https://github.com/nostra13/Android-Universal-Image-Loader/wiki/Useful-Info) - Read it before asking a question + * [User Support](https://github.com/nostra13/Android-Universal-Image-Loader/wiki/User-Support) - Read it before creating new issue + * [Sample project](https://github.com/nostra13/Android-Universal-Image-Loader/tree/master/sample) - Learn it to understand the right way of library usage + * [ChangeLog](https://github.com/nostra13/Android-Universal-Image-Loader/blob/master/CHANGELOG.md) - Info about API changes is here -## Configuration and Display Options +## Usage - * ImageLoader **Configuration (`ImageLoaderConfiguration`) is global** for application. - * **Display Options (`DisplayImageOptions`) are local** for every display task (`ImageLoader.displayImage(...)`). +### Dependency -### Configuration -All options in Configuration builder are optional. Use only those you really want to customize.
*See default values for config options in Java docs for every option.* -``` java -// DON'T COPY THIS CODE TO YOUR PROJECT! This is just example of ALL options using. -File cacheDir = StorageUtils.getCacheDirectory(context); -ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) - .memoryCacheExtraOptions(480, 800) // default = device screen dimensions - .discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null) - .taskExecutor(...) - .taskExecutorForCachedImages(...) - .threadPoolSize(3) // default - .threadPriority(Thread.NORM_PRIORITY - 1) // default - .tasksProcessingOrder(QueueProcessingType.FIFO) // default - .denyCacheImageMultipleSizesInMemory() - .memoryCache(new LruMemoryCache(2 * 1024 * 1024)) - .memoryCacheSize(2 * 1024 * 1024) - .memoryCacheSizePercentage(13) // default - .discCache(new UnlimitedDiscCache(cacheDir)) // default - .discCacheSize(50 * 1024 * 1024) - .discCacheFileCount(100) - .discCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default - .imageDownloader(new BaseImageDownloader(context)) // default - .imageDecoder(new BaseImageDecoder()) // default - .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default - .writeDebugLogs() - .build(); ``` - -### Display Options -Display Options can be applied to every display task (`ImageLoader.displayImage(...)` call). - -**Note:** If Display Options wasn't passed to `ImageLoader.displayImage(...)`method then default Display Options from configuration (`ImageLoaderConfiguration.defaultDisplayImageOptions(...)`) will be used. -``` java -// DON'T COPY THIS CODE TO YOUR PROJECT! This is just example of ALL options using. -DisplayImageOptions options = new DisplayImageOptions.Builder() - .showStubImage(R.drawable.ic_stub) - .showImageForEmptyUri(R.drawable.ic_empty) - .showImageOnFail(R.drawable.ic_error) - .resetViewBeforeLoading(false) // default - .delayBeforeLoading(1000) - .cacheInMemory(false) // default - .cacheOnDisc(false) // default - .preProcessor(...) - .postProcessor(...) - .extraForDownloader(...) - .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default - .bitmapConfig(Bitmap.Config.ARGB_8888) // default - .decodingOptions(...) - .displayer(new SimpleBitmapDisplayer()) // default - .handler(new Handler()) // default - .build(); +implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' ``` -## Usage - ### Acceptable URIs examples ``` java -String imageUri = "http://site.com/image.png"; // from Web -String imageUri = "file:///mnt/sdcard/image.png"; // from SD card -String imageUri = "content://media/external/audio/albumart/13"; // from content provider -String imageUri = "assets://image.png"; // from assets -String imageUri = "drawable://" + R.drawable.image; // from drawables (only images, non-9patch) +"http://site.com/image.png" // from Web +"file:///mnt/sdcard/image.png" // from SD card +"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail) +"content://media/external/images/media/13" // from content provider +"content://media/external/video/media/13" // from content provider (video thumbnail) +"assets://image.png" // from assets +"drawable://" + R.drawable.img // from drawables (non-9patch images) ``` **NOTE:** Use `drawable://` only if you really need it! Always **consider the native way** to load drawables - `ImageView.setImageResource(...)` instead of using of `ImageLoader`. ### Simple ``` java -// Load image, decode it to Bitmap and display Bitmap in ImageView +ImageLoader imageLoader = ImageLoader.getInstance(); // Get singleton instance +``` +``` java +// Load image, decode it to Bitmap and display Bitmap in ImageView (or any other view +// which implements ImageAware interface) imageLoader.displayImage(imageUri, imageView); ``` ``` java @@ -179,11 +70,16 @@ imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() { } }); ``` +``` java +// Load image, decode it to Bitmap and return Bitmap synchronously +Bitmap bmp = imageLoader.loadImageSync(imageUri); +``` ### Complete ``` java -// Load image, decode it to Bitmap and display Bitmap in ImageView -imageLoader.displayImage(imageUri, imageView, displayOptions, new ImageLoadingListener() { +// Load image, decode it to Bitmap and display Bitmap in ImageView (or any other view +// which implements ImageAware interface) +imageLoader.displayImage(imageUri, imageView, options, new ImageLoadingListener() { @Override public void onLoadingStarted(String imageUri, View view) { ... @@ -200,171 +96,44 @@ imageLoader.displayImage(imageUri, imageView, displayOptions, new ImageLoadingLi public void onLoadingCancelled(String imageUri, View view) { ... } +}, new ImageLoadingProgressListener() { + @Override + public void onProgressUpdate(String imageUri, View view, int current, int total) { + ... + } }); ``` ``` java // Load image, decode it to Bitmap and return Bitmap to callback -ImageSize targetSize = new ImageSize(120, 80); // result Bitmap will be fit to this size -imageLoader.loadImage(imageUri, targetSize, displayOptions, new SimpleImageLoadingListener() { +ImageSize targetSize = new ImageSize(80, 50); // result Bitmap will be fit to this size +imageLoader.loadImage(imageUri, targetSize, options, new SimpleImageLoadingListener() { @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { // Do whatever you want with Bitmap } }); ``` - -### ImageLoader Helpers -Other useful methods and classes to consider. -
-ImageLoader |
-			| - getMemoryCache()
-			| - clearMemoryCache()
-			| - getDiscCache()
-			| - clearDiscCache()
-			| - denyNetworkDownloads(boolean)
-			| - handleSlowNetwork(boolean)
-			| - pause()
-			| - resume()
-			| - stop()
-			| - destroy()
-			| - getLoadingUriForView(ImageView)
-			| - cancelDisplayTask(ImageView)
-
-MemoryCacheUtil |
-				| - findCachedBitmapsForImageUri(...)
-				| - findCacheKeysForImageUri(...)
-				| - removeFromCache(...)
-
-DiscCacheUtil |
-			  | - findInCache(...)
-			  | - removeFromCache(...)
-
-StorageUtils |
-			 | - getCacheDirectory(Context)
-			 | - getIndividualCacheDirectory(Context)
-			 | - getOwnCacheDirectory(Context, String)
-
-PauseOnScrollListener
-
-Also look into more detailed **[Library Map](https://github.com/nostra13/Android-Universal-Image-Loader/wiki/Library-Map)** - -## Useful Info -1. **Caching is NOT enabled by default.** If you want loaded images will be cached in memory and/or on disc then you should enable caching in DisplayImageOptions this way: -``` java -// Create default options which will be used for every -// displayImage(...) call if no options will be passed to this method -DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder() - ... - .cacheInMemory(true) - .cacheOnDisc(true) - ... - .build(); -ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()) - ... - .defaultDisplayImageOptions(defaultOptions) - ... - .build(); -ImageLoader.getInstance().init(config); // Do it on Application start -``` ``` java -// Then later, when you want to display image -ImageLoader.getInstance().displayImage(imageUrl, imageView); // Default options will be used +// Load image, decode it to Bitmap and return Bitmap synchronously +ImageSize targetSize = new ImageSize(80, 50); // result Bitmap will be fit to this size +Bitmap bmp = imageLoader.loadImageSync(imageUri, targetSize, options); ``` -or this way: -``` java -DisplayImageOptions options = new DisplayImageOptions.Builder() - ... - .cacheInMemory(true) - .cacheOnDisc(true) - ... - .build(); -ImageLoader.getInstance().displayImage(imageUrl, imageView, options); // Incoming options will be used -``` - -2. If you enabled disc caching then UIL try to cache images on external storage (/sdcard/Android/data/[package_name]/cache). If external storage is not available then images are cached on device's filesytem. -To provide caching on external storage (SD card) add following permission to AndroidManifest.xml: -``` java - -``` - -3. How UIL define Bitmap size needed for exact ImageView? It searches defined parameters: - * Get actual measured width and height of ImageView - * Get `android:layout_width` and `android:layout_height` parameters - * Get `android:maxWidth` and/or `android:maxHeight` parameters - * Get maximum width and/or height parameters from configuration (`memoryCacheExtraOptions(int, int)` option) - * Get width and/or height of device screen - - So **try to set** `android:layout_width`|`android:layout_height` or `android:maxWidth`|`android:maxHeight` parameters for ImageView if you know approximate maximum size of it. It will help correctly compute Bitmap size needed for this view and **save memory**. - -4. If you often got **OutOfMemoryError** in your app using Universal Image Loader then try next (all of them or several): - - Reduce thread pool size in configuration (`.threadPoolSize(...)`). 1 - 5 is recommended. - - Use `.bitmapConfig(Bitmap.Config.RGB_565)` in display options. Bitmaps in RGB_565 consume 2 times less memory than in ARGB_8888. - - Use `.memoryCache(new WeakMemoryCache())` in configuration or disable caching in memory at all in display options (don't call `.cacheInMemory()`). - - Use `.imageScaleType(ImageScaleType.IN_SAMPLE_INT)` in display options. Or try `.imageScaleType(ImageScaleType.EXACTLY)`. - - Avoid using RoundedBitmapDisplayer. It creates new Bitmap object with ARGB_8888 config for displaying during work. - -5. For memory cache configuration (`ImageLoaderConfiguration.memoryCache(...)`) you can use already prepared implementations. - * Cache using **only strong** references: - * `LruMemoryCache` (Least recently used bitmap is deleted when cache size limit is exceeded) - **Used by default for API >= 9** - * Caches using **weak and strong** references: - * `UsingFreqLimitedMemoryCache` (Least frequently used bitmap is deleted when cache size limit is exceeded) - * `LRULimitedMemoryCache` (Least recently used bitmap is deleted when cache size limit is exceeded) - **Used by default for API < 9** - * `FIFOLimitedMemoryCache` (FIFO rule is used for deletion when cache size limit is exceeded) - * `LargestLimitedMemoryCache` (The largest bitmap is deleted when cache size limit is exceeded) - * `LimitedAgeMemoryCache` (Decorator. Cached object is deleted when its age exceeds defined value) - * Cache using **only weak** references: - * `WeakMemoryCache` (Unlimited cache) -6. For disc cache configuration (`ImageLoaderConfiguration.discCache(...)`) you can use already prepared implementations: - * `UnlimitedDiscCache` (The fastest cache, doesn't limit cache size) - **Used by default** - * `TotalSizeLimitedDiscCache` (Cache limited by total cache size. If cache size exceeds specified limit then file with the most oldest last usage date will be deleted) - * `FileCountLimitedDiscCache` (Cache limited by file count. If file count in cache directory exceeds specified limit then file with the most oldest last usage date will be deleted. Use it if your cached files are of about the same size.) - * `LimitedAgeDiscCache` (Size-unlimited cache with limited files' lifetime. If age of cached file exceeds defined limit then it will be deleted from cache.) - - **NOTE:** UnlimitedDiscCache is 30%-faster than other limited disc cache implementations. +## Load & Display Task Flow +![Task Flow](https://github.com/nostra13/Android-Universal-Image-Loader/raw/master/wiki/UIL_Flow.png) -7. To display bitmap (`DisplayImageOptions.displayer(...)`) you can use already prepared implementations: - * `RoundedBitmapDisplayer` (Displays bitmap with rounded corners) - * `FadeInBitmapDisplayer` (Displays image with "fade in" animation) -8. To avoid list (grid, ...) scrolling lags you can use `PauseOnScrollListener`: -``` java -boolean pauseOnScroll = false; // or true -boolean pauseOnFling = true; // or false -PauseOnScrollListener listener = new PauseOnScrollListener(imageLoader, pauseOnScroll, pauseOnFling); -listView.setOnScrollListener(listener); -``` - ## Applications using Universal Image Loader -**[MediaHouse, UPnP/DLNA Browser](https://play.google.com/store/apps/details?id=com.dbapp.android.mediahouse)** | [Деловой Киров](https://play.google.com/store/apps/details?id=ru.normakirov.dknorma) | [Бизнес-завтрак](https://play.google.com/store/apps/details?id=ru.normakirov.businesslunch) | [Menu55](http://www.free-lance.ru/users/max475imus/viewproj.php?prjid=3152141) | [SpokenPic](http://spokenpic.com) | [Kumir](https://play.google.com/store/apps/details?id=ru.premiakumir.android) | [TuuSo Image Search](https://play.google.com/store/apps/details?id=com.tuuso) | [Газета Стройка](https://play.google.com/store/apps/details?id=ru.normakirov.stroyka) | **[Prezzi Benzina (AndroidFuel)](https://play.google.com/store/apps/details?id=org.vernazza.androidfuel)** | [Quiz Guess The Guy] (https://play.google.com/store/apps/details?id=com.game.guesstheguy) | [Volksempfänger (alpha)](http://volksempfaenger.0x4a42.net) | **[ROM Toolbox Lite](https://play.google.com/store/apps/details?id=com.jrummy.liberty.toolbox), [Pro](https://play.google.com/store/apps/details?id=com.jrummy.liberty.toolboxpro)** | [London 2012 Games](https://play.google.com/store/apps/details?id=com.mbwasi.london) | [카톡 이미지 - 예쁜 프로필 이미지](https://play.google.com/store/apps/details?id=com.bydoori.firstbasea) | [dailyPen](https://play.google.com/store/apps/details?id=com.bydoori.dailypen) | [Mania!](https://play.google.com/store/apps/details?id=com.astro.mania.activities) | **[Stadium Astro](https://play.google.com/store/apps/details?id=com.astro.stadium.activities)** | **[Chef Astro](https://play.google.com/store/apps/details?id=com.sencha.test)** | [Lafemme Fashion Finder](https://play.google.com/store/apps/details?id=me.getlafem.lafemme2) | [FastPaleo](https://play.google.com/store/apps/details?id=com.mqmobile.droid.fastpaleo) | **[Sporee - Live Soccer Scores](https://play.google.com/store/apps/details?id=com.sporee.android)** | [friendizer](https://play.google.com/store/apps/details?id=com.teamagly.friendizer) | [LowPrice lowest book price](https://play.google.com/store/apps/details?id=com.binarybricks.lowprice) | [bluebee](https://play.google.com/store/apps/details?id=mobi.bluebee.android.app) | **[EyeEm - Photo Filter Camera](https://play.google.com/store/apps/details?id=com.baseapp.eyeem)** | [Festival Wallpaper](https://play.google.com/store/apps/details?id=com.cs.fwallpaper) | [Gaudi Hall](https://play.google.com/store/apps/details?id=ru.normakirov.gaudihall) | [Spocal](https://play.google.com/store/apps/details?id=net.spocal.android) | **[PhotoDownloader for Facebook](https://play.google.com/store/apps/details?id=com.giannz.photodownloader)** | [Вкладыши](https://play.google.com/store/apps/details?id=com.banjen.app.gumimages) | [Dressdrobe](https://play.google.com/store/apps/details?id=com.dressdrobe.mario) | [mofferin](https://play.google.com/store/apps/details?id=com.mmobile.mofferin) | [WordBoxer](http://www.wordboxer.com/) | [EZ Imgur](https://play.google.com/store/apps/details?id=com.ezimgur) | [Ciudad en línea](https://play.google.com/store/apps/details?id=com.aliadosweb.android.cel) | [Urbanismo en línea](https://play.google.com/store/apps/details?id=com.aliadosweb.android.opel) | [Waypost](https://play.google.com/store/apps/details?id=com.brushfire.waypost) | [Moonrise Kingdom Wallpapers HD](https://play.google.com/store/apps/details?id=net.dnlk.moonrisekingdom.gallery) | [Chic or Shock?](https://play.google.com/store/apps/details?id=com.chicorshock) | [Auto Wallpapers](https://play.google.com/store/apps/details?id=ru.evgsd.autowallpapers) | [Brasil Notícias](https://play.google.com/store/apps/details?id=com.acerolamob.android.brasilnoticias) | [ProfiAuto’s VideoBlog](https://play.google.com/store/apps/details?id=pl.profiauto.android.videoblog) | [CarteleraApp (Cine)](https://play.google.com/store/apps/details?id=com.jcminarro.android.tools), [AdsFree](https://play.google.com/store/apps/details?id=com.jcminarro.android.tools.carteleraApp) | [Listonic - Zamów Zakupy](https://play.google.com/store/apps/details?id=com.listonic.shop) | **[Topface - meeting is easy](https://play.google.com/store/apps/details?id=com.topface.topface)** | [Name The Meme](https://play.google.com/store/apps/details?id=it.fi.appstyx.namethememe) | [Name The World](https://play.google.com/store/apps/details?id=it.fi.appstyx.nametheworld) | [Pregnancy Tickers - Widget](https://play.google.com/store/apps/details?id=com.romkuapps.tickers) | [User Manager ROOT Android 4.2](https://play.google.com/store/apps/details?id=com.ramdroid.usermanagerpro) | [Theke](https://play.google.com/store/apps/details?id=com.sh.theke) | [SensibleJournal](https://play.google.com/store/apps/details?id=dk.dtu.imm.sensiblejournal) | [PiCorner for Flickr, Instagram](https://play.google.com/store/apps/details?id=com.gmail.charleszq.picorner) | [Survey-n-More - Paid Surveys](https://play.google.com/store/apps/details?id=com.surveynmore.paidsurveyapp) | [STROBEL Verlag Basic](https://play.google.com/store/apps/details?id=de.nexoma.android.strobel.basic) | **[reddit is fun](https://play.google.com/store/apps/details?id=com.andrewshu.android.reddit)**, [golden platinum](https://play.google.com/store/apps/details?id=com.andrewshu.android.redditdonation) | [iDukan Diet Tracker](https://play.google.com/store/apps/details?id=com.harptreesoftware.idukan) | [Geek Hero Comic](https://play.google.com/store/apps/details?id=pete.apps.media.geekherocomic) | [Sprinter](https://play.google.com/store/apps/details?id=com.manavo.sprinter) | [Twxter](https://play.google.com/store/apps/details?id=com.oadigital.twxter) | [Locaside ★ Parties und Events](https://play.google.com/store/apps/details?id=de.partyison.app) | [fileboost](https://play.google.com/store/apps/details?id=apps.powdercode.fileboost) | [Urbanoe Mobile](https://play.google.com/store/apps/details?id=com.teleronsoftware.urbanoe.client) | [What Channel's the Game On...?](https://play.google.com/store/apps/details?id=com.mosaheb.wcgo.user) | [MythTV Android Frontend](https://play.google.com/store/apps/details?id=org.mythtv) | **[Diaro - personal diary](https://play.google.com/store/apps/details?id=com.pixelcrater.Diaro)** | [AwwBrowser](https://play.google.com/store/apps/details?id=com.ahadjittofis.awwfree) | **[KCCO Pro](https://play.google.com/store/apps/details?id=com.hiv0lt.KCCOpro)** | [STQRY](https://play.google.com/store/apps/details?id=nz.co.stqry) | [Forbes Reader Holo](https://play.google.com/store/apps/details?id=com.smartsoft.forbesreader) | [Pönis Filmclub](https://play.google.com/store/apps/details?id=de.appic.poeni) | [Socially You - Free](https://play.google.com/store/apps/details?id=com.dzinesunlimited.sociallyyou), [PRO](https://play.google.com/store/apps/details?id=com.dzinesunlimited.sociallyyou.paid) | [КПРФ.ру](https://play.google.com/store/apps/details?id=ru.kprf.mobikprfru) | [Moment.me](https://play.google.com/store/apps/details?id=me.moment.momentme) | [Colonial Club](https://play.google.com/store/apps/details?id=com.colonialclub.android) | [Plex for PlexPass](https://play.google.com/store/apps/details?id=com.plexapp.plex) | [Perfect Spot](https://play.google.com/store/apps/details?id=com.perfectspot) | [My Diet Tracker](https://play.google.com/store/apps/details?id=com.mmg.mydiettracker) | [All Cebu](https://play.google.com/store/apps/details?id=allcebu.cd.com) | **[WebMoney Keeper Mobile](https://play.google.com/store/apps/details?id=ru.webmoney.keeper.mobile)** | [Ja, Rock!](https://play.google.com/store/apps/details?id=pl.jarock) | [Art Widget](https://play.google.com/store/apps/details?id=biz.artega.artwidget), [Pro](https://play.google.com/store/apps/details?id=biz.artega.artwidget.pro) | [Le Monde Archives](https://play.google.com/store/apps/details?id=com.lemonde.androidapp.archives) | **[LoL Memento League of Legends](https://play.google.com/store/apps/details?id=com.buchland.lolmemento)** | **[WANNA B! 워너비!](https://play.google.com/store/apps/details?id=com.nhn.wannab)** | [Alcázar de San Juan](https://play.google.com/store/apps/details?id=es.mac.alcazardesanjuan) | [PetsDaily](https://play.google.com/store/apps/details?id=com.petsdaily2) | [CarCrazee](https://play.google.com/store/apps/details?id=com.applabsdigital.carcrazee) | **[Meetup](https://play.google.com/store/apps/details?id=com.meetup)** | [G'day Australia (Newspapers)](https://play.google.com/store/apps/details?id=com.gDayAus) | **[Vingle - Magazines by Fans](https://play.google.com/store/apps/details?id=com.vingle.android)** | [Facebook Album Downloader](https://play.google.com/store/apps/details?id=com.googlepages.vysakhp.barebone_fb) | [Esplorea](https://play.google.com/store/apps/details?id=fr.esplorea.android) | [Dog Breeds](https://play.google.com/store/apps/details?id=com.dogbreeds) | [롱비치하우스 펜션 - 을왕리해수욕장](https://play.google.com/store/apps/details?id=com.hncsoft.beachstar) | [DJ Paolo](https://play.google.com/store/apps/details?id=de.android.tschakle.djpaolo) | [@to Music - VK, Last.fm, Radio](https://play.google.com/store/apps/details?id=by.istin.android.vkmusic) | [배달몬스터-주문하고 로또받자(특허출원)](https://play.google.com/store/apps/details?id=com.foxcall.dvrmonster) | [Extra! Newspaper Covers](https://play.google.com/store/apps/details?id=es.udc.jporta.android.npcovers) | [iWestern](https://play.google.com/store/apps/details?id=ca.uwo.android) | [All is Wall - HD Wallpapers](https://play.google.com/store/apps/details?id=com.weetech.alliswall) | [Galbijjim Searcher](https://play.google.com/store/apps/details?id=com.nesswit.galbijjimsearcher) | [Slow Radio Unofficial](https://play.google.com/store/apps/details?id=com.pasha.slowradio) | [Protein Finder](https://play.google.com/store/apps/details?id=com.sagar.proteinfinder) | [Robird](https://play.google.com/store/apps/details?id=com.aaplab.android.robird) | [MPme Radio](https://play.google.com/store/apps/details?id=com.mpme.client) | [MicroHealth Hemofilia](https://play.google.com/store/apps/details?id=com.microhealth.hemophilia) | **[Anime Music Radio](https://play.google.com/store/apps/details?id=com.maxxt.animeradio)** | [Top Games](https://play.google.com/store/apps/details?id=com.simplegear.topgames) | [米折-购物省钱助手,淘宝网天猫聚划算京东等600商城返利](https://play.google.com/store/apps/details?id=com.husor.mizhe) | [Learn 'n' Share](https://play.google.com/store/apps/details?id=com.freestyledevelopment.learnnshare) | [ЯП.Мобайл](https://play.google.com/store/apps/details?id=ru.yap.mobile) | [AssamKart](https://play.google.com/store/apps/details?id=com.gmms.assamkart.ui) | [Da Ai TV](https://play.google.com/store/apps/details?id=tv.daai.daaitv) | [watch.is](https://play.google.com/store/apps/details?id=com.dkc.watchis) | [HDOut](https://play.google.com/store/apps/details?id=com.dkc.hdout) | **[FS VideoBox - онлайн кинотеатр](https://play.google.com/store/apps/details?id=com.dkc.fs)** | [UsedAppleJuice](https://play.google.com/store/apps/details?id=com.dkc.uaj) | [Killermatch - tennis, squash..](https://play.google.com/store/apps/details?id=net.kjeldahl.tournman) +**[MediaHouse, UPnP/DLNA Browser](https://play.google.com/store/apps/details?id=com.dbapp.android.mediahouse)** | **[Prezzi Benzina (AndroidFuel)](https://play.google.com/store/apps/details?id=org.vernazza.androidfuel)** | **[ROM Toolbox Lite](https://play.google.com/store/apps/details?id=com.jrummy.liberty.toolbox)**, [Pro](https://play.google.com/store/apps/details?id=com.jrummy.liberty.toolboxpro) | [Stadium Astro](https://play.google.com/store/apps/details?id=com.astro.stadium.activities) | [Chef Astro](https://play.google.com/store/apps/details?id=com.sencha.test) | [Sporee - Live Soccer Scores](https://play.google.com/store/apps/details?id=com.sporee.android) | **[EyeEm - Photo Filter Camera](https://play.google.com/store/apps/details?id=com.baseapp.eyeem)** | **[Topface - meeting is easy](https://play.google.com/store/apps/details?id=com.topface.topface)** | **[reddit is fun](https://play.google.com/store/apps/details?id=com.andrewshu.android.reddit)** | **[Diaro - personal diary](https://play.google.com/store/apps/details?id=com.pixelcrater.Diaro)** | **[Meetup](https://play.google.com/store/apps/details?id=com.meetup)** | [Vingle - Magazines by Fans](https://play.google.com/store/apps/details?id=com.vingle.android) | [Anime Music Radio](https://play.google.com/store/apps/details?id=com.maxxt.animeradio) | [WidgetLocker Theme Viewer](https://play.google.com/store/apps/details?id=com.companionfree.WLThemeViewer) | [ShortBlogger for Tumblr](https://play.google.com/store/apps/details?id=com.luckydroid.tumblelog) | [SnapDish Food Camera](https://play.google.com/store/apps/details?id=com.vuzz.snapdish) | **[Twitch](https://play.google.com/store/apps/details?id=tv.twitch.android.viewer)** | [TVShow Time, TV show guide](https://play.google.com/store/apps/details?id=com.tozelabs.tvshowtime) | [Planning Center Services](https://play.google.com/store/apps/details?id=com.ministrycentered.PlanningCenter) | **[Lapse It](https://play.google.com/store/apps/details?id=com.ui.LapseIt)** | [My Cloud Player for SoundCloud](https://play.google.com/store/apps/details?id=com.mycloudplayers.mycloudplayer) | **[SoundTracking](https://play.google.com/store/apps/details?id=com.schematiclabs.soundtracking)** | [LoopLR Social Video](https://play.google.com/store/apps/details?id=com.looplr) | [Hír24](https://play.google.com/store/apps/details?id=hu.sanomamedia.hir24) | **[Immobilien Scout24](https://play.google.com/store/apps/details?id=de.is24.android)** | **[Lieferheld - Pizza Pasta Sushi](https://play.google.com/store/apps/details?id=de.lieferheld.android)** | [Loocator: free sex datings](https://play.google.com/store/apps/details?id=com.ivicode.loocator) | [벨팡-개편 이벤트,컬러링,벨소리,무료,최신가요,링투유](https://play.google.com/store/apps/details?id=com.mediahubs.www) | [Streambels AirPlay/DLNA Player](https://play.google.com/store/apps/details?id=com.tuxera.streambels) | [Ship Mate - All Cruise Lines](https://play.google.com/store/apps/details?id=shipmate.carnival) | [Disk & Storage Analyzer](https://play.google.com/store/apps/details?id=com.mobile_infographics_tools.mydrive) | [糗事百科](https://play.google.com/store/apps/details?id=qsbk.app) | [Balance BY](https://play.google.com/store/apps/details?id=com.vladyud.balance) | **[Anti Theft Alarm - Security](https://play.google.com/store/apps/details?id=br.com.verde.alarme)** | **[XiiaLive™ - Internet Radio](https://play.google.com/store/apps/details?id=com.android.DroidLiveLite)** | **[Bandsintown Concerts](https://play.google.com/store/apps/details?id=com.bandsintown)** | **[Save As Web Archive](https://play.google.com/store/apps/details?id=jp.fuukiemonster.webmemo)** | [MCPE STORE -Download MCPE file](https://play.google.com/store/apps/details?id=com.newidea.mcpestore) | **[All-In-One Toolbox (29 Tools)](http://aiotoolbox.com/)** | [Zaim](https://play.google.com/store/apps/details?id=net.zaim.android) | **[Calculator Plus Free](https://play.google.com/store/apps/details?id=com.digitalchemy.calculator.freedecimal)** | [Truedialer by Truecaller](https://play.google.com/store/apps/details?id=com.truecaller.phoneapp) | [DoggCatcher Podcast Player](https://play.google.com/store/apps/details?id=com.snoggdoggler.android.applications.doggcatcher.v1_0) | [PingTools Network Utilities](https://play.google.com/store/apps/details?id=ua.com.streamsoft.pingtools) | [The Traveler](https://play.google.com/store/apps/details?id=edu.bsu.android.apps.traveler) | [minube: travel photo album](https://play.google.com/store/apps/details?id=com.minube.app) | [Wear Store for Wear Apps](https://play.google.com/store/apps/details?id=goko.ws2) | [Cast Store for Chromecast Apps](https://play.google.com/store/apps/details?id=goko.gcs) | [WebMoney Keeper](https://play.google.com/store/apps/details?id=com.webmoney.my) ## Donation You can support the project and thank the author for his hard work :) -Click here to lend your support to: Universal Image Loader for Android and make a donation at www.pledgie.com ! Flattr this * **PayPal** - nostra.uil[at]gmail[dot]com -* **[WebMoney](http://www.webmoney.ru/)** - Z417203268219 -* **[GitTip](https://www.gittip.com/nostra13/)** - nostra13 - -## License - -If you use Universal Image Loader code in your application you should inform the author about it ( *email: nostra13[at]gmail[dot]com* ) like this: -> **Subject:** UIL usage notification
-> **Text:** I use Universal Image Loader <lib_version> in <application_name> - http://link_to_google_play. -> I [allow | don't allow] to mention my app in section "Applications using Universal Image Loader" on GitHub. - -Also I'll be grateful if you mention UIL in application UI with string **"Using Universal Image Loader (c) 2011-2013, Sergey Tarasevich"** (e.g. in some "About" section). - - 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 +## Alternative libraries - 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. \ No newline at end of file + * [Fresco](https://github.com/facebook/fresco) + * [Glide](https://github.com/bumptech/glide) + * [Picasso](https://github.com/square/picasso) + * [Volley : ImageLoader](https://android.googlesource.com/platform/frameworks/volley/) diff --git a/UniversalImageLoader.png b/UniversalImageLoader.png index 3b6d20246..6c679cbdf 100644 Binary files a/UniversalImageLoader.png and b/UniversalImageLoader.png differ diff --git a/android_eclipse_formatter.xml b/android_eclipse_formatter.xml deleted file mode 100644 index 00c1fb875..000000000 --- a/android_eclipse_formatter.xml +++ /dev/null @@ -1,291 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..338b1a897 --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.6.2' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +if (JavaVersion.current().isJava8Compatible()) { + allprojects { + tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:none', '-quiet') + } + } +} diff --git a/downloads/universal-image-loader-1.8.5-javadoc.jar b/downloads/universal-image-loader-1.8.5-javadoc.jar deleted file mode 100644 index 1cac5997d..000000000 Binary files a/downloads/universal-image-loader-1.8.5-javadoc.jar and /dev/null differ diff --git a/downloads/universal-image-loader-1.8.5-sources.jar b/downloads/universal-image-loader-1.8.5-sources.jar deleted file mode 100644 index 308c2e8fe..000000000 Binary files a/downloads/universal-image-loader-1.8.5-sources.jar and /dev/null differ diff --git a/downloads/universal-image-loader-1.8.5-with-sources.jar b/downloads/universal-image-loader-1.8.5-with-sources.jar deleted file mode 100644 index 080318eb8..000000000 Binary files a/downloads/universal-image-loader-1.8.5-with-sources.jar and /dev/null differ diff --git a/downloads/universal-image-loader-1.8.5.jar b/downloads/universal-image-loader-1.8.5.jar deleted file mode 100644 index 8d66fe1e1..000000000 Binary files a/downloads/universal-image-loader-1.8.5.jar and /dev/null differ diff --git a/downloads/universal-image-loader-1.9.5-javadoc.jar b/downloads/universal-image-loader-1.9.5-javadoc.jar new file mode 100644 index 000000000..0069f186c Binary files /dev/null and b/downloads/universal-image-loader-1.9.5-javadoc.jar differ diff --git a/downloads/universal-image-loader-1.9.5-sources.jar b/downloads/universal-image-loader-1.9.5-sources.jar new file mode 100644 index 000000000..430168115 Binary files /dev/null and b/downloads/universal-image-loader-1.9.5-sources.jar differ diff --git a/downloads/universal-image-loader-1.9.5.jar b/downloads/universal-image-loader-1.9.5.jar new file mode 100644 index 000000000..520dac3f0 Binary files /dev/null and b/downloads/universal-image-loader-1.9.5.jar differ diff --git a/downloads/universal-image-loader-sample-1.8.5.apk b/downloads/universal-image-loader-sample-1.8.5.apk deleted file mode 100644 index fc043b646..000000000 Binary files a/downloads/universal-image-loader-sample-1.8.5.apk and /dev/null differ diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..021cc7f09 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +org.gradle.daemon=true + +VERSION_NAME=1.9.5 +VERSION_COE=40 +GROUP=com.nostra13.universalimageloader + +POM_DESCRIPTION=Powerful and flexible instrument for asynchronous image loading, caching and displaying on Android +POM_URL=https://github.com/nostra13/Android-Universal-Image-Loader +POM_SCM_URL=https://github.com/nostra13/Android-Universal-Image-Loader +POM_SCM_CONNECTION=scm:git@github.com:nostra13/Android-Universal-Image-Loader.git +POM_SCM_DEV_CONNECTION=scm:git@github.com:nostra13/Android-Universal-Image-Loader.git +POM_LICENCE_NAME=The Apache Software License, Version 2.0 +POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt +POM_LICENCE_DIST=repo +POM_DEVELOPER_ID=nostra13 +POM_DEVELOPER_NAME=Sergey Tarasevich + diff --git a/gradle/maven_push.gradle b/gradle/maven_push.gradle new file mode 100644 index 000000000..3b4876fdb --- /dev/null +++ b/gradle/maven_push.gradle @@ -0,0 +1,131 @@ +/* + * Copyright 2013 Chris Banes + * + * 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. + */ + +apply plugin: 'maven' +apply plugin: 'signing' + +def isReleaseBuild() { + return VERSION_NAME.contains("SNAPSHOT") == false +} + +def getReleaseRepositoryUrl() { + return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL + : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" +} + +def getSnapshotRepositoryUrl() { + return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL + : "https://oss.sonatype.org/content/repositories/snapshots/" +} + +def getRepositoryUsername() { + return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" +} + +def getRepositoryPassword() { + return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" +} + +afterEvaluate { project -> + uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = VERSION_NAME + + repository(url: getReleaseRepositoryUrl()) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + snapshotRepository(url: getSnapshotRepositoryUrl()) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + + pom.project { + name POM_NAME + packaging POM_PACKAGING + description POM_DESCRIPTION + url POM_URL + + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEV_CONNECTION + } + + licenses { + license { + name POM_LICENCE_NAME + url POM_LICENCE_URL + distribution POM_LICENCE_DIST + } + } + + developers { + developer { + id POM_DEVELOPER_ID + name POM_DEVELOPER_NAME + } + } + } + } + } + } + + task installArchives(type: Upload) { + description "Installs the artifacts to the local Maven repository." + configuration = configurations['archives'] + repositories { + mavenDeployer { + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = VERSION_NAME + + repository url: "file://${System.properties['user.home']}/.m2/repository" + } + } + } + + signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives + } + + task androidJavadocs(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + options { + encoding = "UTF-8" + } + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + } + + task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { + classifier = 'javadoc' + from androidJavadocs.destinationDir + } + + task androidSourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.srcDirs + } + + artifacts { + archives androidSourcesJar + archives androidJavadocsJar + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..085a1cdc2 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..01a199e8d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Apr 23 15:38:19 MSK 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..91a7e269e --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..8a0b282aa --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/library/AndroidManifest.xml b/library/AndroidManifest.xml deleted file mode 100644 index 5c0ac56ba..000000000 --- a/library/AndroidManifest.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/library/build.gradle b/library/build.gradle new file mode 100644 index 000000000..ef58b3eff --- /dev/null +++ b/library/build.gradle @@ -0,0 +1,45 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + buildToolsVersion "28.0.3" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 28 + versionCode 40 + versionName "1.9.5" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + testImplementation 'junit:junit:4.12' + testImplementation 'org.robolectric:robolectric:3.0-rc3' + testImplementation 'com.squareup.assertj:assertj-android:1.0.0' +} + + +// build a jar with source files +task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' +} +artifacts { + archives sourcesJar +} + +// Build a jar file in addition to the default aar file +android.libraryVariants.all { variant -> + def name = variant.buildType.name + def task = project.tasks.create "jar${name.capitalize()}", Jar + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDir + artifacts.add('archives', task); +} + +apply from: '../gradle/maven_push.gradle' diff --git a/library/gradle.properties b/library/gradle.properties new file mode 100644 index 000000000..c1679b17d --- /dev/null +++ b/library/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=Universal Image Loader Library +POM_ARTIFACT_ID=universal-image-loader +POM_PACKAGING=jar diff --git a/library/pom.xml b/library/pom.xml deleted file mode 100644 index 080e3bdb8..000000000 --- a/library/pom.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - 4.0.0 - com.nostra13.universalimageloader - universal-image-loader - jar - Universal Image Loader Library - - - com.nostra13.universalimageloader - parent - 1.8.6-SNAPSHOT - - - - - - - com.squareup - fest-android - 1.0.3 - test - - - org.robolectric - robolectric - 2.0-alpha-1 - test - - - junit - junit - 4.8.2 - test - - - - - com.google.android - android - provided - - - - - src - test - - - org.codehaus.mojo - build-helper-maven-plugin - 1.7 - - - add-test-sources - generate-test-sources - - add-test-source - - - - ${project.basedir}/test-gen - - - - - - - com.jayway.maven.plugins.android.generation2 - android-maven-plugin - - - org.apache.maven.plugins - maven-eclipse-plugin - - - org.apache.maven.plugins - maven-source-plugin - - - org.apache.maven.plugins - maven-javadoc-plugin - - - - - - \ No newline at end of file diff --git a/library/project.properties b/library/project.properties index acfc74eb1..b756f4487 100644 --- a/library/project.properties +++ b/library/project.properties @@ -8,5 +8,5 @@ # project structure. # Project target. -target=android-16 +target=android-21 android.library=true diff --git a/library/src/com/nostra13/universalimageloader/cache/disc/BaseDiscCache.java b/library/src/com/nostra13/universalimageloader/cache/disc/BaseDiscCache.java deleted file mode 100644 index 9499a5d35..000000000 --- a/library/src/com/nostra13/universalimageloader/cache/disc/BaseDiscCache.java +++ /dev/null @@ -1,70 +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.cache.disc; - -import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; -import com.nostra13.universalimageloader.core.DefaultConfigurationFactory; - -import java.io.File; - -/** - * Base disc cache. Implements common functionality for disc cache. - * - * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) - * @see DiscCacheAware - * @see FileNameGenerator - * @since 1.0.0 - */ -public abstract class BaseDiscCache implements DiscCacheAware { - - private static final String ERROR_ARG_NULL = "\"%s\" argument must be not null"; - - protected File cacheDir; - - private FileNameGenerator fileNameGenerator; - - public BaseDiscCache(File cacheDir) { - this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator()); - } - - public BaseDiscCache(File cacheDir, 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.fileNameGenerator = fileNameGenerator; - } - - @Override - public File get(String key) { - String fileName = fileNameGenerator.generate(key); - return new File(cacheDir, fileName); - } - - @Override - public void clear() { - File[] files = cacheDir.listFiles(); - if (files != null) { - for (File f : files) { - f.delete(); - } - } - } -} \ No newline at end of file diff --git a/library/src/com/nostra13/universalimageloader/cache/disc/DiscCacheAware.java b/library/src/com/nostra13/universalimageloader/cache/disc/DiscCacheAware.java deleted file mode 100644 index 524c28d4c..000000000 --- a/library/src/com/nostra13/universalimageloader/cache/disc/DiscCacheAware.java +++ /dev/null @@ -1,43 +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.cache.disc; - -import java.io.File; - -/** - * Interface for disc cache - * - * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) - * @since 1.0.0 - */ -public interface DiscCacheAware { - /** - * This method must not to save file on file system in fact. It is called after image was cached in cache directory - * and it was decoded to bitmap in memory. Such order is required to prevent possible deletion of file after it was - * cached on disc and before it was tried to decode to bitmap. - */ - void put(String key, File file); - - /** - * Returns {@linkplain File file object} appropriate incoming key.
- * NOTE: Must not to return a null. Method must return specific {@linkplain File file object} for - * incoming key whether file exists or not. - */ - File get(String key); - - /** Clears cache directory */ - void clear(); -} diff --git a/library/src/com/nostra13/universalimageloader/cache/disc/LimitedDiscCache.java b/library/src/com/nostra13/universalimageloader/cache/disc/LimitedDiscCache.java deleted file mode 100644 index 8e63a80b9..000000000 --- a/library/src/com/nostra13/universalimageloader/cache/disc/LimitedDiscCache.java +++ /dev/null @@ -1,162 +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.cache.disc; - -import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; -import com.nostra13.universalimageloader.core.DefaultConfigurationFactory; - -import java.io.File; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Abstract disc cache limited by some parameter. If cache exceeds specified limit then file with the most oldest last - * usage date will be deleted. - * - * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) - * @see BaseDiscCache - * @see FileNameGenerator - * @since 1.0.0 - */ -public abstract class LimitedDiscCache extends BaseDiscCache { - - private static final int INVALID_SIZE = -1; - - private final AtomicInteger cacheSize; - - private final int sizeLimit; - - private final Map lastUsageDates = Collections.synchronizedMap(new HashMap()); - - /** - * @param cacheDir Directory for file caching. Important: Specify separate folder for cached files. It's - * needed for right cache limit work. - * @param sizeLimit Cache limit value. If cache exceeds this limit then file with the most oldest last usage date - * will be deleted. - */ - public LimitedDiscCache(File cacheDir, int sizeLimit) { - this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator(), sizeLimit); - } - - /** - * @param cacheDir Directory for file caching. Important: Specify separate folder for cached files. It's - * needed for right cache limit work. - * @param fileNameGenerator Name generator for cached files - * @param sizeLimit Cache limit value. If cache exceeds this limit then file with the most oldest last usage date - * will be deleted. - */ - public LimitedDiscCache(File cacheDir, FileNameGenerator fileNameGenerator, int sizeLimit) { - super(cacheDir, fileNameGenerator); - this.sizeLimit = sizeLimit; - cacheSize = new AtomicInteger(); - calculateCacheSizeAndFillUsageMap(); - } - - private void calculateCacheSizeAndFillUsageMap() { - new Thread(new Runnable() { - @Override - public void run() { - int size = 0; - File[] cachedFiles = cacheDir.listFiles(); - if (cachedFiles != null) { // rarely but it can happen, don't know why - for (File cachedFile : cachedFiles) { - size += getSize(cachedFile); - lastUsageDates.put(cachedFile, cachedFile.lastModified()); - } - cacheSize.set(size); - } - } - }).start(); - } - - @Override - public void put(String key, File file) { - int valueSize = getSize(file); - int curCacheSize = cacheSize.get(); - - while (curCacheSize + valueSize > sizeLimit) { - int freedSize = removeNext(); - if (freedSize == INVALID_SIZE) break; // cache is empty (have nothing to delete) - curCacheSize = cacheSize.addAndGet(-freedSize); - } - cacheSize.addAndGet(valueSize); - - Long currentTime = System.currentTimeMillis(); - file.setLastModified(currentTime); - lastUsageDates.put(file, currentTime); - } - - @Override - public File get(String key) { - File file = super.get(key); - - Long currentTime = System.currentTimeMillis(); - file.setLastModified(currentTime); - lastUsageDates.put(file, currentTime); - - return file; - } - - @Override - public void clear() { - lastUsageDates.clear(); - cacheSize.set(0); - super.clear(); - } - - /** Remove next file and returns it's size */ - private int removeNext() { - if (lastUsageDates.isEmpty()) { - return INVALID_SIZE; - } - Long oldestUsage = null; - File mostLongUsedFile = null; - Set> entries = lastUsageDates.entrySet(); - synchronized (lastUsageDates) { - for (Entry entry : entries) { - if (mostLongUsedFile == null) { - mostLongUsedFile = entry.getKey(); - oldestUsage = entry.getValue(); - } else { - Long lastValueUsage = entry.getValue(); - if (lastValueUsage < oldestUsage) { - oldestUsage = lastValueUsage; - mostLongUsedFile = entry.getKey(); - } - } - } - } - - int fileSize = 0; - if (mostLongUsedFile != null) { - if (mostLongUsedFile.exists()) { - fileSize = getSize(mostLongUsedFile); - if (mostLongUsedFile.delete()) { - lastUsageDates.remove(mostLongUsedFile); - } - } else { - lastUsageDates.remove(mostLongUsedFile); - } - } - return fileSize; - } - - protected abstract int getSize(File file); -} \ No newline at end of file diff --git a/library/src/com/nostra13/universalimageloader/cache/disc/impl/FileCountLimitedDiscCache.java b/library/src/com/nostra13/universalimageloader/cache/disc/impl/FileCountLimitedDiscCache.java deleted file mode 100644 index 6c4a4f8e6..000000000 --- a/library/src/com/nostra13/universalimageloader/cache/disc/impl/FileCountLimitedDiscCache.java +++ /dev/null @@ -1,58 +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.cache.disc.impl; - -import com.nostra13.universalimageloader.cache.disc.LimitedDiscCache; -import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; -import com.nostra13.universalimageloader.core.DefaultConfigurationFactory; - -import java.io.File; - -/** - * Disc cache limited by file count. If file count in cache directory exceeds specified limit then file with the most - * oldest last usage date will be deleted. - * - * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) - * @see LimitedDiscCache - * @since 1.0.0 - */ -public class FileCountLimitedDiscCache extends LimitedDiscCache { - /** - * @param cacheDir Directory for file caching. Important: Specify separate folder for cached files. It's - * needed for right cache limit work. - * @param maxFileCount Maximum file count for cache. If file count in cache directory exceeds this limit then file - * with the most oldest last usage date will be deleted. - */ - public FileCountLimitedDiscCache(File cacheDir, int maxFileCount) { - this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator(), maxFileCount); - } - - /** - * @param cacheDir Directory for file caching. Important: Specify separate folder for cached files. It's - * needed for right cache limit work. - * @param fileNameGenerator Name generator for cached files - * @param maxFileCount Maximum file count for cache. If file count in cache directory exceeds this limit then file - * with the most oldest last usage date will be deleted. - */ - public FileCountLimitedDiscCache(File cacheDir, FileNameGenerator fileNameGenerator, int maxFileCount) { - super(cacheDir, fileNameGenerator, maxFileCount); - } - - @Override - protected int getSize(File file) { - return 1; - } -} diff --git a/library/src/com/nostra13/universalimageloader/cache/disc/impl/TotalSizeLimitedDiscCache.java b/library/src/com/nostra13/universalimageloader/cache/disc/impl/TotalSizeLimitedDiscCache.java deleted file mode 100644 index e98bb95ef..000000000 --- a/library/src/com/nostra13/universalimageloader/cache/disc/impl/TotalSizeLimitedDiscCache.java +++ /dev/null @@ -1,66 +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.cache.disc.impl; - -import com.nostra13.universalimageloader.cache.disc.LimitedDiscCache; -import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; -import com.nostra13.universalimageloader.core.DefaultConfigurationFactory; -import com.nostra13.universalimageloader.utils.L; - -import java.io.File; - -/** - * Disc cache limited by total cache size. If cache size exceeds specified limit then file with the most oldest last - * usage date will be deleted. - * - * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) - * @see LimitedDiscCache - * @since 1.0.0 - */ -public class TotalSizeLimitedDiscCache extends LimitedDiscCache { - - private static final int MIN_NORMAL_CACHE_SIZE_IN_MB = 2; - private static final int MIN_NORMAL_CACHE_SIZE = MIN_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024; - - /** - * @param cacheDir Directory for file caching. Important: Specify separate folder for cached files. It's - * needed for right cache limit work. - * @param maxCacheSize Maximum cache directory size (in bytes). If cache size exceeds this limit then file with the - * most oldest last usage date will be deleted. - */ - public TotalSizeLimitedDiscCache(File cacheDir, int maxCacheSize) { - this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator(), maxCacheSize); - } - - /** - * @param cacheDir Directory for file caching. Important: Specify separate folder for cached files. It's - * needed for right cache limit work. - * @param fileNameGenerator Name generator for cached files - * @param maxCacheSize Maximum cache directory size (in bytes). If cache size exceeds this limit then file with the - * most oldest last usage date will be deleted. - */ - public TotalSizeLimitedDiscCache(File cacheDir, FileNameGenerator fileNameGenerator, int maxCacheSize) { - super(cacheDir, fileNameGenerator, maxCacheSize); - if (maxCacheSize < MIN_NORMAL_CACHE_SIZE) { - L.w("You set too small disc cache size (less than %1$d Mb)", MIN_NORMAL_CACHE_SIZE_IN_MB); - } - } - - @Override - protected int getSize(File file) { - return (int) file.length(); - } -} diff --git a/library/src/com/nostra13/universalimageloader/core/ImageLoader.java b/library/src/com/nostra13/universalimageloader/core/ImageLoader.java deleted file mode 100644 index 2c9d3a9ad..000000000 --- a/library/src/com/nostra13/universalimageloader/core/ImageLoader.java +++ /dev/null @@ -1,440 +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.text.TextUtils; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.widget.ImageView; -import android.widget.ImageView.ScaleType; -import com.nostra13.universalimageloader.cache.disc.DiscCacheAware; -import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware; -import com.nostra13.universalimageloader.core.assist.*; -import com.nostra13.universalimageloader.core.display.BitmapDisplayer; -import com.nostra13.universalimageloader.core.display.FakeBitmapDisplayer; -import com.nostra13.universalimageloader.utils.ImageSizeUtils; -import com.nostra13.universalimageloader.utils.L; - -/** - * 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 final ImageLoadingListener emptyListener = new SimpleImageLoadingListener(); - private final BitmapDisplayer fakeBitmapDisplayer = new FakeBitmapDisplayer(); - - 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) { - if (configuration.writeLogs) 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 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, imageView, 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 DisplayImageOptions Display image options} for image 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, imageView, options, 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. - * @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, imageView, null, listener); - } - - /** - * 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 DisplayImageOptions Display image options} for image 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. - * @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) { - checkConfiguration(); - if (imageView == null) { - throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS); - } - if (listener == null) { - listener = emptyListener; - } - if (options == null) { - options = configuration.defaultDisplayImageOptions; - } - - if (TextUtils.isEmpty(uri)) { - engine.cancelDisplayTaskFor(imageView); - listener.onLoadingStarted(uri, imageView); - if (options.shouldShowImageForEmptyUri()) { - imageView.setImageResource(options.getImageForEmptyUri()); - } else { - imageView.setImageDrawable(null); - } - listener.onLoadingComplete(uri, imageView, null); - return; - } - - ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageView, configuration.maxImageWidthForMemoryCache, configuration.maxImageHeightForMemoryCache); - String memoryCacheKey = MemoryCacheUtil.generateKey(uri, targetSize); - engine.prepareDisplayTaskFor(imageView, memoryCacheKey); - - listener.onLoadingStarted(uri, imageView); - Bitmap bmp = configuration.memoryCache.get(memoryCacheKey); - if (bmp != null && !bmp.isRecycled()) { - if (configuration.writeLogs) L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey); - - if (options.shouldPostProcess()) { - ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, memoryCacheKey, options, listener, engine.getLockForUri(uri)); - ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo, options.getHandler()); - engine.submit(displayTask); - } else { - options.getDisplayer().display(bmp, imageView, LoadedFrom.MEMORY_CACHE); - listener.onLoadingComplete(uri, imageView, bmp); - } - } else { - if (options.shouldShowStubImage()) { - imageView.setImageResource(options.getStubImage()); - } else { - if (options.isResetViewBeforeLoading()) { - imageView.setImageDrawable(null); - } - } - - ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageView, targetSize, memoryCacheKey, options, listener, engine.getLockForUri(uri)); - LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo, options.getHandler()); - engine.submit(displayTask); - } - } - - /** - * 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. - * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before - */ - public void loadImage(String uri, ImageLoadingListener listener) { - loadImage(uri, null, null, listener); - } - - /** - * 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 minImageSize 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 minImageSize . - * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on UI - * thread. - * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before - */ - public void loadImage(String uri, ImageSize minImageSize, ImageLoadingListener listener) { - loadImage(uri, minImageSize, null, listener); - } - - /** - * 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 DisplayImageOptions Display image options} for image displaying. If null - - * default display image options - * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from - * configuration} will be used.
- * Incoming options should contain {@link FakeBitmapDisplayer} as displayer. - * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events 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); - } - - /** - * 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 minImageSize . - * @param options {@linkplain DisplayImageOptions Display image options} for image displaying. If null - - * default display image options - * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from - * configuration} will be used.
- * Incoming options should contain {@link FakeBitmapDisplayer} as displayer. - * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events 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) { - checkConfiguration(); - if (targetImageSize == null) { - targetImageSize = new ImageSize(configuration.maxImageWidthForMemoryCache, configuration.maxImageHeightForMemoryCache); - } - if (options == null) { - options = configuration.defaultDisplayImageOptions; - } - - DisplayImageOptions optionsWithFakeDisplayer; - if (options.getDisplayer() instanceof FakeBitmapDisplayer) { - optionsWithFakeDisplayer = options; - } else { - optionsWithFakeDisplayer = new DisplayImageOptions.Builder().cloneFrom(options).displayer(fakeBitmapDisplayer).build(); - } - - ImageView fakeImage = new ImageView(configuration.context); - fakeImage.setLayoutParams(new LayoutParams(targetImageSize.getWidth(), targetImageSize.getHeight())); - fakeImage.setScaleType(ScaleType.CENTER_CROP); - - displayImage(uri, fakeImage, optionsWithFakeDisplayer, listener); - } - - /** - * 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); - } - } - - /** - * Returns memory cache - * - * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before - */ - public MemoryCacheAware 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 exceptionSpecial valueBlocksTimes 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 applicablenot applicable
      Last Element (Tail)
      Throws exceptionSpecial valueBlocksTimes 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 applicablenot 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 exceptionSpecial valueBlocksTimes 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 applicablenot applicable
      Last Element (Tail)
      Throws exceptionSpecial valueBlocksTimes 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 applicablenot 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 exceptionSpecial valueThrows exceptionSpecial 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 exceptionSpecial valueThrows exceptionSpecial 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 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 c) { - return drainTo(c, Integer.MAX_VALUE); - } - - /** - * @throws UnsupportedOperationException {@inheritDoc} - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} - * @throws IllegalArgumentException {@inheritDoc} - */ - public int drainTo(Collection 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 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 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 c) { + return drainTo(c, Integer.MAX_VALUE); + } + + /** + * @throws UnsupportedOperationException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + public int drainTo(Collection 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 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.
      + * References {@link #ViewAware(android.view.View, boolean) ImageViewAware(imageView, true)}. + * + * @param view {@link android.view.View View} to work with + */ + public ViewAware(View view) { + this(view, true); + } + + /** + * Constructor + * + * @param view {@link android.view.View View} to work with + * @param checkActualViewSize true - then {@link #getWidth()} and {@link #getHeight()} will check actual + * size of View. 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 View, 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 ViewAware(View view, boolean checkActualViewSize) { + if (view == null) throw new IllegalArgumentException("view must not be null"); + + this.viewRef = new WeakReference(view); + this.checkActualViewSize = checkActualViewSize; + } + + /** + * {@inheritDoc} + *

      + * Width is defined by target {@link android.view.View view} parameters, configuration + * parameters or device display dimensions.
      + * Size computing algorithm (go by steps until get non-zero value):
      + * 1) Get the actual drawn getWidth() of the View
      + * 2) Get layout_width + */ + @Override + public int getWidth() { + View view = viewRef.get(); + if (view != null) { + final ViewGroup.LayoutParams params = view.getLayoutParams(); + int width = 0; + if (checkActualViewSize && params != null && params.width != ViewGroup.LayoutParams.WRAP_CONTENT) { + width = view.getWidth(); // Get actual image width + } + if (width <= 0 && params != null) width = params.width; // Get layout width parameter + return width; + } + return 0; + } + + /** + * {@inheritDoc} + *

      + * Height is defined by target {@link android.view.View view} parameters, configuration + * parameters or device display dimensions.
      + * Size computing algorithm (go by steps until get non-zero value):
      + * 1) Get the actual drawn getHeight() of the View
      + * 2) Get layout_height + */ + @Override + public int getHeight() { + View view = viewRef.get(); + if (view != null) { + final ViewGroup.LayoutParams params = view.getLayoutParams(); + int height = 0; + if (checkActualViewSize && params != null && params.height != ViewGroup.LayoutParams.WRAP_CONTENT) { + height = view.getHeight(); // Get actual image height + } + if (height <= 0 && params != null) height = params.height; // Get layout height parameter + return height; + } + return 0; + } + + @Override + public ViewScaleType getScaleType() { + return ViewScaleType.CROP; + } + + @Override + public View getWrappedView() { + return viewRef.get(); + } + + @Override + public boolean isCollected() { + return viewRef.get() == null; + } + + @Override + public int getId() { + View view = viewRef.get(); + return view == null ? super.hashCode() : view.hashCode(); + } + + @Override + public boolean setImageDrawable(Drawable drawable) { + if (Looper.myLooper() == Looper.getMainLooper()) { + View view = viewRef.get(); + if (view != null) { + setImageDrawableInto(drawable, view); + return true; + } + } else { + L.w(WARN_CANT_SET_DRAWABLE); + } + return false; + } + + @Override + public boolean setImageBitmap(Bitmap bitmap) { + if (Looper.myLooper() == Looper.getMainLooper()) { + View view = viewRef.get(); + if (view != null) { + setImageBitmapInto(bitmap, view); + return true; + } + } else { + L.w(WARN_CANT_SET_BITMAP); + } + return false; + } + + /** + * Should set drawable into incoming view. Incoming view is guaranteed not null.
      + * This method is called on UI thread. + */ + protected abstract void setImageDrawableInto(Drawable drawable, View view); + + /** + * Should set Bitmap into incoming view. Incoming view is guaranteed not null.< br /> + * This method is called on UI thread. + */ + protected abstract void setImageBitmapInto(Bitmap bitmap, View view); +} diff --git a/library/src/com/nostra13/universalimageloader/core/assist/ImageLoadingListener.java b/library/src/main/java/com/nostra13/universalimageloader/core/listener/ImageLoadingListener.java similarity index 79% rename from library/src/com/nostra13/universalimageloader/core/assist/ImageLoadingListener.java rename to library/src/main/java/com/nostra13/universalimageloader/core/listener/ImageLoadingListener.java index 1c4c8cd61..7be04fbcf 100644 --- a/library/src/com/nostra13/universalimageloader/core/assist/ImageLoadingListener.java +++ b/library/src/main/java/com/nostra13/universalimageloader/core/listener/ImageLoadingListener.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ -package com.nostra13.universalimageloader.core.assist; +package com.nostra13.universalimageloader.core.listener; import android.graphics.Bitmap; import android.view.View; +import com.nostra13.universalimageloader.core.assist.FailReason; /** * Listener for image loading process.
      @@ -24,7 +25,7 @@ * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @see SimpleImageLoadingListener - * @see FailReason + * @see com.nostra13.universalimageloader.core.assist.FailReason * @since 1.0.0 */ public interface ImageLoadingListener { @@ -41,8 +42,9 @@ public interface ImageLoadingListener { * Is called when an error was occurred during image loading * * @param imageUri Loading image URI - * @param view View for image - * @param failReason {@linkplain FailReason The reason} why image loading was failed + * @param view View for image. Can be null. + * @param failReason {@linkplain com.nostra13.universalimageloader.core.assist.FailReason The reason} why image + * loading was failed */ void onLoadingFailed(String imageUri, View view, FailReason failReason); @@ -50,7 +52,7 @@ public interface ImageLoadingListener { * Is called when image is loaded successfully (and displayed in View if one was specified) * * @param imageUri Loaded image URI - * @param view View for image + * @param view View for image. Can be null. * @param loadedImage Bitmap of loaded and decoded image */ void onLoadingComplete(String imageUri, View view, Bitmap loadedImage); @@ -59,7 +61,7 @@ public interface ImageLoadingListener { * Is called when image loading task was cancelled because View for image was reused in newer task * * @param imageUri Loading image URI - * @param view View for image + * @param view View for image. Can be null. */ void onLoadingCancelled(String imageUri, View view); } diff --git a/library/src/com/nostra13/universalimageloader/utils/IoUtils.java b/library/src/main/java/com/nostra13/universalimageloader/core/listener/ImageLoadingProgressListener.java similarity index 50% rename from library/src/com/nostra13/universalimageloader/utils/IoUtils.java rename to library/src/main/java/com/nostra13/universalimageloader/core/listener/ImageLoadingProgressListener.java index b32a84791..c9ce6848c 100644 --- a/library/src/com/nostra13/universalimageloader/utils/IoUtils.java +++ b/library/src/main/java/com/nostra13/universalimageloader/core/listener/ImageLoadingProgressListener.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright 2011-2013 Sergey Tarasevich + * 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. @@ -13,42 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ -package com.nostra13.universalimageloader.utils; +package com.nostra13.universalimageloader.core.listener; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import android.view.View; /** - * Provides I/O operations + * Listener for image loading progress. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) - * @since 1.0.0 + * @since 1.9.1 */ -public final class IoUtils { +public interface ImageLoadingProgressListener { - private static final int BUFFER_SIZE = 32 * 1024; // 32 KB - - private IoUtils() { - } - - public static void copyStream(InputStream is, OutputStream os) throws IOException { - byte[] bytes = new byte[BUFFER_SIZE]; - while (true) { - int count = is.read(bytes, 0, BUFFER_SIZE); - if (count == -1) { - break; - } - os.write(bytes, 0, count); - } - } - - public static void closeSilently(Closeable closeable) { - try { - closeable.close(); - } catch (Exception e) { - // Do nothing - } - } + /** + * Is called when image loading progress changed. + * + * @param imageUri Image URI + * @param view View for image. Can be null. + * @param current Downloaded size in bytes + * @param total Total size in bytes + */ + void onProgressUpdate(String imageUri, View view, int current, int total); } diff --git a/library/src/com/nostra13/universalimageloader/core/assist/PauseOnScrollListener.java b/library/src/main/java/com/nostra13/universalimageloader/core/listener/PauseOnScrollListener.java similarity index 93% rename from library/src/com/nostra13/universalimageloader/core/assist/PauseOnScrollListener.java rename to library/src/main/java/com/nostra13/universalimageloader/core/listener/PauseOnScrollListener.java index ee4572386..870822f65 100644 --- a/library/src/com/nostra13/universalimageloader/core/assist/PauseOnScrollListener.java +++ b/library/src/main/java/com/nostra13/universalimageloader/core/listener/PauseOnScrollListener.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ -package com.nostra13.universalimageloader.core.assist; +package com.nostra13.universalimageloader.core.listener; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; @@ -56,10 +56,11 @@ public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boo * @param imageLoader {@linkplain ImageLoader} instance for controlling * @param pauseOnScroll Whether {@linkplain ImageLoader#pause() pause ImageLoader} during touch scrolling * @param pauseOnFling Whether {@linkplain ImageLoader#pause() pause ImageLoader} during fling - * @param customListener Your custom {@link OnScrollListener} for {@linkplain AbsListView list view} which also will - * be get scroll events + * @param customListener Your custom {@link OnScrollListener} for {@linkplain AbsListView list view} which also + * will be get scroll events */ - public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling, OnScrollListener customListener) { + public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling, + OnScrollListener customListener) { this.imageLoader = imageLoader; this.pauseOnScroll = pauseOnScroll; this.pauseOnFling = pauseOnFling; diff --git a/library/src/com/nostra13/universalimageloader/core/assist/SimpleImageLoadingListener.java b/library/src/main/java/com/nostra13/universalimageloader/core/listener/SimpleImageLoadingListener.java similarity index 85% rename from library/src/com/nostra13/universalimageloader/core/assist/SimpleImageLoadingListener.java rename to library/src/main/java/com/nostra13/universalimageloader/core/listener/SimpleImageLoadingListener.java index e0569c482..3e42223d5 100644 --- a/library/src/com/nostra13/universalimageloader/core/assist/SimpleImageLoadingListener.java +++ b/library/src/main/java/com/nostra13/universalimageloader/core/listener/SimpleImageLoadingListener.java @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ -package com.nostra13.universalimageloader.core.assist; +package com.nostra13.universalimageloader.core.listener; import android.graphics.Bitmap; import android.view.View; +import com.nostra13.universalimageloader.core.assist.FailReason; /** * A convenient class to extend when you only want to listen for a subset of all the image loading events. This - * implements all methods in the {@link ImageLoadingListener} but does nothing. + * implements all methods in the {@link com.nostra13.universalimageloader.core.listener.ImageLoadingListener} but does + * nothing. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.4.0 diff --git a/library/src/com/nostra13/universalimageloader/core/process/BitmapProcessor.java b/library/src/main/java/com/nostra13/universalimageloader/core/process/BitmapProcessor.java similarity index 100% rename from library/src/com/nostra13/universalimageloader/core/process/BitmapProcessor.java rename to library/src/main/java/com/nostra13/universalimageloader/core/process/BitmapProcessor.java diff --git a/library/src/com/nostra13/universalimageloader/core/assist/DiscCacheUtil.java b/library/src/main/java/com/nostra13/universalimageloader/utils/DiskCacheUtils.java similarity index 60% rename from library/src/com/nostra13/universalimageloader/core/assist/DiscCacheUtil.java rename to library/src/main/java/com/nostra13/universalimageloader/utils/DiskCacheUtils.java index 7d438fd9e..3b17fd7d4 100644 --- a/library/src/com/nostra13/universalimageloader/core/assist/DiscCacheUtil.java +++ b/library/src/main/java/com/nostra13/universalimageloader/utils/DiskCacheUtils.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. @@ -13,37 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ -package com.nostra13.universalimageloader.core.assist; +package com.nostra13.universalimageloader.utils; -import com.nostra13.universalimageloader.cache.disc.DiscCacheAware; +import com.nostra13.universalimageloader.cache.disc.DiskCache; import java.io.File; /** - * Utility for convenient work with disc cache.
      + * Utility for convenient work with disk cache.
      * NOTE: This utility works with file system so avoid using it on application main thread. * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.8.0 */ -public final class DiscCacheUtil { +public final class DiskCacheUtils { - private DiscCacheUtil() { + private DiskCacheUtils() { } - /** Returns {@link File} of cached image or null if image was not cached in disc cache */ - public static File findInCache(String imageUri, DiscCacheAware discCache) { - File image = discCache.get(imageUri); - return image.exists() ? image : null; + /** Returns {@link File} of cached image or null if image was not cached in disk cache */ + public static File findInCache(String imageUri, DiskCache diskCache) { + File image = diskCache.get(imageUri); + return image != null && image.exists() ? image : null; } /** - * Removed cached image file from disc cache (if image was cached in disc cache before) + * Removed cached image file from disk cache (if image was cached in disk cache before) * * @return true - if cached image file existed and was deleted; false - otherwise. */ - public static boolean removeFromCache(String imageUri, DiscCacheAware discCache) { - File image = discCache.get(imageUri); - return image.delete(); + public static boolean removeFromCache(String imageUri, DiskCache diskCache) { + File image = diskCache.get(imageUri); + return image != null && image.exists() && image.delete(); } } diff --git a/library/src/main/java/com/nostra13/universalimageloader/utils/ImageSizeUtils.java b/library/src/main/java/com/nostra13/universalimageloader/utils/ImageSizeUtils.java new file mode 100644 index 000000000..7437587d7 --- /dev/null +++ b/library/src/main/java/com/nostra13/universalimageloader/utils/ImageSizeUtils.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * 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.utils; + +import android.graphics.BitmapFactory; +import android.opengl.GLES10; + +import com.nostra13.universalimageloader.core.assist.ImageSize; +import com.nostra13.universalimageloader.core.assist.ViewScaleType; +import com.nostra13.universalimageloader.core.imageaware.ImageAware; + +import javax.microedition.khronos.opengles.GL10; + +/** + * Provides calculations with image sizes, scales + * + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) + * @since 1.8.3 + */ +public final class ImageSizeUtils { + + private static final int DEFAULT_MAX_BITMAP_DIMENSION = 2048; + + private static ImageSize maxBitmapSize; + + static { + int[] maxTextureSize = new int[1]; + GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0); + int maxBitmapDimension = Math.max(maxTextureSize[0], DEFAULT_MAX_BITMAP_DIMENSION); + maxBitmapSize = new ImageSize(maxBitmapDimension, maxBitmapDimension); + } + + private ImageSizeUtils() { + } + + /** + * Defines target size for image aware view. Size is defined by target + * {@link com.nostra13.universalimageloader.core.imageaware.ImageAware view} parameters, configuration + * parameters or device display dimensions.
      + */ + public static ImageSize defineTargetSizeForView(ImageAware imageAware, ImageSize maxImageSize) { + int width = imageAware.getWidth(); + if (width <= 0) { + width = maxImageSize.getWidth(); + } else { + width = Math.min(width, maxImageSize.getWidth()); + } + + int height = imageAware.getHeight(); + if (height <= 0) { + height = maxImageSize.getHeight(); + } else { + height = Math.min(height, maxImageSize.getHeight()); + } + + return new ImageSize(width, height); + } + + /** + * 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) { + final int srcWidth = srcSize.getWidth(); + final int srcHeight = srcSize.getHeight(); + final int targetWidth = targetSize.getWidth(); + final int targetHeight = targetSize.getHeight(); + + int scale = 1; + + switch (viewScaleType) { + case FIT_INSIDE: + if (powerOf2Scale) { + final int halfWidth = srcWidth / 2; + final int halfHeight = srcHeight / 2; + while ((halfWidth / scale) > targetWidth || (halfHeight / scale) > targetHeight) { // || + scale *= 2; + } + } else { + scale = Math.max(srcWidth / targetWidth, srcHeight / targetHeight); // max + } + break; + case CROP: + if (powerOf2Scale) { + final int halfWidth = srcWidth / 2; + final int halfHeight = srcHeight / 2; + while ((halfWidth / scale) > targetWidth && (halfHeight / scale) > targetHeight) { // && + scale *= 2; + } + } else { + scale = Math.min(srcWidth / targetWidth, srcHeight / targetHeight); // min + } + break; + } + + if (scale < 1) { + scale = 1; + } + scale = considerMaxTextureSize(srcWidth, srcHeight, scale, powerOf2Scale); + + return scale; + } + + private static int considerMaxTextureSize(int srcWidth, int srcHeight, int scale, boolean powerOf2) { + final int maxWidth = maxBitmapSize.getWidth(); + final int maxHeight = maxBitmapSize.getHeight(); + while ((srcWidth / scale) > maxWidth || (srcHeight / scale) > maxHeight) { + if (powerOf2) { + scale *= 2; + } else { + scale++; + } + } + return scale; + } + + /** + * Computes minimal sample size for downscaling image so result image size won't exceed max acceptable OpenGL + * texture size.
      + * We can't create Bitmap in memory with size exceed max texture size (usually this is 2048x2048) so this method + * calculate minimal sample size which should be applied to image to fit into these limits. + * + * @param srcSize Original image size + * @return Minimal sample size + */ + public static int computeMinImageSampleSize(ImageSize srcSize) { + final int srcWidth = srcSize.getWidth(); + final int srcHeight = srcSize.getHeight(); + final int targetWidth = maxBitmapSize.getWidth(); + final int targetHeight = maxBitmapSize.getHeight(); + + final int widthScale = (int) Math.ceil((float) srcWidth / targetWidth); + final int heightScale = (int) Math.ceil((float) srcHeight / targetHeight); + + return Math.max(widthScale, heightScale); // max + } + + /** + * 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) { + final int srcWidth = srcSize.getWidth(); + final int srcHeight = srcSize.getHeight(); + final int targetWidth = targetSize.getWidth(); + final int targetHeight = targetSize.getHeight(); + + final float widthScale = (float) srcWidth / targetWidth; + final float heightScale = (float) srcHeight / targetHeight; + + final int destWidth; + final 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/java/com/nostra13/universalimageloader/utils/IoUtils.java b/library/src/main/java/com/nostra13/universalimageloader/utils/IoUtils.java new file mode 100644 index 000000000..498f3c2fc --- /dev/null +++ b/library/src/main/java/com/nostra13/universalimageloader/utils/IoUtils.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * 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.utils; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Provides I/O operations + * + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) + * @since 1.0.0 + */ +public final class IoUtils { + + /** {@value} */ + public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 KB + /** {@value} */ + public static final int DEFAULT_IMAGE_TOTAL_SIZE = 500 * 1024; // 500 Kb + /** {@value} */ + public static final int CONTINUE_LOADING_PERCENTAGE = 75; + + private IoUtils() { + } + + /** + * Copies stream, fires progress events by listener, can be interrupted by listener. Uses buffer size = + * {@value #DEFAULT_BUFFER_SIZE} bytes. + * + * @param is Input stream + * @param os Output stream + * @param listener null-ok; Listener of copying progress and controller of copying interrupting + * @return true - if stream copied successfully; false - if copying was interrupted by listener + * @throws IOException + */ + public static boolean copyStream(InputStream is, OutputStream os, CopyListener listener) throws IOException { + return copyStream(is, os, listener, DEFAULT_BUFFER_SIZE); + } + + /** + * Copies stream, fires progress events by listener, can be interrupted by listener. + * + * @param is Input stream + * @param os Output stream + * @param listener null-ok; Listener of copying progress and controller of copying interrupting + * @param bufferSize Buffer size for copying, also represents a step for firing progress listener callback, i.e. + * progress event will be fired after every copied bufferSize bytes + * @return true - if stream copied successfully; false - if copying was interrupted by listener + * @throws IOException + */ + public static boolean copyStream(InputStream is, OutputStream os, CopyListener listener, int bufferSize) + throws IOException { + int current = 0; + int total = is.available(); + if (total <= 0) { + total = DEFAULT_IMAGE_TOTAL_SIZE; + } + + final byte[] bytes = new byte[bufferSize]; + int count; + if (shouldStopLoading(listener, current, total)) return false; + while ((count = is.read(bytes, 0, bufferSize)) != -1) { + os.write(bytes, 0, count); + current += count; + if (shouldStopLoading(listener, current, total)) return false; + } + os.flush(); + return true; + } + + private static boolean shouldStopLoading(CopyListener listener, int current, int total) { + if (listener != null) { + boolean shouldContinue = listener.onBytesCopied(current, total); + if (!shouldContinue) { + if (100 * current / total < CONTINUE_LOADING_PERCENTAGE) { + return true; // if loaded more than 75% then continue loading anyway + } + } + } + return false; + } + + /** + * Reads all data from stream and close it silently + * + * @param is Input stream + */ + public static void readAndCloseStream(InputStream is) { + final byte[] bytes = new byte[DEFAULT_BUFFER_SIZE]; + try { + while (is.read(bytes, 0, DEFAULT_BUFFER_SIZE) != -1); + } catch (IOException ignored) { + } finally { + closeSilently(is); + } + } + + public static void closeSilently(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception ignored) { + } + } + } + + /** Listener and controller for copy process */ + public static interface CopyListener { + /** + * @param current Loaded bytes + * @param total Total bytes for loading + * @return true - if copying should be continued; false - if copying should be interrupted + */ + boolean onBytesCopied(int current, int total); + } +} diff --git a/library/src/com/nostra13/universalimageloader/utils/L.java b/library/src/main/java/com/nostra13/universalimageloader/utils/L.java similarity index 63% rename from library/src/com/nostra13/universalimageloader/utils/L.java rename to library/src/main/java/com/nostra13/universalimageloader/utils/L.java index be095acc7..427b8671e 100644 --- a/library/src/com/nostra13/universalimageloader/utils/L.java +++ b/library/src/main/java/com/nostra13/universalimageloader/utils/L.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. @@ -19,7 +19,7 @@ import com.nostra13.universalimageloader.core.ImageLoader; /** - * "Less-word" analog of Android {@link Log logger} + * "Less-word" analog of Android {@link android.util.Log logger} * * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.6.4 @@ -27,23 +27,51 @@ public final class L { private static final String LOG_FORMAT = "%1$s\n%2$s"; - private static volatile boolean DISABLED = false; + private static volatile boolean writeDebugLogs = false; + private static volatile boolean writeLogs = true; private L() { } - /** Enables logger (if {@link #disableLogging()} was called before) */ + /** + * Enables logger (if {@link #disableLogging()} was called before) + * + * @deprecated Use {@link #writeLogs(boolean) writeLogs(true)} instead + */ + @Deprecated public static void enableLogging() { - DISABLED = false; + writeLogs(true); } - /** Disables logger, no logs will be passed to LogCat, all log methods will do nothing */ + /** + * Disables logger, no logs will be passed to LogCat, all log methods will do nothing + * + * @deprecated Use {@link #writeLogs(boolean) writeLogs(false)} instead + */ + @Deprecated public static void disableLogging() { - DISABLED = true; + writeLogs(false); + } + + /** + * Enables/disables detail logging of {@link ImageLoader} work. + * Consider {@link com.nostra13.universalimageloader.utils.L#disableLogging()} to disable + * ImageLoader logging completely (even error logs)
      + * Debug logs are disabled by default. + */ + public static void writeDebugLogs(boolean writeDebugLogs) { + L.writeDebugLogs = writeDebugLogs; + } + + /** Enables/disables logging of {@link ImageLoader} completely (even error logs). */ + public static void writeLogs(boolean writeLogs) { + L.writeLogs = writeLogs; } public static void d(String message, Object... args) { - log(Log.DEBUG, null, message, args); + if (writeDebugLogs) { + log(Log.DEBUG, null, message, args); + } } public static void i(String message, Object... args) { @@ -67,7 +95,7 @@ public static void e(Throwable ex, String message, Object... args) { } private static void log(int priority, Throwable ex, String message, Object... args) { - if (DISABLED) return; + if (!writeLogs) return; if (args.length > 0) { message = String.format(message, args); } diff --git a/library/src/com/nostra13/universalimageloader/core/assist/MemoryCacheUtil.java b/library/src/main/java/com/nostra13/universalimageloader/utils/MemoryCacheUtils.java similarity index 92% rename from library/src/com/nostra13/universalimageloader/core/assist/MemoryCacheUtil.java rename to library/src/main/java/com/nostra13/universalimageloader/utils/MemoryCacheUtils.java index cb78fd0bb..39bed8799 100644 --- a/library/src/com/nostra13/universalimageloader/core/assist/MemoryCacheUtil.java +++ b/library/src/main/java/com/nostra13/universalimageloader/utils/MemoryCacheUtils.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. @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ -package com.nostra13.universalimageloader.core.assist; +package com.nostra13.universalimageloader.utils; import android.graphics.Bitmap; -import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware; + +import com.nostra13.universalimageloader.cache.memory.MemoryCache; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.core.assist.ImageSize; import java.util.ArrayList; import java.util.Comparator; @@ -29,12 +31,12 @@ * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) * @since 1.6.3 */ -public final class MemoryCacheUtil { +public final class MemoryCacheUtils { private static final String URI_AND_SIZE_SEPARATOR = "_"; private static final String WIDTH_AND_HEIGHT_SEPARATOR = "x"; - private MemoryCacheUtil() { + private MemoryCacheUtils() { } /** @@ -62,7 +64,7 @@ public int compare(String key1, String key2) { * {@link ImageLoaderConfiguration.Builder#denyCacheImageMultipleSizesInMemory() * denyCacheImageMultipleSizesInMemory()} option in {@linkplain ImageLoaderConfiguration configuration} */ - public static List findCachedBitmapsForImageUri(String imageUri, MemoryCacheAware memoryCache) { + public static List findCachedBitmapsForImageUri(String imageUri, MemoryCache memoryCache) { List values = new ArrayList(); for (String key : memoryCache.keys()) { if (key.startsWith(imageUri)) { @@ -78,7 +80,7 @@ public static List findCachedBitmapsForImageUri(String imageUri, MemoryC * {@link ImageLoaderConfiguration.Builder#denyCacheImageMultipleSizesInMemory() * denyCacheImageMultipleSizesInMemory()} option in {@linkplain ImageLoaderConfiguration configuration} */ - public static List findCacheKeysForImageUri(String imageUri, MemoryCacheAware memoryCache) { + public static List findCacheKeysForImageUri(String imageUri, MemoryCache memoryCache) { List values = new ArrayList(); for (String key : memoryCache.keys()) { if (key.startsWith(imageUri)) { @@ -94,7 +96,7 @@ public static List findCacheKeysForImageUri(String imageUri, MemoryCache * {@link ImageLoaderConfiguration.Builder#denyCacheImageMultipleSizesInMemory() * denyCacheImageMultipleSizesInMemory()} option in {@linkplain ImageLoaderConfiguration configuration} */ - public static void removeFromCache(String imageUri, MemoryCacheAware memoryCache) { + public static void removeFromCache(String imageUri, MemoryCache memoryCache) { List keysToRemove = new ArrayList(); for (String key : memoryCache.keys()) { if (key.startsWith(imageUri)) { diff --git a/library/src/com/nostra13/universalimageloader/utils/StorageUtils.java b/library/src/main/java/com/nostra13/universalimageloader/utils/StorageUtils.java similarity index 55% rename from library/src/com/nostra13/universalimageloader/utils/StorageUtils.java rename to library/src/main/java/com/nostra13/universalimageloader/utils/StorageUtils.java index 082fe82ab..13026495f 100644 --- a/library/src/com/nostra13/universalimageloader/utils/StorageUtils.java +++ b/library/src/main/java/com/nostra13/universalimageloader/utils/StorageUtils.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. @@ -44,19 +44,45 @@ private StorageUtils() { * Android defines cache directory on device's file system. * * @param context Application context - * @return Cache {@link File directory} + * @return Cache {@link File directory}.
      + * NOTE: Can be null in some unpredictable cases (if SD card is unmounted and + * {@link android.content.Context#getCacheDir() Context.getCacheDir()} returns null). */ public static File getCacheDirectory(Context context) { + return getCacheDirectory(context, true); + } + + /** + * Returns application cache directory. Cache directory will be created on SD card + * ("/Android/data/[app_package_name]/cache") (if card is mounted and app has appropriate permission) or + * on device's file system depending incoming parameters. + * + * @param context Application context + * @param preferExternal Whether prefer external location for cache + * @return Cache {@link File directory}.
      + * NOTE: Can be null in some unpredictable cases (if SD card is unmounted and + * {@link android.content.Context#getCacheDir() Context.getCacheDir()} returns null). + */ + public static File getCacheDirectory(Context context, boolean preferExternal) { File appCacheDir = null; - if (Environment.getExternalStorageState().equals(MEDIA_MOUNTED) && hasExternalStoragePermission(context)) { + String externalStorageState; + try { + externalStorageState = Environment.getExternalStorageState(); + } catch (NullPointerException e) { // (sh)it happens (Issue #660) + externalStorageState = ""; + } catch (IncompatibleClassChangeError e) { // (sh)it happens too (Issue #989) + externalStorageState = ""; + } + if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState) && hasExternalStoragePermission(context)) { appCacheDir = getExternalCacheDir(context); } if (appCacheDir == null) { appCacheDir = context.getCacheDir(); } if (appCacheDir == null) { - L.w("Can't define system cache directory!"); - appCacheDir = context.getCacheDir(); // retry + String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/"; + L.w("Can't define system cache directory! '%s' will be used.", cacheDirPath); + appCacheDir = new File(cacheDirPath); } return appCacheDir; } @@ -70,11 +96,24 @@ public static File getCacheDirectory(Context context) { * @return Cache {@link File directory} */ public static File getIndividualCacheDirectory(Context context) { - File cacheDir = getCacheDirectory(context); - File individualCacheDir = new File(cacheDir, INDIVIDUAL_DIR_NAME); + return getIndividualCacheDirectory(context, INDIVIDUAL_DIR_NAME); + } + + /** + * Returns individual application cache directory (for only image caching from ImageLoader). Cache directory will be + * created on SD card ("/Android/data/[app_package_name]/cache/uil-images") if card is mounted and app has + * appropriate permission. Else - Android defines cache directory on device's file system. + * + * @param context Application context + * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images") + * @return Cache {@link File directory} + */ + public static File getIndividualCacheDirectory(Context context, String cacheDir) { + File appCacheDir = getCacheDirectory(context); + File individualCacheDir = new File(appCacheDir, cacheDir); if (!individualCacheDir.exists()) { if (!individualCacheDir.mkdir()) { - individualCacheDir = cacheDir; + individualCacheDir = appCacheDir; } } return individualCacheDir; @@ -90,7 +129,26 @@ public static File getIndividualCacheDirectory(Context context) { */ public static File getOwnCacheDirectory(Context context, String cacheDir) { File appCacheDir = null; - if (Environment.getExternalStorageState().equals(MEDIA_MOUNTED) && hasExternalStoragePermission(context)) { + if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) { + appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir); + } + if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) { + appCacheDir = context.getCacheDir(); + } + return appCacheDir; + } + + /** + * Returns specified application cache directory. Cache directory will be created on SD card by defined path if card + * is mounted and app has appropriate permission. Else - Android defines cache directory on device's file system. + * + * @param context Application context + * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images") + * @return Cache {@link File directory} + */ + public static File getOwnCacheDirectory(Context context, String cacheDir, boolean preferExternal) { + File appCacheDir = null; + if (preferExternal && MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) { appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir); } if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) { diff --git a/library/src/test/java/com/nostra13/universalimageloader/core/assist/ImageSizeTest.java b/library/src/test/java/com/nostra13/universalimageloader/core/assist/ImageSizeTest.java new file mode 100644 index 000000000..c28b8f331 --- /dev/null +++ b/library/src/test/java/com/nostra13/universalimageloader/core/assist/ImageSizeTest.java @@ -0,0 +1,180 @@ +package com.nostra13.universalimageloader.core.assist; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.nostra13.universalimageloader.core.imageaware.ImageAware; +import com.nostra13.universalimageloader.core.imageaware.ImageViewAware; +import com.nostra13.universalimageloader.utils.ImageSizeUtils; + +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class ImageSizeTest { + private Activity mActivity; + private ImageView mView; + private ImageAware mImageAware; + + @Before + public void setUp() throws Exception { + mActivity = new Activity(); + + // Make and set view with some prelim values to test + mView = new TestImageView(mActivity); + mView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + mView.measure(View.MeasureSpec.makeMeasureSpec(250, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(250, View.MeasureSpec.EXACTLY)); + + mImageAware = new ImageViewAware(mView); + } + + @Test + public void testGetImageSizeScaleTo_useImageActualSize() throws Exception { + // We layout the view to give it a width and height + mView.measure(View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY)); + mView.layout(0, 0, 200, 200); + + ImageSize expected = new ImageSize(200, 200); + ImageSize result = ImageSizeUtils.defineTargetSizeForView(mImageAware, new ImageSize(590, 590)); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getWidth()).isEqualTo(expected.getWidth()); + Assertions.assertThat(result.getHeight()).isEqualTo(expected.getHeight()); + } + + /** + * This will make sure the view falls back to the ViewParams/Max/Or Config if wrap content so that it is never + * shrunk to the first image size. In this case it falls back to the config size + * + * @throws Exception + */ + @Test + public void testGetImageSizeScaleTo_dontUseImageActualSizeWithWrapContent() throws Exception { + //Set it to wrap content so that it will fall back to + mView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + mView.measure(View.MeasureSpec.makeMeasureSpec(250, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(250, View.MeasureSpec.EXACTLY)); + // We layout the view to give it a width and height + mView.layout(0, 0, 200, 200); + + ImageSize expected = new ImageSize(500, 500); + ImageSize result = ImageSizeUtils.defineTargetSizeForView(mImageAware, new ImageSize(500, 500)); + Assertions.assertThat(result).isNotNull().isEqualsToByComparingFields(expected); + } + + @Test + public void testGetImageSizeScaleTo_useImageLayoutParams() throws Exception { + // Set a defined width + mView.setLayoutParams(new FrameLayout.LayoutParams(300, 300)); + + ImageSize expected = new ImageSize(300, 300); + ImageSize result = ImageSizeUtils.defineTargetSizeForView(mImageAware, new ImageSize(500, 500)); + Assertions.assertThat(result).isNotNull().isEqualsToByComparingFields(expected); + } + + @Test + public void testGetImageSizeScaleTo_useImageConfigMaxSize() throws Exception { + ImageSize expected = new ImageSize(500, 500); + ImageSize result = ImageSizeUtils.defineTargetSizeForView(mImageAware, new ImageSize(500, 500)); + Assertions.assertThat(result).isNotNull().isEqualsToByComparingFields(expected); + } + + @Test + public void testComputeImageSampleSize_fitInside() throws Exception { + final ViewScaleType scaleType = ViewScaleType.FIT_INSIDE; + int result; + + ImageSize srcSize = new ImageSize(300, 100); + ImageSize targetSize = new ImageSize(30, 10); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, false); + Assertions.assertThat(result).isEqualTo(10); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, true); + Assertions.assertThat(result).isEqualTo(8); + + srcSize = new ImageSize(300, 100); + targetSize = new ImageSize(200, 200); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, false); + Assertions.assertThat(result).isEqualTo(1); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, true); + Assertions.assertThat(result).isEqualTo(1); + + srcSize = new ImageSize(300, 100); + targetSize = new ImageSize(55, 40); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, false); + Assertions.assertThat(result).isEqualTo(5); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, true); + Assertions.assertThat(result).isEqualTo(4); + + srcSize = new ImageSize(300, 100); + targetSize = new ImageSize(30, 40); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, false); + Assertions.assertThat(result).isEqualTo(10); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, true); + Assertions.assertThat(result).isEqualTo(8); + + srcSize = new ImageSize(5000, 70); + targetSize = new ImageSize(2000, 30); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, false); + Assertions.assertThat(result).isEqualTo(3); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, true); + Assertions.assertThat(result).isEqualTo(4); + } + + @Test + public void testComputeImageSampleSize_centerCrop() throws Exception { + final ViewScaleType scaleType = ViewScaleType.CROP; + int result; + + ImageSize srcSize = new ImageSize(300, 100); + ImageSize targetSize = new ImageSize(30, 10); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, false); + Assertions.assertThat(result).isEqualTo(10); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, true); + Assertions.assertThat(result).isEqualTo(8); + + srcSize = new ImageSize(300, 100); + targetSize = new ImageSize(200, 200); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, false); + Assertions.assertThat(result).isEqualTo(1); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, true); + Assertions.assertThat(result).isEqualTo(1); + + srcSize = new ImageSize(300, 100); + targetSize = new ImageSize(55, 40); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, false); + Assertions.assertThat(result).isEqualTo(2); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, true); + Assertions.assertThat(result).isEqualTo(2); + + srcSize = new ImageSize(300, 100); + targetSize = new ImageSize(30, 30); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, false); + Assertions.assertThat(result).isEqualTo(3); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, true); + Assertions.assertThat(result).isEqualTo(2); + + srcSize = new ImageSize(5000, 70); + targetSize = new ImageSize(300, 30); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, false); + Assertions.assertThat(result).isEqualTo(3); + result = ImageSizeUtils.computeImageSampleSize(srcSize, targetSize, scaleType, true); + Assertions.assertThat(result).isEqualTo(4); + } + + /** Fixes {@link NoSuchMethodError} for ImageView#onLayout(...) */ + private class TestImageView extends ImageView { + TestImageView(Context activity) { + super(activity); + } + + @Override + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + } + } +} diff --git a/library/test/com/nostra13/universalimageloader/core/download/BaseImageDownloaderTest.java b/library/src/test/java/com/nostra13/universalimageloader/core/download/BaseImageDownloaderTest.java similarity index 97% rename from library/test/com/nostra13/universalimageloader/core/download/BaseImageDownloaderTest.java rename to library/src/test/java/com/nostra13/universalimageloader/core/download/BaseImageDownloaderTest.java index 10cde24dd..5edb1d4cf 100644 --- a/library/test/com/nostra13/universalimageloader/core/download/BaseImageDownloaderTest.java +++ b/library/src/test/java/com/nostra13/universalimageloader/core/download/BaseImageDownloaderTest.java @@ -1,6 +1,6 @@ package com.nostra13.universalimageloader.core.download; -import org.fest.assertions.api.Assertions; +import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; diff --git a/library/test-gen/com/nostra13/universalimageloader/R.java b/library/test-gen/com/nostra13/universalimageloader/R.java deleted file mode 100644 index 83c81468a..000000000 --- a/library/test-gen/com/nostra13/universalimageloader/R.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.nostra13.universalimageloader; - -public class R -{ - public static final class attr - { - } - - public static final class drawable - { - } - - public static final class id - { - } - - public static final class layout - { - } - - public static final class string - { - } -} \ No newline at end of file diff --git a/library/test/com/nostra13/universalimageloader/core/assist/ImageSizeTest.java b/library/test/com/nostra13/universalimageloader/core/assist/ImageSizeTest.java deleted file mode 100644 index ad558c4e6..000000000 --- a/library/test/com/nostra13/universalimageloader/core/assist/ImageSizeTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.nostra13.universalimageloader.core.assist; - -import org.fest.assertions.api.Assertions; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import android.app.Activity; -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.nostra13.universalimageloader.utils.ImageSizeUtils; - -@RunWith(RobolectricTestRunner.class) -public class ImageSizeTest { - private Activity mActivity; - private ImageView mView; - - @Before - public void setUp() throws Exception { - mActivity = new Activity(); - - // Make and set view with some prelim values to test - mView = new TestImageView(mActivity); - mView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - mView.measure(View.MeasureSpec.makeMeasureSpec(250, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(250, View.MeasureSpec.EXACTLY)); - } - - @Test - public void testGetImageSizeScaleTo_useImageActualSize() throws Exception { - // We layout the view to give it a width and height - mView.measure(View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY)); - mView.layout(0, 0, 200, 200); - - ImageSize expected = new ImageSize(200, 200); - ImageSize result = ImageSizeUtils.defineTargetSizeForView(mView, 590, 590); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getWidth()).isEqualTo(expected.getWidth()); - Assertions.assertThat(result.getHeight()).isEqualTo(expected.getHeight()); - } - - /** - * This will make sure the view falls back to the ViewParams/Max/Or Config if wrap content so that it is never - * shrunk to the first image size. In this case it falls back to the config size - * - * @throws Exception - */ - @Test - public void testGetImageSizeScaleTo_dontUseImageActualSizeWithWrapContent() throws Exception { - //Set it to wrap content so that it will fall back to - mView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - mView.measure(View.MeasureSpec.makeMeasureSpec(250, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(250, View.MeasureSpec.EXACTLY)); - // We layout the view to give it a width and height - mView.layout(0, 0, 200, 200); - - ImageSize expected = new ImageSize(500, 500); - ImageSize result = ImageSizeUtils.defineTargetSizeForView(mView, 500, 500); - Assertions.assertThat(result).isNotNull().isEqualsToByComparingFields(expected); - } - - @Test - public void testGetImageSizeScaleTo_useImageLayoutParams() throws Exception { - // Set a defined width - mView.setLayoutParams(new FrameLayout.LayoutParams(300, 300)); - - ImageSize expected = new ImageSize(300, 300); - ImageSize result = ImageSizeUtils.defineTargetSizeForView(mView, 500, 500); - Assertions.assertThat(result).isNotNull().isEqualsToByComparingFields(expected); - } - - @Test - public void testGetImageSizeScaleTo_useImageCacheMaxSize() throws Exception { - ImageSize expected = new ImageSize(500, 500); - ImageSize result = ImageSizeUtils.defineTargetSizeForView(mView, 500, 500); - Assertions.assertThat(result).isNotNull().isEqualsToByComparingFields(expected); - } - - @Test - public void testGetImageSizeScaleTo_useDisplayMetrics() throws Exception { - //The default Robolectic disp metrics are 480x800 normal hdpi device basically - ImageSize expected = new ImageSize(480, 800); - ImageSize result = ImageSizeUtils.defineTargetSizeForView(mView, 0, 0); - Assertions.assertThat(result).isNotNull().isEqualsToByComparingFields(expected); - } - - /** Fixes {@link NoSuchMethodError} for ImageView#onLayout(...) */ - private class TestImageView extends ImageView { - TestImageView(Context activity) { - super(activity); - } - - @Override - public void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - } - } -} diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 296317fec..000000000 --- a/pom.xml +++ /dev/null @@ -1,166 +0,0 @@ - - - - 4.0.0 - com.nostra13.universalimageloader - parent - pom - Universal Image Loader Project - 1.8.6-SNAPSHOT - Powerful and flexible instrument for asynchronous image loading, caching and displaying on Android - https://github.com/nostra13/Android-Universal-Image-Loader - 2011 - - - org.sonatype.oss - oss-parent - 7 - - - - - Apache License Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - scm:git:git://github.com/nostra13/Android-Universal-Image-Loader.git - scm:git:git@github.com:nostra13/Android-Universal-Image-Loader.git - https://github.com/nostra13/Android-Universal-Image-Loader - HEAD - - - - - nostra13 - Sergey Tarasevich - 3 - - developer - - - - - - GitHub Issues - https://github.com/nostra13/Android-Universal-Image-Loader/issues - - - - library - sample - - - - UTF-8 - UTF-8 - 1.6 - 4.1.1.4 - 16 - r7 - - - - - - com.google.android - android - ${android.version} - provided - - - com.google.android - support-v4 - ${android-support.version} - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.0 - - ${java.version} - ${java.version} - - - - org.apache.maven.plugins - maven-release-plugin - 2.4 - - v@{project.version} - - - - com.jayway.maven.plugins.android.generation2 - android-maven-plugin - 3.6.0 - - - ${android.platform} - - true - - true - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.9 - - - com.google.android:android - - bin - - com.android.ide.eclipse.adt.ANDROID_FRAMEWORK - - - com.android.ide.eclipse.adt.AndroidNature - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - com.android.ide.eclipse.adt.PreCompilerBuilder - com.android.ide.eclipse.adt.ApkBuilder - - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9 - - - attach-javadocs - - jar - - - - - - - src - - - \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100644 index 000000000..7339f6c88 --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,44 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion "28.0.3" + + defaultConfig { + applicationId "com.nostra13.universalimageloader" + minSdkVersion 16 + targetSdkVersion 28 + versionCode 40 + versionName "1.9.5" + } + + useLibrary 'org.apache.http.legacy' + lintOptions { + abortOnError false + } +} + +dependencies { + implementation project(':library') + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.squareup.okhttp:okhttp:2.4.0' +} + + +File propFile = file('signing.properties'); +if (propFile.exists()) { + def Properties props = new Properties() + props.load(new FileInputStream(propFile)) + + if (props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') && + props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) { + android.signingConfigs.release.storeFile = file(props['STORE_FILE']) + android.signingConfigs.release.storePassword = props['STORE_PASSWORD'] + android.signingConfigs.release.keyAlias = props['KEY_ALIAS'] + android.signingConfigs.release.keyPassword = props['KEY_PASSWORD'] + } else { + android.buildTypes.release.signingConfig = null + } +} else { + android.buildTypes.release.signingConfig = null +} diff --git a/sample/gradle.properties b/sample/gradle.properties new file mode 100644 index 000000000..71329fc0c --- /dev/null +++ b/sample/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=Universal Image Loader Sample +POM_ARTIFACT_ID=universal-image-loader-sample +POM_PACKAGING=apk diff --git a/sample/libs/android-support-v4.jar b/sample/libs/android-support-v4.jar deleted file mode 100644 index 6080877d4..000000000 Binary files a/sample/libs/android-support-v4.jar and /dev/null differ diff --git a/sample/libs/universal-image-loader-1.8.5-with-sources.jar b/sample/libs/universal-image-loader-1.8.5-with-sources.jar deleted file mode 100644 index 080318eb8..000000000 Binary files a/sample/libs/universal-image-loader-1.8.5-with-sources.jar and /dev/null differ diff --git a/sample/pom.xml b/sample/pom.xml deleted file mode 100644 index 79c5466f4..000000000 --- a/sample/pom.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - 4.0.0 - com.nostra13.universalimageloader - universal-image-loader-sample - apk - Universal Image Loader Sample - - - com.nostra13.universalimageloader - parent - 1.8.6-SNAPSHOT - - - - - com.google.android - android - ${android.version} - - - com.google.android - support-v4 - ${android-support.version} - - - ${project.groupId} - universal-image-loader - jar - ${project.version} - - - - - src - - - com.jayway.maven.plugins.android.generation2 - android-maven-plugin - - - ${android.platform} - - true - - - - org.apache.maven.plugins - maven-eclipse-plugin - - - org.apache.maven.plugins - maven-source-plugin - - - org.apache.maven.plugins - maven-javadoc-plugin - - - - - \ No newline at end of file diff --git a/sample/project.properties b/sample/project.properties index 895c9ce2e..bbe203c84 100644 --- a/sample/project.properties +++ b/sample/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-16 +target=android-21 diff --git a/sample/res/drawable-hdpi/ic_launcher.png b/sample/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index f69abb78f..000000000 Binary files a/sample/res/drawable-hdpi/ic_launcher.png and /dev/null differ diff --git a/sample/res/drawable-ldpi/ic_empty.png b/sample/res/drawable-ldpi/ic_empty.png deleted file mode 100644 index 01f614f3d..000000000 Binary files a/sample/res/drawable-ldpi/ic_empty.png and /dev/null differ diff --git a/sample/res/drawable-ldpi/ic_error.png b/sample/res/drawable-ldpi/ic_error.png deleted file mode 100644 index 4376473bb..000000000 Binary files a/sample/res/drawable-ldpi/ic_error.png and /dev/null differ diff --git a/sample/res/drawable-ldpi/ic_launcher.png b/sample/res/drawable-ldpi/ic_launcher.png deleted file mode 100644 index 3381f3af4..000000000 Binary files a/sample/res/drawable-ldpi/ic_launcher.png and /dev/null differ diff --git a/sample/res/drawable-ldpi/ic_stub.png b/sample/res/drawable-ldpi/ic_stub.png deleted file mode 100644 index 65ec4074b..000000000 Binary files a/sample/res/drawable-ldpi/ic_stub.png and /dev/null differ diff --git a/sample/res/drawable-mdpi/ic_launcher.png b/sample/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index f75bd859f..000000000 Binary files a/sample/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/sample/res/drawable-xhdpi/ic_launcher.png b/sample/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 9eb0e0a43..000000000 Binary files a/sample/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/sample/res/layout/ac_home.xml b/sample/res/layout/ac_home.xml deleted file mode 100644 index 4333cd00f..000000000 --- a/sample/res/layout/ac_home.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - -