Replace UniversalImageLoader submodule by checked in code

This commit is contained in:
Daniel Martí 2015-03-31 20:16:11 +02:00
parent 6f91fb41f8
commit ad1819c10e
75 changed files with 11829 additions and 5 deletions

4
.gitmodules vendored
View File

@ -1,7 +1,3 @@
[submodule "extern/Universal-Image-Loader"]
path = extern/UniversalImageLoader
url = https://github.com/nostra13/Android-Universal-Image-Loader
ignore = dirty
[submodule "extern/MemorizingTrustManager"]
path = extern/MemorizingTrustManager
url = https://github.com/ge0rg/MemorizingTrustManager.git

@ -1 +0,0 @@
Subproject commit 7dd735871fec70d4a93837a26503470804b7d7e4

201
extern/UniversalImageLoader/LICENSE vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nostra13.universalimageloader"
android:versionCode="38"
android:versionName="1.9.3" >
<uses-sdk
android:minSdkVersion="5"
android:targetSdkVersion="19" />
</manifest>

View File

@ -0,0 +1,12 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "ant.properties", and override values to adapt the script to your
# project structure.
# Project target.
target=android-19
android.library=true

View File

@ -0,0 +1,87 @@
/*******************************************************************************
* 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 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.0.0
* @deprecated Use {@link DiskCache DiskCache} instead
*/
@Deprecated
public interface DiscCacheAware {
/**
* 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 <b>null</b> if image wasn't cached
*/
File get(String imageUri);
/**
* Saves image stream in disk cache.
*
* @param imageUri Original image URI
* @param imageStream Input stream of image
* @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 <b>true</b> - if image was saved successfully; <b>false</b> - if image wasn't saved in disk cache.
* @throws 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 <b>true</b> - if bitmap was saved successfully; <b>false</b> - 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 <b>true</b> - if image file is deleted successfully; <b>false</b> - 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();
}

View File

@ -0,0 +1,25 @@
/*******************************************************************************
* 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;
/**
* Interface for disk cache
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.9.2
*/
public interface DiskCache extends DiscCacheAware {
}

View File

@ -0,0 +1,189 @@
/*******************************************************************************
* 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 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 BaseDiscCache 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 BaseDiscCache(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 BaseDiscCache(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 BaseDiscCache(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 {
IoUtils.closeSilently(imageStream);
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;
}
}

View File

@ -0,0 +1,127 @@
/*******************************************************************************
* 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 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;
/**
* Cache which deletes files which were loaded more than defined time. Cache size is unlimited.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.3.1
*/
public class LimitedAgeDiscCache extends BaseDiscCache {
private final long maxFileAge;
private final Map<File, Long> loadingDates = Collections.synchronizedMap(new HashMap<File, Long>());
/**
* @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 LimitedAgeDiscCache(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 LimitedAgeDiscCache(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, File reserveCacheDir, FileNameGenerator fileNameGenerator, long maxAge) {
super(cacheDir, reserveCacheDir, fileNameGenerator);
this.maxFileAge = maxAge * 1000; // to milliseconds
}
@Override
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) {
cached = false;
loadingDate = file.lastModified();
} else {
cached = true;
}
if (System.currentTimeMillis() - loadingDate > maxFileAge) {
file.delete();
loadingDates.remove(file);
} else if (!cached) {
loadingDates.put(file, loadingDate);
}
}
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);
}
}

View File

@ -0,0 +1,52 @@
/*******************************************************************************
* 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.naming.FileNameGenerator;
import java.io.File;
/**
* Default implementation of {@linkplain com.nostra13.universalimageloader.cache.disc.DiskCache disk cache}.
* Cache size is unlimited.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
public class UnlimitedDiscCache extends BaseDiscCache {
/** @param cacheDir Directory for file caching */
public UnlimitedDiscCache(File cacheDir) {
super(cacheDir);
}
/**
* @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, File reserveCacheDir) {
super(cacheDir, reserveCacheDir);
}
/**
* @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 UnlimitedDiscCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
super(cacheDir, reserveCacheDir, fileNameGenerator);
}
}

View File

@ -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 <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences,
* accessible as streams or files. Each value must be between {@code 0} and
* {@code Integer.MAX_VALUE} bytes in length.
*
* <p>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.
*
* <p>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.
*
* <p>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.
* <ul>
* <li>When an entry is being <strong>created</strong> it is necessary to
* supply a full set of values; the empty value should be used as a
* placeholder if necessary.
* <li>When an entry is being <strong>edited</strong>, it is not necessary
* to supply data for every value; values default to their previous
* value.
* </ul>
* 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.
*
* <p>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.
*
* <p>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<String, Entry> lruEntries =
new LinkedHashMap<String, Entry>(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<Runnable>());
private final Callable<Void> cleanupCallable = new Callable<Void>() {
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<Entry> 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<Entry>(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<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
private void trimToFileCount() throws IOException {
while (fileCount > maxFileCount) {
Map.Entry<String, 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");
}
}
}

View File

@ -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 LruDiscCache 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
* <strong>[a-z0-9_-]{1,64}</strong>
* @param cacheMaxSize Max cache size in bytes. <b>0</b> means cache size is unlimited.
* @throws IOException if cache can't be initialized (e.g. "No space left on device")
*/
public LruDiscCache(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
* <strong>[a-z0-9_-]{1,64}</strong>
* @param cacheMaxSize Max cache size in bytes. <b>0</b> means cache size is unlimited.
* @param cacheMaxFileCount Max file count in cache. <b>0</b> means file count is unlimited.
* @throws IOException if cache can't be initialized (e.g. "No space left on device")
*/
public LruDiscCache(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;
}
}

View File

@ -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.
*
* <p>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}.
*
* <p>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.
*
* <p>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;
}
}

View File

@ -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) {
}
}
}
}

View File

@ -0,0 +1,28 @@
/*******************************************************************************
* 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.naming;
/**
* Generates names for files at disk cache
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.3.1
*/
public interface FileNameGenerator {
/** Generates unique file name for image defined by URI */
String generate(String imageUri);
}

View File

@ -0,0 +1,29 @@
/*******************************************************************************
* 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.naming;
/**
* Names image file as image URI {@linkplain String#hashCode() hashcode}
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.3.1
*/
public class HashCodeFileNameGenerator implements FileNameGenerator {
@Override
public String generate(String imageUri) {
return String.valueOf(imageUri.hashCode());
}
}

View File

@ -0,0 +1,53 @@
/*******************************************************************************
* 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.naming;
import com.nostra13.universalimageloader.utils.L;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Names image file as MD5 hash of image URI
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.4.0
*/
public class Md5FileNameGenerator implements FileNameGenerator {
private static final String HASH_ALGORITHM = "MD5";
private static final int RADIX = 10 + 26; // 10 digits + 26 letters
@Override
public String generate(String imageUri) {
byte[] md5 = getMD5(imageUri.getBytes());
BigInteger bi = new BigInteger(md5).abs();
return bi.toString(RADIX);
}
private byte[] getMD5(byte[] data) {
byte[] hash = null;
try {
MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
digest.update(data);
hash = digest.digest();
} catch (NoSuchAlgorithmException e) {
L.e(e);
}
return hash;
}
}

View File

@ -0,0 +1,71 @@
/*******************************************************************************
* 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.memory;
import android.graphics.Bitmap;
import java.lang.ref.Reference;
import java.util.*;
/**
* Base memory cache. Implements common functionality for memory cache. Provides object references (
* {@linkplain Reference not strong}) storing.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
public abstract class BaseMemoryCache implements MemoryCache {
/** Stores not strong references to objects */
private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(new HashMap<String, Reference<Bitmap>>());
@Override
public Bitmap get(String key) {
Bitmap result = null;
Reference<Bitmap> reference = softMap.get(key);
if (reference != null) {
result = reference.get();
}
return result;
}
@Override
public boolean put(String key, Bitmap value) {
softMap.put(key, createReference(value));
return true;
}
@Override
public Bitmap remove(String key) {
Reference<Bitmap> bmpRef = softMap.remove(key);
return bmpRef == null ? null : bmpRef.get();
}
@Override
public Collection<String> keys() {
synchronized (softMap) {
return new HashSet<String>(softMap.keySet());
}
}
@Override
public void clear() {
softMap.clear();
}
/** Creates {@linkplain Reference not strong} reference of value */
protected abstract Reference<Bitmap> createReference(Bitmap value);
}

View File

@ -0,0 +1,112 @@
/*******************************************************************************
* 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.memory;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.utils.L;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Limited cache. Provides object storing. Size of all stored bitmaps will not to exceed size limit (
* {@link #getSizeLimit()}).<br />
* <br />
* <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of
* Bitmaps (depends on cache size), weak references - for all other cached Bitmaps.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @see BaseMemoryCache
* @since 1.0.0
*/
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;
private final int sizeLimit;
private final AtomicInteger cacheSize;
/**
* Contains strong references to stored objects. Each next object is added last. If hard cache size will exceed
* 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<Bitmap> hardCache = Collections.synchronizedList(new LinkedList<Bitmap>());
/** @param sizeLimit Maximum size for cache (in bytes) */
public LimitedMemoryCache(int sizeLimit) {
this.sizeLimit = sizeLimit;
cacheSize = new AtomicInteger();
if (sizeLimit > MAX_NORMAL_CACHE_SIZE) {
L.w("You set too large memory cache size (more than %1$d Mb)", MAX_NORMAL_CACHE_SIZE_IN_MB);
}
}
@Override
public boolean put(String key, Bitmap value) {
boolean putSuccessfully = false;
// Try to add value to hard cache
int valueSize = getSize(value);
int sizeLimit = getSizeLimit();
int curCacheSize = cacheSize.get();
if (valueSize < sizeLimit) {
while (curCacheSize + valueSize > sizeLimit) {
Bitmap removedValue = removeNext();
if (hardCache.remove(removedValue)) {
curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
}
}
hardCache.add(value);
cacheSize.addAndGet(valueSize);
putSuccessfully = true;
}
// Add value to soft cache
super.put(key, value);
return putSuccessfully;
}
@Override
public Bitmap remove(String key) {
Bitmap value = super.get(key);
if (value != null) {
if (hardCache.remove(value)) {
cacheSize.addAndGet(-getSize(value));
}
}
return super.remove(key);
}
@Override
public void clear() {
hardCache.clear();
cacheSize.set(0);
super.clear();
}
protected int getSizeLimit() {
return sizeLimit;
}
protected abstract int getSize(Bitmap value);
protected abstract Bitmap removeNext();
}

View File

@ -0,0 +1,27 @@
/*******************************************************************************
* 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.memory;
import android.graphics.Bitmap;
/**
* Interface for memory cache
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.9.2
*/
public interface MemoryCache extends MemoryCacheAware<String, Bitmap> {
}

View File

@ -0,0 +1,49 @@
/*******************************************************************************
* 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.memory;
import java.util.Collection;
/**
* Interface for memory cache
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
* @deprecated Use {@link com.nostra13.universalimageloader.cache.memory.MemoryCache MemoryCache}
* instead
*/
@Deprecated
public interface MemoryCacheAware<K, V> {
/**
* Puts value into cache by key
*
* @return <b>true</b> - if value was put into cache successfully, <b>false</b> - if value was <b>not</b> put into
* cache
*/
boolean put(K key, V value);
/** Returns value by key. If there is no value for key then null will be returned. */
V get(K key);
/** Removes item by key */
V remove(K key);
/** Returns all keys of cache */
Collection<K> keys();
/** Remove all items from cache */
void clear();
}

View File

@ -0,0 +1,84 @@
/*******************************************************************************
* 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.memory.impl;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* 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 cache clearing is processed by FIFO principle.<br />
* <br />
* <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of
* Bitmaps (depends on cache size), weak references - for all other cached Bitmaps.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
public class FIFOLimitedMemoryCache extends LimitedMemoryCache {
private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>());
public FIFOLimitedMemoryCache(int sizeLimit) {
super(sizeLimit);
}
@Override
public boolean put(String key, Bitmap value) {
if (super.put(key, value)) {
queue.add(value);
return true;
} else {
return false;
}
}
@Override
public Bitmap remove(String key) {
Bitmap value = super.get(key);
if (value != null) {
queue.remove(value);
}
return super.remove(key);
}
@Override
public void clear() {
queue.clear();
super.clear();
}
@Override
protected int getSize(Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
protected Bitmap removeNext() {
return queue.remove(0);
}
@Override
protected Reference<Bitmap> createReference(Bitmap value) {
return new WeakReference<Bitmap>(value);
}
}

View File

@ -0,0 +1,81 @@
/*******************************************************************************
* 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.memory.impl;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.MemoryCache;
import java.util.Collection;
import java.util.Comparator;
/**
* 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.<br />
* <b>NOTE:</b> Used for internal needs. Normally you don't need to use this class.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
public class FuzzyKeyMemoryCache implements MemoryCache {
private final MemoryCache cache;
private final Comparator<String> keyComparator;
public FuzzyKeyMemoryCache(MemoryCache cache, Comparator<String> keyComparator) {
this.cache = cache;
this.keyComparator = keyComparator;
}
@Override
public boolean put(String key, Bitmap value) {
// Search equal key and remove this entry
synchronized (cache) {
String keyToRemove = null;
for (String cacheKey : cache.keys()) {
if (keyComparator.compare(key, cacheKey) == 0) {
keyToRemove = cacheKey;
break;
}
}
if (keyToRemove != null) {
cache.remove(keyToRemove);
}
}
return cache.put(key, value);
}
@Override
public Bitmap get(String key) {
return cache.get(key);
}
@Override
public Bitmap remove(String key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public Collection<String> keys() {
return cache.keys();
}
}

View File

@ -0,0 +1,103 @@
/*******************************************************************************
* 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.memory.impl;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* 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 least recently used bitmap is deleted from cache.<br />
* <br />
* <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of
* Bitmaps (depends on cache size), weak references - for all other cached Bitmaps.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.3.0
*/
public class LRULimitedMemoryCache extends LimitedMemoryCache {
private static final int INITIAL_CAPACITY = 10;
private static final float LOAD_FACTOR = 1.1f;
/** Cache providing Least-Recently-Used logic */
private final Map<String, Bitmap> lruCache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(INITIAL_CAPACITY, LOAD_FACTOR, true));
/** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
public LRULimitedMemoryCache(int maxSize) {
super(maxSize);
}
@Override
public boolean put(String key, Bitmap value) {
if (super.put(key, value)) {
lruCache.put(key, value);
return true;
} else {
return false;
}
}
@Override
public Bitmap get(String key) {
lruCache.get(key); // call "get" for LRU logic
return super.get(key);
}
@Override
public Bitmap remove(String key) {
lruCache.remove(key);
return super.remove(key);
}
@Override
public void clear() {
lruCache.clear();
super.clear();
}
@Override
protected int getSize(Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
protected Bitmap removeNext() {
Bitmap mostLongUsedValue = null;
synchronized (lruCache) {
Iterator<Entry<String, Bitmap>> it = lruCache.entrySet().iterator();
if (it.hasNext()) {
Entry<String, Bitmap> entry = it.next();
mostLongUsedValue = entry.getValue();
it.remove();
}
}
return mostLongUsedValue;
}
@Override
protected Reference<Bitmap> createReference(Bitmap value) {
return new WeakReference<Bitmap>(value);
}
}

View File

@ -0,0 +1,109 @@
/*******************************************************************************
* 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.memory.impl;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* 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 has the largest size is deleted from
* cache.<br />
* <br />
* <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of
* Bitmaps (depends on cache size), weak references - for all other cached Bitmaps.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
public class LargestLimitedMemoryCache extends LimitedMemoryCache {
/**
* 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<Bitmap, Integer> valueSizes = Collections.synchronizedMap(new HashMap<Bitmap, Integer>());
public LargestLimitedMemoryCache(int sizeLimit) {
super(sizeLimit);
}
@Override
public boolean put(String key, Bitmap value) {
if (super.put(key, value)) {
valueSizes.put(value, getSize(value));
return true;
} else {
return false;
}
}
@Override
public Bitmap remove(String key) {
Bitmap value = super.get(key);
if (value != null) {
valueSizes.remove(value);
}
return super.remove(key);
}
@Override
public void clear() {
valueSizes.clear();
super.clear();
}
@Override
protected int getSize(Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
protected Bitmap removeNext() {
Integer maxSize = null;
Bitmap largestValue = null;
Set<Entry<Bitmap, Integer>> entries = valueSizes.entrySet();
synchronized (valueSizes) {
for (Entry<Bitmap, Integer> entry : entries) {
if (largestValue == null) {
largestValue = entry.getKey();
maxSize = entry.getValue();
} else {
Integer size = entry.getValue();
if (size > maxSize) {
maxSize = size;
largestValue = entry.getKey();
}
}
}
}
valueSizes.remove(largestValue);
return largestValue;
}
@Override
protected Reference<Bitmap> createReference(Bitmap value) {
return new WeakReference<Bitmap>(value);
}
}

View File

@ -0,0 +1,88 @@
/*******************************************************************************
* 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.memory.impl;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.MemoryCache;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* 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 MemoryCache
* @since 1.3.1
*/
public class LimitedAgeMemoryCache implements MemoryCache {
private final MemoryCache cache;
private final long maxAge;
private final Map<String, Long> loadingDates = Collections.synchronizedMap(new HashMap<String, Long>());
/**
* @param cache Wrapped memory cache
* @param maxAge Max object age <b>(in seconds)</b>. If object age will exceed this value then it'll be removed from
* cache on next treatment (and therefore be reloaded).
*/
public LimitedAgeMemoryCache(MemoryCache cache, long maxAge) {
this.cache = cache;
this.maxAge = maxAge * 1000; // to milliseconds
}
@Override
public boolean put(String key, Bitmap value) {
boolean putSuccesfully = cache.put(key, value);
if (putSuccesfully) {
loadingDates.put(key, System.currentTimeMillis());
}
return putSuccesfully;
}
@Override
public Bitmap get(String key) {
Long loadingDate = loadingDates.get(key);
if (loadingDate != null && System.currentTimeMillis() - loadingDate > maxAge) {
cache.remove(key);
loadingDates.remove(key);
}
return cache.get(key);
}
@Override
public Bitmap remove(String key) {
loadingDates.remove(key);
return cache.remove(key);
}
@Override
public Collection<String> keys() {
return cache.keys();
}
@Override
public void clear() {
cache.clear();
loadingDates.clear();
}
}

View File

@ -0,0 +1,144 @@
package com.nostra13.universalimageloader.cache.memory.impl;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.MemoryCache;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to
* the head of a queue. When a Bitmap is added to a full cache, the Bitmap at the end of that queue is evicted and may
* become eligible for garbage collection.<br />
* <br />
* <b>NOTE:</b> This cache uses only strong references for stored Bitmaps.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.8.1
*/
public class LruMemoryCache implements MemoryCache {
private final LinkedHashMap<String, Bitmap> map;
private final int maxSize;
/** Size of this cache in bytes */
private int size;
/** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
public LruMemoryCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
}
/**
* Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head
* of the queue. This returns null if a Bitmap is not cached.
*/
@Override
public final Bitmap get(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
synchronized (this) {
return map.get(key);
}
}
/** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue. */
@Override
public final boolean put(String key, Bitmap value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
synchronized (this) {
size += sizeOf(key, value);
Bitmap previous = map.put(key, value);
if (previous != null) {
size -= sizeOf(key, previous);
}
}
trimToSize(maxSize);
return true;
}
/**
* Remove the eldest entries until the total of remaining entries is at or below the requested size.
*
* @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
*/
private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize || map.isEmpty()) {
break;
}
Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= sizeOf(key, value);
}
}
}
/** Removes the entry for {@code key} if it exists. */
@Override
public final Bitmap remove(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
synchronized (this) {
Bitmap previous = map.remove(key);
if (previous != null) {
size -= sizeOf(key, previous);
}
return previous;
}
}
@Override
public Collection<String> keys() {
synchronized (this) {
return new HashSet<String>(map.keySet());
}
}
@Override
public void clear() {
trimToSize(-1); // -1 will evict 0-sized elements
}
/**
* Returns the size {@code Bitmap} in bytes.
* <p/>
* An entry's size must not change while it is in the cache.
*/
private int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
public synchronized final String toString() {
return String.format("LruCache[maxSize=%d]", maxSize);
}
}

View File

@ -0,0 +1,122 @@
/*******************************************************************************
* 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.memory.impl;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* 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
* cache.<br />
* <br />
* <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of
* Bitmaps (depends on cache size), weak references - for all other cached Bitmaps.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
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
* {@link #softMap} and can be collected by GC at any time)
*/
private final Map<Bitmap, Integer> usingCounts = Collections.synchronizedMap(new HashMap<Bitmap, Integer>());
public UsingFreqLimitedMemoryCache(int sizeLimit) {
super(sizeLimit);
}
@Override
public boolean put(String key, Bitmap value) {
if (super.put(key, value)) {
usingCounts.put(value, 0);
return true;
} else {
return false;
}
}
@Override
public Bitmap get(String key) {
Bitmap value = super.get(key);
// Increment usage count for value if value is contained in hardCahe
if (value != null) {
Integer usageCount = usingCounts.get(value);
if (usageCount != null) {
usingCounts.put(value, usageCount + 1);
}
}
return value;
}
@Override
public Bitmap remove(String key) {
Bitmap value = super.get(key);
if (value != null) {
usingCounts.remove(value);
}
return super.remove(key);
}
@Override
public void clear() {
usingCounts.clear();
super.clear();
}
@Override
protected int getSize(Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
protected Bitmap removeNext() {
Integer minUsageCount = null;
Bitmap leastUsedValue = null;
Set<Entry<Bitmap, Integer>> entries = usingCounts.entrySet();
synchronized (usingCounts) {
for (Entry<Bitmap, Integer> entry : entries) {
if (leastUsedValue == null) {
leastUsedValue = entry.getKey();
minUsageCount = entry.getValue();
} else {
Integer lastValueUsage = entry.getValue();
if (lastValueUsage < minUsageCount) {
minUsageCount = lastValueUsage;
leastUsedValue = entry.getKey();
}
}
}
}
usingCounts.remove(leastUsedValue);
return leastUsedValue;
}
@Override
protected Reference<Bitmap> createReference(Bitmap value) {
return new WeakReference<Bitmap>(value);
}
}

View File

@ -0,0 +1,37 @@
/*******************************************************************************
* 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.memory.impl;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.memory.BaseMemoryCache;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
/**
* Memory cache with {@linkplain WeakReference weak references} to {@linkplain android.graphics.Bitmap bitmaps}<br />
* <br />
* <b>NOTE:</b> This cache uses only weak references for stored Bitmaps.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.5.3
*/
public class WeakMemoryCache extends BaseMemoryCache {
@Override
protected Reference<Bitmap> createReference(Bitmap value) {
return new WeakReference<Bitmap>(value);
}
}

View File

@ -0,0 +1,160 @@
/*******************************************************************************
* 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.content.Context;
import com.nostra13.universalimageloader.cache.disc.DiskCache;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.cache.disc.naming.HashCodeFileNameGenerator;
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;
import com.nostra13.universalimageloader.core.decode.BaseImageDecoder;
import com.nostra13.universalimageloader.core.decode.ImageDecoder;
import com.nostra13.universalimageloader.core.display.BitmapDisplayer;
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.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;
/**
* Factory for providing of default options for {@linkplain ImageLoaderConfiguration configuration}
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.5.6
*/
public class DefaultConfigurationFactory {
/** Creates default implementation of task executor */
public static Executor createExecutor(int threadPoolSize, int threadPriority,
QueueProcessingType tasksProcessingType) {
boolean lifo = tasksProcessingType == QueueProcessingType.LIFO;
BlockingQueue<Runnable> taskQueue =
lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();
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 */
public static FileNameGenerator createFileNameGenerator() {
return new HashCodeFileNameGenerator();
}
/**
* 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);
try {
return new LruDiscCache(individualCacheDir, reserveCacheDir, diskCacheFileNameGenerator, diskCacheSize,
diskCacheFileCount);
} catch (IOException e) {
L.e(e);
// continue and create unlimited cache
}
}
File cacheDir = StorageUtils.getCacheDirectory(context);
return new UnlimitedDiscCache(cacheDir, reserveCacheDir, diskCacheFileNameGenerator);
}
/** 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 cacheDir;
}
/**
* Creates default implementation of {@link MemoryCache} - {@link LruMemoryCache}<br />
* Default cache size = 1/8 of available app memory.
*/
public static MemoryCache createMemoryCache(int memoryCacheSize) {
if (memoryCacheSize == 0) {
memoryCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8);
}
return new LruMemoryCache(memoryCacheSize);
}
/** Creates default implementation of {@link ImageDownloader} - {@link BaseImageDownloader} */
public static ImageDownloader createImageDownloader(Context context) {
return new BaseImageDownloader(context);
}
/** Creates default implementation of {@link ImageDecoder} - {@link BaseImageDecoder} */
public static ImageDecoder createImageDecoder(boolean loggingEnabled) {
return new BaseImageDecoder(loggingEnabled);
}
/** Creates default implementation of {@link BitmapDisplayer} - {@link SimpleBitmapDisplayer} */
public static BitmapDisplayer createBitmapDisplayer() {
return new SimpleBitmapDisplayer();
}
/** Creates default implementation of {@linkplain ThreadFactory thread factory} for task executor */
private static ThreadFactory createThreadFactory(int threadPriority, String threadNamePrefix) {
return new DefaultThreadFactory(threadPriority, threadNamePrefix);
}
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
private final int threadPriority;
DefaultThreadFactory(int threadPriority, String threadNamePrefix) {
this.threadPriority = threadPriority;
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);
t.setPriority(threadPriority);
return t;
}
}
}

View File

@ -0,0 +1,81 @@
/*******************************************************************************
* 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 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 com.nostra13.universalimageloader.core.imageaware.ImageAware}. Must be called on UI thread.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @see ImageLoadingListener
* @see BitmapDisplayer
* @since 1.3.1
*/
final class DisplayBitmapTask implements Runnable {
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 ImageAware imageAware;
private final String memoryCacheKey;
private final BitmapDisplayer displayer;
private final ImageLoadingListener listener;
private final ImageLoaderEngine engine;
private final LoadedFrom loadedFrom;
public DisplayBitmapTask(Bitmap bitmap, ImageLoadingInfo imageLoadingInfo, ImageLoaderEngine engine,
LoadedFrom loadedFrom) {
this.bitmap = bitmap;
imageUri = imageLoadingInfo.uri;
imageAware = imageLoadingInfo.imageAware;
memoryCacheKey = imageLoadingInfo.memoryCacheKey;
displayer = imageLoadingInfo.options.getDisplayer();
listener = imageLoadingInfo.listener;
this.engine = engine;
this.loadedFrom = loadedFrom;
}
@Override
public void run() {
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 {
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 ImageAware is actual */
private boolean isViewWasReused() {
String currentCacheKey = engine.getLoadingUriForView(imageAware);
return !memoryCacheKey.equals(currentCacheKey);
}
}

View File

@ -0,0 +1,509 @@
/*******************************************************************************
* 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.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory.Options;
import android.graphics.drawable.Drawable;
import android.os.Handler;
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;
import com.nostra13.universalimageloader.core.download.ImageDownloader;
import com.nostra13.universalimageloader.core.process.BitmapProcessor;
/**
* Contains options for image display. Defines:
* <ul>
* <li>whether stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
* image aware view} during image loading</li>
* <li>whether stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
* image aware view} if empty URI is passed</li>
* <li>whether stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
* image aware view} if image loading fails</li>
* <li>whether {@link com.nostra13.universalimageloader.core.imageaware.ImageAware image aware view} should be reset
* before image loading start</li>
* <li>whether loaded image will be cached in memory</li>
* <li>whether loaded image will be cached on disk</li>
* <li>image scale type</li>
* <li>decoding options (including bitmap decoding configuration)</li>
* <li>delay before loading of image</li>
* <li>whether consider EXIF parameters of image</li>
* <li>auxiliary object which will be passed to {@link ImageDownloader#getStream(String, Object) ImageDownloader}</li>
* <li>pre-processor for image Bitmap (before caching in memory)</li>
* <li>post-processor for image Bitmap (after caching in memory, before displaying)</li>
* <li>how decoded {@link Bitmap} will be displayed</li>
* </ul>
* <p/>
* You can create instance:
* <ul>
* <li>with {@link Builder}:<br />
* <b>i.e.</b> :
* <code>new {@link DisplayImageOptions}.{@link Builder#Builder() Builder()}.{@link Builder#cacheInMemory() cacheInMemory()}.
* {@link Builder#showImageOnLoading(int) showImageOnLoading()}.{@link Builder#build() build()}</code><br />
* </li>
* <li>or by static method: {@link #createSimple()}</li> <br />
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
public final class DisplayImageOptions {
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 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) {
imageResOnLoading = builder.imageResOnLoading;
imageResForEmptyUri = builder.imageResForEmptyUri;
imageResOnFail = builder.imageResOnFail;
imageOnLoading = builder.imageOnLoading;
imageForEmptyUri = builder.imageForEmptyUri;
imageOnFail = builder.imageOnFail;
resetViewBeforeLoading = builder.resetViewBeforeLoading;
cacheInMemory = builder.cacheInMemory;
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 shouldShowImageOnLoading() {
return imageOnLoading != null || imageResOnLoading != 0;
}
public boolean shouldShowImageForEmptyUri() {
return imageForEmptyUri != null || imageResForEmptyUri != 0;
}
public boolean shouldShowImageOnFail() {
return imageOnFail != null || imageResOnFail != 0;
}
public boolean shouldPreProcess() {
return preProcessor != null;
}
public boolean shouldPostProcess() {
return postProcessor != null;
}
public boolean shouldDelayBeforeLoading() {
return delayBeforeLoading > 0;
}
public Drawable getImageOnLoading(Resources res) {
return imageResOnLoading != 0 ? res.getDrawable(imageResOnLoading) : imageOnLoading;
}
public Drawable getImageForEmptyUri(Resources res) {
return imageResForEmptyUri != 0 ? res.getDrawable(imageResForEmptyUri) : imageForEmptyUri;
}
public Drawable getImageOnFail(Resources res) {
return imageResOnFail != 0 ? res.getDrawable(imageResOnFail) : imageOnFail;
}
public boolean isResetViewBeforeLoading() {
return resetViewBeforeLoading;
}
public boolean isCacheInMemory() {
return cacheInMemory;
}
public boolean isCacheOnDisk() {
return cacheOnDisk;
}
public ImageScaleType getImageScaleType() {
return imageScaleType;
}
public Options getDecodingOptions() {
return decodingOptions;
}
public int getDelayBeforeLoading() {
return delayBeforeLoading;
}
public boolean isConsiderExifParams() {
return considerExifParams;
}
public Object getExtraForDownloader() {
return extraForDownloader;
}
public BitmapProcessor getPreProcessor() {
return preProcessor;
}
public BitmapProcessor getPostProcessor() {
return postProcessor;
}
public BitmapDisplayer getDisplayer() {
return displayer;
}
public Handler getHandler() {
return handler;
}
boolean isSyncLoading() {
return isSyncLoading;
}
/**
* Builder for {@link DisplayImageOptions}
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
*/
public static class Builder {
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 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;
}
/**
* Incoming image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
* image aware view} during image loading
*
* @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 showImageOnLoading(Drawable drawable) {
imageOnLoading = drawable;
return this;
}
/**
* 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 <b>ImageLoader.displayImage(...)</b> method.
*
* @param imageRes Image resource
*/
public Builder showImageForEmptyUri(int 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 <b>ImageLoader.displayImage(...)</b> 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 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) {
imageResOnFail = imageRes;
return this;
}
/**
* 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 <b>null</b>) before image loading start
*
* @deprecated Use {@link #resetViewBeforeLoading(boolean) resetViewBeforeLoading(true)} instead
*/
public Builder resetViewBeforeLoading() {
resetViewBeforeLoading = true;
return this;
}
/**
* Sets whether {@link com.nostra13.universalimageloader.core.imageaware.ImageAware
* image aware view} will be reset (set <b>null</b>) before image loading start
*/
public Builder resetViewBeforeLoading(boolean resetViewBeforeLoading) {
this.resetViewBeforeLoading = resetViewBeforeLoading;
return this;
}
/**
* Loaded image will be cached in memory
*
* @deprecated Use {@link #cacheInMemory(boolean) cacheInMemory(true)} instead
*/
@Deprecated
public Builder cacheInMemory() {
cacheInMemory = true;
return this;
}
/** Sets whether loaded image will be cached in memory */
public Builder cacheInMemory(boolean cacheInMemory) {
this.cacheInMemory = cacheInMemory;
return this;
}
/**
* Loaded image will be cached on disk
*
* @deprecated Use {@link #cacheOnDisk(boolean) cacheOnDisk(true)} instead
*/
@Deprecated
public Builder cacheOnDisc() {
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 disk */
public Builder cacheOnDisk(boolean cacheOnDisk) {
this.cacheOnDisk = cacheOnDisk;
return this;
}
/**
* Sets {@linkplain ImageScaleType scale type} for decoding image. This parameter is used while define scale
* size for decoding image to Bitmap. Default value - {@link ImageScaleType#IN_SAMPLE_POWER_OF_2}
*/
public Builder imageScaleType(ImageScaleType imageScaleType) {
this.imageScaleType = imageScaleType;
return this;
}
/** Sets {@link Bitmap.Config bitmap config} for image decoding. Default value - {@link Bitmap.Config#ARGB_8888} */
public Builder bitmapConfig(Bitmap.Config bitmapConfig) {
if (bitmapConfig == null) throw new IllegalArgumentException("bitmapConfig can't be null");
decodingOptions.inPreferredConfig = bitmapConfig;
return this;
}
/**
* Sets options for image decoding.<br />
* <b>NOTE:</b> {@link Options#inSampleSize} of incoming options will <b>NOT</b> be considered. Library
* calculate the most appropriate sample size itself according yo {@link #imageScaleType(ImageScaleType)}
* options.<br />
* <b>NOTE:</b> This option overlaps {@link #bitmapConfig(android.graphics.Bitmap.Config) bitmapConfig()}
* option.
*/
public Builder decodingOptions(Options decodingOptions) {
if (decodingOptions == null) throw new IllegalArgumentException("decodingOptions can't be null");
this.decodingOptions = decodingOptions;
return this;
}
/** Sets delay time before starting loading task. Default - no delay. */
public Builder delayBeforeLoading(int delayInMillis) {
this.delayBeforeLoading = delayInMillis;
return this;
}
/** Sets auxiliary object which will be passed to {@link ImageDownloader#getStream(String, Object)} */
public Builder extraForDownloader(Object extra) {
this.extraForDownloader = 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.<br />
* Image will be pre-processed even if caching in memory is disabled.
*/
public Builder preProcessor(BitmapProcessor preProcessor) {
this.preProcessor = preProcessor;
return this;
}
/**
* 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) {
this.postProcessor = postProcessor;
return this;
}
/**
* Sets custom {@link BitmapDisplayer displayer} for image loading task. Default value -
* {@link DefaultConfigurationFactory#createBitmapDisplayer()}
*/
public Builder displayer(BitmapDisplayer displayer) {
if (displayer == null) throw new IllegalArgumentException("displayer can't be null");
this.displayer = 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.
*/
public Builder handler(Handler handler) {
this.handler = handler;
return this;
}
/** Sets all options equal to incoming options */
public Builder cloneFrom(DisplayImageOptions options) {
imageResOnLoading = options.imageResOnLoading;
imageResForEmptyUri = options.imageResForEmptyUri;
imageResOnFail = options.imageResOnFail;
imageOnLoading = options.imageOnLoading;
imageForEmptyUri = options.imageForEmptyUri;
imageOnFail = options.imageOnFail;
resetViewBeforeLoading = options.resetViewBeforeLoading;
cacheInMemory = options.cacheInMemory;
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;
}
/** Builds configured {@link DisplayImageOptions} object */
public DisplayImageOptions build() {
return new DisplayImageOptions(this);
}
}
/**
* Creates options appropriate for single displaying:
* <ul>
* <li>View will <b>not</b> be reset before loading</li>
* <li>Loaded image will <b>not</b> be cached in memory</li>
* <li>Loaded image will <b>not</b> be cached on disk</li>
* <li>{@link ImageScaleType#IN_SAMPLE_POWER_OF_2} decoding type will be used</li>
* <li>{@link Bitmap.Config#ARGB_8888} bitmap config will be used for image decoding</li>
* <li>{@link SimpleBitmapDisplayer} will be used for image displaying</li>
* </ul>
* <p/>
* These option are appropriate for simple single-use image (from drawables or from Internet) displaying.
*/
public static DisplayImageOptions createSimple() {
return new Builder().build();
}
}

View File

@ -0,0 +1,763 @@
/*******************************************************************************
* 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.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}<br />
* <b>NOTE:</b> {@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 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.<br />
* If configurations was set before ( {@link #isInited()} == true) then this method does nothing.<br />
* To force initialization with new configuration you should {@linkplain #destroy() destroy ImageLoader} at first.
*
* @param configuration {@linkplain ImageLoaderConfiguration ImageLoader configuration}
* @throws IllegalArgumentException if <b>configuration</b> 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 <b>true</b> - if ImageLoader {@linkplain #init(ImageLoaderConfiguration) is initialized with
* configuration}; <b>false</b> - otherwise
*/
public boolean isInited() {
return configuration != null;
}
/**
* Adds display image task to execution pool. Image will be set to ImageAware when it's turn. <br/>
* Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration
* configuration} will be used.<br />
* <b>NOTE:</b> {@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 <b>imageAware</b> 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.<br />
* Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration
* configuration} will be used.<br />
* <b>NOTE:</b> {@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 <b>imageAware</b> 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.<br />
* <b>NOTE:</b> {@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 <b>null</b> - 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 <b>imageAware</b> 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.<br />
* <b>NOTE:</b> {@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 <b>null</b> - 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 <b>imageAware</b> 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.<br />
* <b>NOTE:</b> {@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 <b>null</b> - 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 <b>imageAware</b> is null
*/
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
checkConfiguration();
if (imageAware == null) {
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
if (listener == null) {
listener = emptyListener;
}
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;
}
ImageSize 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. <br/>
* Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration
* configuration} will be used.<br />
* <b>NOTE:</b> {@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 <b>imageView</b> 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.<br />
* <b>NOTE:</b> {@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 <b>null</b> - 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 <b>imageView</b> 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.<br />
* Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration
* configuration} will be used.<br />
* <b>NOTE:</b> {@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 <b>imageView</b> 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.<br />
* <b>NOTE:</b> {@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 <b>null</b> - 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 <b>imageView</b> 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.<br />
* <b>NOTE:</b> {@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 <b>null</b> - 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 <b>imageView</b> 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}.
* <br />
* <b>NOTE:</b> {@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}.
* <br />
* <b>NOTE:</b> {@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 <b>equal or larger</b> (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}.
* <br />
* <b>NOTE:</b> {@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 <b>null</b> - default display image options
* {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from
* configuration} will be used.<br />
* @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}.
* <br />
* <b>NOTE:</b> {@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 <b>equal or larger</b> (usually a bit
* larger) than incoming targetImageSize.
* @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
* decoding and displaying. If <b>null</b> - default display image options
* {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
* from configuration} will be used.<br />
* @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}.
* <br />
* <b>NOTE:</b> {@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 <b>equal or larger</b> (usually a bit
* larger) than incoming targetImageSize.
* @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
* decoding and displaying. If <b>null</b> - default display image options
* {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
* from configuration} will be used.<br />
* @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.<br />
* Default display image options
* {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from
* configuration} will be used.<br />
* <b>NOTE:</b> {@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 <b>null</b> 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.<br />
* <b>NOTE:</b> {@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 <b>null</b> - default display image options
* {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from
* configuration} will be used.
* @return Result image Bitmap. Can be <b>null</b> 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.<br />
* Default display image options
* {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from
* configuration} will be used.<br />
* <b>NOTE:</b> {@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 <b>equal or larger</b> (usually a bit
* larger) than incoming targetImageSize.
* @return Result image Bitmap. Can be <b>null</b> 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.<br />
* <b>NOTE:</b> {@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 <b>equal or larger</b> (usually a bit
* larger) than incoming targetImageSize.
* @param options {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions Options} for image
* decoding and scaling. If <b>null</b> - default display image options
* {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions)
* from configuration} will be used.
* @return Result image Bitmap. Can be <b>null</b> 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);
}
}
/**
* 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.<br />
* <br />
* 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 <b>true</b> - to deny engine to download images from the network; <b>false</b> -
* 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 <a
* href="http://code.google.com/p/android/issues/detail?id=6066">this known problem</a> or not.
*
* @param handleSlowNetwork pass <b>true</b> - to use {@link FlushedInputStream} for network downloads; <b>false</b>
* - 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}.
* <br />
* 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.<br />
* <b>NOTE:</b> This method doesn't shutdown
* {@linkplain com.nostra13.universalimageloader.core.ImageLoaderConfiguration.Builder#taskExecutor(java.util.concurrent.Executor)
* custom task executors} if you set them.<br />
* ImageLoader still can be used after calling this method.
*/
public void stop() {
engine.stop();
}
/**
* {@linkplain #stop() Stops ImageLoader} and clears current configuration. <br />
* You can {@linkplain #init(ImageLoaderConfiguration) init} ImageLoader with new configuration after calling this
* method.
*/
public void destroy() {
if (configuration != null) L.d(LOG_DESTROY);
stop();
configuration.diskCache.close();
engine = null;
configuration = null;
}
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;
}
}
}

View File

@ -0,0 +1,655 @@
/*******************************************************************************
* 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.content.Context;
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.MemoryCache;
import com.nostra13.universalimageloader.cache.memory.impl.FuzzyKeyMemoryCache;
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.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)
* @see ImageLoader
* @see MemoryCache
* @see DiskCache
* @see DisplayImageOptions
* @see ImageDownloader
* @see FileNameGenerator
* @since 1.0.0
*/
public final class ImageLoaderConfiguration {
final Resources resources;
final int maxImageWidthForMemoryCache;
final int maxImageHeightForMemoryCache;
final int maxImageWidthForDiskCache;
final int maxImageHeightForDiskCache;
final BitmapProcessor processorForDiskCache;
final Executor taskExecutor;
final Executor taskExecutorForCachedImages;
final boolean customExecutor;
final boolean customExecutorForCachedImages;
final int threadPoolSize;
final int threadPriority;
final QueueProcessingType tasksProcessingType;
final MemoryCache memoryCache;
final DiskCache diskCache;
final ImageDownloader downloader;
final ImageDecoder decoder;
final DisplayImageOptions defaultDisplayImageOptions;
final ImageDownloader networkDeniedDownloader;
final ImageDownloader slowNetworkDownloader;
private ImageLoaderConfiguration(final Builder builder) {
resources = builder.context.getResources();
maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache;
maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache;
maxImageWidthForDiskCache = builder.maxImageWidthForDiskCache;
maxImageHeightForDiskCache = builder.maxImageHeightForDiskCache;
processorForDiskCache = builder.processorForDiskCache;
taskExecutor = builder.taskExecutor;
taskExecutorForCachedImages = builder.taskExecutorForCachedImages;
threadPoolSize = builder.threadPoolSize;
threadPriority = builder.threadPriority;
tasksProcessingType = builder.tasksProcessingType;
diskCache = builder.diskCache;
memoryCache = builder.memoryCache;
defaultDisplayImageOptions = builder.defaultDisplayImageOptions;
downloader = builder.downloader;
decoder = builder.decoder;
customExecutor = builder.customExecutor;
customExecutorForCachedImages = builder.customExecutorForCachedImages;
networkDeniedDownloader = new NetworkDeniedImageDownloader(downloader);
slowNetworkDownloader = new SlowNetworkImageDownloader(downloader);
L.writeDebugLogs(builder.writeLogs);
}
/**
* Creates default configuration for {@link ImageLoader} <br />
* <b>Default values:</b>
* <ul>
* <li>maxImageWidthForMemoryCache = device's screen width</li>
* <li>maxImageHeightForMemoryCache = device's screen height</li>
* <li>maxImageWidthForDikcCache = unlimited</li>
* <li>maxImageHeightForDiskCache = unlimited</li>
* <li>threadPoolSize = {@link Builder#DEFAULT_THREAD_POOL_SIZE this}</li>
* <li>threadPriority = {@link Builder#DEFAULT_THREAD_PRIORITY this}</li>
* <li>allow to cache different sizes of image in memory</li>
* <li>memoryCache = {@link DefaultConfigurationFactory#createMemoryCache(int)}</li>
* <li>diskCache = {@link com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache}</li>
* <li>imageDownloader = {@link DefaultConfigurationFactory#createImageDownloader(Context)}</li>
* <li>imageDecoder = {@link DefaultConfigurationFactory#createImageDecoder(boolean)}</li>
* <li>diskCacheFileNameGenerator = {@link DefaultConfigurationFactory#createFileNameGenerator()}</li>
* <li>defaultDisplayImageOptions = {@link DisplayImageOptions#createSimple() Simple options}</li>
* <li>tasksProcessingOrder = {@link QueueProcessingType#FIFO}</li>
* <li>detailed logging disabled</li>
* </ul>
*/
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}
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
*/
public static class Builder {
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.";
/** {@value} */
public static final int DEFAULT_THREAD_POOL_SIZE = 3;
/** {@value} */
public static final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY - 1;
/** {@value} */
public static final QueueProcessingType DEFAULT_TASK_PROCESSING_TYPE = QueueProcessingType.FIFO;
private Context context;
private int maxImageWidthForMemoryCache = 0;
private int maxImageHeightForMemoryCache = 0;
private int maxImageWidthForDiskCache = 0;
private int maxImageHeightForDiskCache = 0;
private BitmapProcessor processorForDiskCache = null;
private Executor taskExecutor = null;
private Executor taskExecutorForCachedImages = null;
private boolean customExecutor = false;
private boolean customExecutorForCachedImages = false;
private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE;
private int threadPriority = DEFAULT_THREAD_PRIORITY;
private boolean denyCacheImageMultipleSizesInMemory = false;
private QueueProcessingType tasksProcessingType = DEFAULT_TASK_PROCESSING_TYPE;
private int memoryCacheSize = 0;
private long diskCacheSize = 0;
private int diskCacheFileCount = 0;
private MemoryCache memoryCache = null;
private DiskCache diskCache = null;
private FileNameGenerator diskCacheFileNameGenerator = null;
private ImageDownloader downloader = null;
private ImageDecoder decoder;
private DisplayImageOptions defaultDisplayImageOptions = null;
private boolean writeLogs = false;
public Builder(Context context) {
this.context = context.getApplicationContext();
}
/**
* 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}. <b>Default value - device's screen width</b>
* @param maxImageHeightForMemoryCache Maximum image height which will be used for memory saving during decoding
* an image to {@link android.graphics.Bitmap Bitmap}. <b>Default value</b> - device's screen height
*/
public Builder memoryCacheExtraOptions(int maxImageWidthForMemoryCache, int maxImageHeightForMemoryCache) {
this.maxImageWidthForMemoryCache = maxImageWidthForMemoryCache;
this.maxImageHeightForMemoryCache = maxImageHeightForMemoryCache;
return this;
}
/**
* @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.<br />
* <b>NOTE: Use this option only when you have appropriate needs. It can make ImageLoader slower.</b>
*
* @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 diskCacheExtraOptions(int maxImageWidthForDiskCache, int maxImageHeightForDiskCache,
BitmapProcessor processorForDiskCache) {
this.maxImageWidthForDiskCache = maxImageWidthForDiskCache;
this.maxImageHeightForDiskCache = maxImageHeightForDiskCache;
this.processorForDiskCache = processorForDiskCache;
return this;
}
/**
* Sets custom {@linkplain Executor executor} for tasks of loading and displaying images.<br />
* <br />
* <b>NOTE:</b> If you set custom executor then following configuration options will not be considered for this
* executor:
* <ul>
* <li>{@link #threadPoolSize(int)}</li>
* <li>{@link #threadPriority(int)}</li>
* <li>{@link #tasksProcessingOrder(QueueProcessingType)}</li>
* </ul>
*
* @see #taskExecutorForCachedImages(Executor)
*/
public Builder taskExecutor(Executor executor) {
if (threadPoolSize != DEFAULT_THREAD_POOL_SIZE || threadPriority != DEFAULT_THREAD_PRIORITY || tasksProcessingType != DEFAULT_TASK_PROCESSING_TYPE) {
L.w(WARNING_OVERLAP_EXECUTOR);
}
this.taskExecutor = executor;
return this;
}
/**
* Sets custom {@linkplain Executor executor} for tasks of displaying <b>cached on disk</b> images (these tasks
* are executed quickly so UIL prefer to use separate executor for them).<br />
* <br />
* If you set the same executor for {@linkplain #taskExecutor(Executor) general tasks} and
* 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.<br />
* <br />
* <b>NOTE:</b> If you set custom executor then following configuration options will not be considered for this
* executor:
* <ul>
* <li>{@link #threadPoolSize(int)}</li>
* <li>{@link #threadPriority(int)}</li>
* <li>{@link #tasksProcessingOrder(QueueProcessingType)}</li>
* </ul>
*
* @see #taskExecutor(Executor)
*/
public Builder taskExecutorForCachedImages(Executor executorForCachedImages) {
if (threadPoolSize != DEFAULT_THREAD_POOL_SIZE || threadPriority != DEFAULT_THREAD_PRIORITY || tasksProcessingType != DEFAULT_TASK_PROCESSING_TYPE) {
L.w(WARNING_OVERLAP_EXECUTOR);
}
this.taskExecutorForCachedImages = executorForCachedImages;
return this;
}
/**
* Sets thread pool size for image display tasks.<br />
* Default value - {@link #DEFAULT_THREAD_POOL_SIZE this}
*/
public Builder threadPoolSize(int threadPoolSize) {
if (taskExecutor != null || taskExecutorForCachedImages != null) {
L.w(WARNING_OVERLAP_EXECUTOR);
}
this.threadPoolSize = threadPoolSize;
return this;
}
/**
* Sets the priority for image loading threads. Should be <b>NOT</b> greater than {@link Thread#MAX_PRIORITY} or
* less than {@link Thread#MIN_PRIORITY}<br />
* Default value - {@link #DEFAULT_THREAD_PRIORITY this}
*/
public Builder threadPriority(int threadPriority) {
if (taskExecutor != null || taskExecutorForCachedImages != null) {
L.w(WARNING_OVERLAP_EXECUTOR);
}
if (threadPriority < Thread.MIN_PRIORITY) {
this.threadPriority = Thread.MIN_PRIORITY;
} else {
if (threadPriority > Thread.MAX_PRIORITY) {
this.threadPriority = Thread.MAX_PRIORITY;
} else {
this.threadPriority = threadPriority;
}
}
return this;
}
/**
* When you display an image in a small {@link android.widget.ImageView ImageView} and later you try to display
* this image (from identical URI) in a larger {@link android.widget.ImageView ImageView} so decoded image of
* bigger size will be cached in memory as a previous decoded image of smaller size.<br />
* So <b>the default behavior is to allow to cache multiple sizes of one image in memory</b>. You can
* <b>deny</b> it by calling <b>this</b> 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;
}
/**
* Sets type of queue processing for tasks for loading and displaying images.<br />
* Default value - {@link QueueProcessingType#FIFO}
*/
public Builder tasksProcessingOrder(QueueProcessingType tasksProcessingType) {
if (taskExecutor != null || taskExecutorForCachedImages != null) {
L.w(WARNING_OVERLAP_EXECUTOR);
}
this.tasksProcessingType = tasksProcessingType;
return this;
}
/**
* Sets maximum memory cache size for {@link android.graphics.Bitmap bitmaps} (in bytes).<br />
* Default value - 1/8 of available app memory.<br />
* <b>NOTE:</b> 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(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");
if (memoryCache != null) {
L.w(WARNING_OVERLAP_MEMORY_CACHE);
}
this.memoryCacheSize = memoryCacheSize;
return this;
}
/**
* Sets maximum memory cache size (in percent of available app memory) for {@link android.graphics.Bitmap
* bitmaps}.<br />
* Default value - 1/8 of available app memory.<br />
* <b>NOTE:</b> 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(MemoryCache)} method to set your own implementation of
* {@link MemoryCache}.
*/
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 * (availableMemoryPercent / 100f));
return this;
}
/**
* Sets memory cache for {@link android.graphics.Bitmap bitmaps}.<br />
* Default value - {@link com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache LruMemoryCache}
* with limited memory cache size (size = 1/8 of available app memory)<br />
* <br />
* <b>NOTE:</b> If you set custom memory cache then following configuration option will not be considered:
* <ul>
* <li>{@link #memoryCacheSize(int)}</li>
* </ul>
*/
public Builder memoryCache(MemoryCache memoryCache) {
if (memoryCacheSize != 0) {
L.w(WARNING_OVERLAP_MEMORY_CACHE);
}
this.memoryCache = memoryCache;
return this;
}
/** @deprecated Use {@link #diskCacheSize(int)} instead */
@Deprecated
public Builder discCacheSize(int maxCacheSize) {
return diskCacheSize(maxCacheSize);
}
/**
* Sets maximum disk cache size for images (in bytes).<br />
* By default: disk cache is unlimited.<br />
* <b>NOTE:</b> If you use this method then
* {@link com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiscCache LruDiscCache}
* will be used as disk cache. You can use {@link #diskCache(DiskCache)} method for introduction your own
* implementation of {@link DiskCache}
*/
public Builder diskCacheSize(int maxCacheSize) {
if (maxCacheSize <= 0) throw new IllegalArgumentException("maxCacheSize must be a positive number");
if (diskCache != null) {
L.w(WARNING_OVERLAP_DISK_CACHE_PARAMS);
}
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 disk cache directory.<br />
* By default: disk cache is unlimited.<br />
* <b>NOTE:</b> If you use this method then
* {@link com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiscCache LruDiscCache}
* will be used as disk cache. You can use {@link #diskCache(DiskCache)} method for introduction your own
* implementation of {@link DiskCache}
*/
public Builder diskCacheFileCount(int maxFileCount) {
if (maxFileCount <= 0) throw new IllegalArgumentException("maxFileCount must be a positive number");
if (diskCache != null) {
L.w(WARNING_OVERLAP_DISK_CACHE_PARAMS);
}
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 disk cache.<br />
* Default value -
* {@link com.nostra13.universalimageloader.core.DefaultConfigurationFactory#createFileNameGenerator()
* DefaultConfigurationFactory.createFileNameGenerator()}
*/
public Builder diskCacheFileNameGenerator(FileNameGenerator fileNameGenerator) {
if (diskCache != null) {
L.w(WARNING_OVERLAP_DISK_CACHE_NAME_GENERATOR);
}
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.<br />
* Default value - {@link com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache
* BaseDiscCache}. Cache directory is defined by
* {@link com.nostra13.universalimageloader.utils.StorageUtils#getCacheDirectory(Context)
* StorageUtils.getCacheDirectory(Context)}.<br />
* <br />
* <b>NOTE:</b> If you set custom disk cache then following configuration option will not be considered:
* <ul>
* <li>{@link #diskCacheSize(int)}</li>
* <li>{@link #diskCacheFileCount(int)}</li>
* <li>{@link #diskCacheFileNameGenerator(FileNameGenerator)}</li>
* </ul>
*/
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;
}
/**
* Sets utility which will be responsible for downloading of image.<br />
* Default value -
* {@link com.nostra13.universalimageloader.core.DefaultConfigurationFactory#createImageDownloader(Context)
* DefaultConfigurationFactory.createImageDownloader()}
*/
public Builder imageDownloader(ImageDownloader imageDownloader) {
this.downloader = imageDownloader;
return this;
}
/**
* Sets utility which will be responsible for decoding of image stream.<br />
* Default value -
* {@link com.nostra13.universalimageloader.core.DefaultConfigurationFactory#createImageDecoder(boolean)
* DefaultConfigurationFactory.createImageDecoder()}
*/
public Builder imageDecoder(ImageDecoder imageDecoder) {
this.decoder = imageDecoder;
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}
* without passing custom {@linkplain DisplayImageOptions options}<br />
* Default value - {@link DisplayImageOptions#createSimple() Simple options}
*/
public Builder defaultDisplayImageOptions(DisplayImageOptions defaultDisplayImageOptions) {
this.defaultDisplayImageOptions = defaultDisplayImageOptions;
return this;
}
/**
* 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)
*/
public Builder writeDebugLogs() {
this.writeLogs = true;
return this;
}
/** Builds configured {@link ImageLoaderConfiguration} object */
public ImageLoaderConfiguration build() {
initEmptyFieldsWithDefaultValues();
return new ImageLoaderConfiguration(this);
}
private void initEmptyFieldsWithDefaultValues() {
if (taskExecutor == null) {
taskExecutor = DefaultConfigurationFactory
.createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
} else {
customExecutor = true;
}
if (taskExecutorForCachedImages == null) {
taskExecutorForCachedImages = DefaultConfigurationFactory
.createExecutor(threadPoolSize, threadPriority, tasksProcessingType);
} else {
customExecutorForCachedImages = true;
}
if (diskCache == null) {
if (diskCacheFileNameGenerator == null) {
diskCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator();
}
diskCache = DefaultConfigurationFactory
.createDiskCache(context, diskCacheFileNameGenerator, diskCacheSize, diskCacheFileCount);
}
if (memoryCache == null) {
memoryCache = DefaultConfigurationFactory.createMemoryCache(memoryCacheSize);
}
if (denyCacheImageMultipleSizesInMemory) {
memoryCache = new FuzzyKeyMemoryCache(memoryCache, MemoryCacheUtils.createFuzzyKeyComparator());
}
if (downloader == null) {
downloader = DefaultConfigurationFactory.createImageDownloader(context);
}
if (decoder == null) {
decoder = DefaultConfigurationFactory.createImageDecoder(writeLogs);
}
if (defaultDisplayImageOptions == null) {
defaultDisplayImageOptions = DisplayImageOptions.createSimple();
}
}
}
/**
* Decorator. Prevents downloads from network (throws {@link IllegalStateException exception}).<br />
* 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 <a href="http://code.google.com/p/android/issues/detail?id=6066">this problem</a> 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;
}
}
}
}

View File

@ -0,0 +1,217 @@
/*******************************************************************************
* 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.view.View;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.assist.FlushedInputStream;
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.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
/**
* {@link ImageLoader} engine which responsible for {@linkplain LoadAndDisplayImageTask display task} execution.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.7.1
*/
class ImageLoaderEngine {
final ImageLoaderConfiguration configuration;
private Executor taskExecutor;
private Executor taskExecutorForCachedImages;
private Executor taskDistributor;
private final Map<Integer, String> cacheKeysForImageAwares = Collections
.synchronizedMap(new HashMap<Integer, String>());
private final Map<String, ReentrantLock> uriLocks = new WeakHashMap<String, ReentrantLock>();
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 = DefaultConfigurationFactory.createTaskDistributor();
}
/** Submits task to execution pool */
void submit(final LoadAndDisplayImageTask task) {
taskDistributor.execute(new Runnable() {
@Override
public void run() {
File image = configuration.diskCache.get(task.getLoadingUri());
boolean isImageCachedOnDisk = image != null && image.exists();
initExecutorsIfNeed();
if (isImageCachedOnDisk) {
taskExecutorForCachedImages.execute(task);
} else {
taskExecutor.execute(task);
}
}
});
}
/** Submits task to execution pool */
void submit(ProcessAndDisplayImageTask task) {
initExecutorsIfNeed();
taskExecutorForCachedImages.execute(task);
}
private void initExecutorsIfNeed() {
if (!configuration.customExecutor && ((ExecutorService) taskExecutor).isShutdown()) {
taskExecutor = createTaskExecutor();
}
if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages)
.isShutdown()) {
taskExecutorForCachedImages = createTaskExecutor();
}
}
private Executor createTaskExecutor() {
return DefaultConfigurationFactory
.createExecutor(configuration.threadPoolSize, configuration.threadPriority,
configuration.tasksProcessingType);
}
/**
* 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 <b>memoryCacheKey</b> with <b>imageAware</b>. Then it helps to define image URI is loaded into View at
* exact moment.
*/
void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
}
/**
* Cancels the task of loading and displaying image for incoming <b>imageAware</b>.
*
* @param imageAware {@link com.nostra13.universalimageloader.core.imageaware.ImageAware} for which display task
* will be cancelled
*/
void cancelDisplayTaskFor(ImageAware imageAware) {
cacheKeysForImageAwares.remove(imageAware.getId());
}
/**
* Denies or allows engine to download images from the network.<br /> <br /> 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 <b>true</b> - to deny engine to download images from the network; <b>false</b> -
* to allow engine to download images from network.
*/
void denyNetworkDownloads(boolean denyNetworkDownloads) {
networkDenied.set(denyNetworkDownloads);
}
/**
* Sets option whether ImageLoader will use {@link FlushedInputStream} for network downloads to handle <a
* href="http://code.google.com/p/android/issues/detail?id=6066">this known problem</a> or not.
*
* @param handleSlowNetwork pass <b>true</b> - to use {@link FlushedInputStream} for network downloads; <b>false</b>
* - otherwise.
*/
void handleSlowNetwork(boolean handleSlowNetwork) {
slowNetwork.set(handleSlowNetwork);
}
/**
* Pauses engine. All new "load&display" tasks won't be executed until ImageLoader is {@link #resume() resumed}.<br
* /> Already running tasks are not paused.
*/
void pause() {
paused.set(true);
}
/** Resumes engine work. Paused "load&display" tasks will continue its work. */
void resume() {
paused.set(false);
synchronized (pauseLock) {
pauseLock.notifyAll();
}
}
/**
* Stops engine, cancels all running and scheduled display image tasks. Clears internal data.
* <br />
* <b>NOTE:</b> 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();
}
if (!configuration.customExecutorForCachedImages) {
((ExecutorService) taskExecutorForCachedImages).shutdownNow();
}
cacheKeysForImageAwares.clear();
uriLocks.clear();
}
void fireCallback(Runnable r) {
taskDistributor.execute(r);
}
ReentrantLock getLockForUri(String uri) {
ReentrantLock lock = uriLocks.get(uri);
if (lock == null) {
lock = new ReentrantLock();
uriLocks.put(uri, lock);
}
return lock;
}
AtomicBoolean getPause() {
return paused;
}
Object getPauseLock() {
return pauseLock;
}
boolean isNetworkDenied() {
return networkDenied.get();
}
boolean isSlowNetwork() {
return slowNetwork.get();
}
}

View File

@ -0,0 +1,58 @@
/*******************************************************************************
* 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 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.imageaware.ImageAware;
import java.util.concurrent.locks.ReentrantLock;
/**
* Information for load'n'display image task
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @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 ImageAware imageAware;
final ImageSize targetSize;
final DisplayImageOptions options;
final ImageLoadingListener listener;
final ImageLoadingProgressListener progressListener;
final ReentrantLock loadFromUriLock;
public ImageLoadingInfo(String uri, ImageAware imageAware, ImageSize targetSize, String memoryCacheKey,
DisplayImageOptions options, ImageLoadingListener listener,
ImageLoadingProgressListener progressListener, ReentrantLock loadFromUriLock) {
this.uri = uri;
this.imageAware = imageAware;
this.targetSize = targetSize;
this.options = options;
this.listener = listener;
this.progressListener = progressListener;
this.loadFromUriLock = loadFromUriLock;
this.memoryCacheKey = memoryCacheKey;
}
}

View File

@ -0,0 +1,472 @@
/*******************************************************************************
* 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 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_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 <b>true</b> - if task should be interrupted; <b>false</b> - 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 <b>true</b> - if task should be interrupted; <b>false</b> - 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()) {
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 <b>true</b> - if image was downloaded successfully; <b>false</b> - 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());
return configuration.diskCache.save(uri, is, this);
}
/** 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 <b>true</b> - if loading should be continued; <b>false</b> - 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 <b>true</b> - 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)); <b>false</b> - 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 <b>true</b> - if target ImageAware is collected by GC; <b>false</b> - 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 <b>true</b> - if current ImageAware is reused for displaying another image; <b>false</b> - 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 <b>true</b> - if current task was interrupted; <b>false</b> - 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 {
}
}

View File

@ -0,0 +1,59 @@
/*******************************************************************************
* 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.core.assist.LoadedFrom;
import com.nostra13.universalimageloader.core.process.BitmapProcessor;
import com.nostra13.universalimageloader.utils.L;
/**
* Presents process'n'display image task. Processes image {@linkplain Bitmap} and display it in {@link ImageView} using
* {@link DisplayBitmapTask}.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.8.0
*/
final class ProcessAndDisplayImageTask implements Runnable {
private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]";
private final ImageLoaderEngine engine;
private final Bitmap bitmap;
private final ImageLoadingInfo imageLoadingInfo;
private final Handler handler;
public ProcessAndDisplayImageTask(ImageLoaderEngine engine, Bitmap bitmap, ImageLoadingInfo imageLoadingInfo,
Handler handler) {
this.engine = engine;
this.bitmap = bitmap;
this.imageLoadingInfo = imageLoadingInfo;
this.handler = handler;
}
@Override
public void run() {
L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);
BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
Bitmap processedBitmap = processor.process(bitmap);
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
LoadedFrom.MEMORY_CACHE);
LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}
}

View File

@ -0,0 +1,82 @@
/*******************************************************************************
* 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.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();
}
}

View File

@ -0,0 +1,65 @@
/*******************************************************************************
* 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.assist;
/**
* Presents the reason why image loading and displaying was failed
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
public class FailReason {
private final FailType type;
private final Throwable cause;
public FailReason(FailType type, Throwable cause) {
this.type = type;
this.cause = cause;
}
/** @return {@linkplain FailType Fail type} */
public FailType getType() {
return type;
}
/** @return Thrown exception/error, can be <b>null</b> */
public Throwable getCause() {
return cause;
}
/** Presents type of fail while image loading */
public static enum FailType {
/** Input/output error. Can be caused by network communication fail or error while caching image on file system. */
IO_ERROR,
/**
* Error while
* {@linkplain android.graphics.BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options)
* decode image to Bitmap}
*/
DECODING_ERROR,
/**
* {@linkplain com.nostra13.universalimageloader.core.ImageLoader#denyNetworkDownloads(boolean) Network
* downloads are denied} and requested image wasn't cached in disk cache before.
*/
NETWORK_DENIED,
/** Not enough memory to create needed Bitmap for image */
OUT_OF_MEMORY,
/** Unknown error was occurred while loading image */
UNKNOWN
}
}

View File

@ -0,0 +1,34 @@
package com.nostra13.universalimageloader.core.assist;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Many streams obtained over slow connection show <a href="http://code.google.com/p/android/issues/detail?id=6066">this
* problem</a>.
*/
public class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int by_te = read();
if (by_te < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}

View File

@ -0,0 +1,78 @@
/*******************************************************************************
* 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.assist;
/**
* Type of image scaling during decoding.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.5.0
*/
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.<br />
* 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".<br />
* 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.<br />
* It's <b>fast</b> type and it's preferable for usage in lists/grids/galleries (and other
* {@linkplain android.widget.AdapterView adapter-views}) .<br />
* Relates to {@link android.graphics.BitmapFactory.Options#inSampleSize}<br />
* Note: If original image size is smaller than target size then original image <b>won't</b> be scaled.
*/
IN_SAMPLE_POWER_OF_2,
/**
* Image will be subsampled in an integer number of times (1, 2, 3, ...). Use it if memory economy is quite
* important.<br />
* Relates to {@link android.graphics.BitmapFactory.Options#inSampleSize}<br />
* Note: If original image size is smaller than target size then original image <b>won't</b> be scaled.
*/
IN_SAMPLE_INT,
/**
* Image will scaled-down 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 important.<br />
* <b>Note:</b> If original image size is smaller than target size then original image <b>won't</b> be scaled.<br />
* <br />
* <b>NOTE:</b> For creating result Bitmap (of exact size) additional Bitmap will be created with
* {@link android.graphics.Bitmap#createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean)
* Bitmap.createBitmap(...)}.<br />
* <b>Cons:</b> Saves memory by keeping smaller Bitmap in memory cache (comparing with IN_SAMPLE... scale types)<br />
* <b>Pros:</b> 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
* important.<br />
* <b>Note:</b> If original image size is smaller than target size then original image <b>will be stretched</b> to
* target size.<br />
* <br />
* <b>NOTE:</b> For creating result Bitmap (of exact size) additional Bitmap will be created with
* {@link android.graphics.Bitmap#createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean)
* Bitmap.createBitmap(...)}.<br />
* <b>Cons:</b> Saves memory by keeping smaller Bitmap in memory cache (comparing with IN_SAMPLE... scale types)<br />
* <b>Pros:</b> Requires more memory in one time for creation of result Bitmap.
*/
EXACTLY_STRETCHED
}

View File

@ -0,0 +1,69 @@
/*******************************************************************************
* 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.assist;
/**
* Present width and height values
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
public class ImageSize {
private static final int TO_STRING_MAX_LENGHT = 9; // "9999x9999".length()
private static final String SEPARATOR = "x";
private final int width;
private final int height;
public ImageSize(int width, int height) {
this.width = width;
this.height = height;
}
public ImageSize(int width, int height, int rotation) {
if (rotation % 180 == 0) {
this.width = width;
this.height = height;
} else {
this.width = height;
this.height = width;
}
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
/** Scales down dimensions in <b>sampleSize</b> times. Returns new object. */
public ImageSize scaleDown(int sampleSize) {
return new ImageSize(width / sampleSize, height / sampleSize);
}
/** Scales dimensions according to incoming scale. Returns new object. */
public ImageSize scale(float scale) {
return new ImageSize((int) (width * scale), (int) (height * scale));
}
@Override
public String toString() {
return new StringBuilder(TO_STRING_MAX_LENGHT).append(width).append(SEPARATOR).append(height).toString();
}
}

View File

@ -0,0 +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
}

View File

@ -0,0 +1,26 @@
/*******************************************************************************
* 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.assist;
/**
* Queue processing type which will be used for display task processing
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.6.3
*/
public enum QueueProcessingType {
FIFO, LIFO
}

View File

@ -0,0 +1,73 @@
/*******************************************************************************
* 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.assist;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
/**
* Simplify {@linkplain ScaleType ImageView's scale type} to 2 types: {@link #FIT_INSIDE} and {@link #CROP}
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.6.1
*/
public enum ViewScaleType {
/**
* 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,
/**
* 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 larger than the corresponding dimension of the view.
*/
CROP;
/**
* Defines scale type of ImageView.
*
* @param imageView {@link ImageView}
* @return {@link #FIT_INSIDE} for
* <ul>
* <li>{@link ScaleType#FIT_CENTER}</li>
* <li>{@link ScaleType#FIT_XY}</li>
* <li>{@link ScaleType#FIT_START}</li>
* <li>{@link ScaleType#FIT_END}</li>
* <li>{@link ScaleType#CENTER_INSIDE}</li>
* </ul>
* {@link #CROP} for
* <ul>
* <li>{@link ScaleType#CENTER}</li>
* <li>{@link ScaleType#CENTER_CROP}</li>
* <li>{@link ScaleType#MATRIX}</li>
* </ul>
*/
public static ViewScaleType fromImageView(ImageView imageView) {
switch (imageView.getScaleType()) {
case FIT_CENTER:
case FIT_XY:
case FIT_START:
case FIT_END:
case CENTER_INSIDE:
return FIT_INSIDE;
case MATRIX:
case CENTER:
case CENTER_CROP:
default:
return CROP;
}
}
}

View File

@ -0,0 +1,616 @@
/*
* 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.
*
* <p><tt>BlockingDeque</tt> 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
* <tt>null</tt> or <tt>false</tt>, 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:
*
* <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <tr>
* <td ALIGN=CENTER COLSPAN = 5> <b>First Element (Head)</b></td>
* </tr>
* <tr>
* <td></td>
* <td ALIGN=CENTER><em>Throws exception</em></td>
* <td ALIGN=CENTER><em>Special value</em></td>
* <td ALIGN=CENTER><em>Blocks</em></td>
* <td ALIGN=CENTER><em>Times out</em></td>
* </tr>
* <tr>
* <td><b>Insert</b></td>
* <td>{@link #addFirst addFirst(e)}</td>
* <td>{@link #offerFirst offerFirst(e)}</td>
* <td>{@link #putFirst putFirst(e)}</td>
* <td>{@link #offerFirst offerFirst(e, time, unit)}</td>
* </tr>
* <tr>
* <td><b>Remove</b></td>
* <td>{@link #removeFirst removeFirst()}</td>
* <td>{@link #pollFirst pollFirst()}</td>
* <td>{@link #takeFirst takeFirst()}</td>
* <td>{@link #pollFirst(long, TimeUnit) pollFirst(time, unit)}</td>
* </tr>
* <tr>
* <td><b>Examine</b></td>
* <td>{@link #getFirst getFirst()}</td>
* <td>{@link #peekFirst peekFirst()}</td>
* <td><em>not applicable</em></td>
* <td><em>not applicable</em></td>
* </tr>
* <tr>
* <td ALIGN=CENTER COLSPAN = 5> <b>Last Element (Tail)</b></td>
* </tr>
* <tr>
* <td></td>
* <td ALIGN=CENTER><em>Throws exception</em></td>
* <td ALIGN=CENTER><em>Special value</em></td>
* <td ALIGN=CENTER><em>Blocks</em></td>
* <td ALIGN=CENTER><em>Times out</em></td>
* </tr>
* <tr>
* <td><b>Insert</b></td>
* <td>{@link #addLast addLast(e)}</td>
* <td>{@link #offerLast offerLast(e)}</td>
* <td>{@link #putLast putLast(e)}</td>
* <td>{@link #offerLast offerLast(e, time, unit)}</td>
* </tr>
* <tr>
* <td><b>Remove</b></td>
* <td>{@link #removeLast() removeLast()}</td>
* <td>{@link #pollLast() pollLast()}</td>
* <td>{@link #takeLast takeLast()}</td>
* <td>{@link #pollLast(long, TimeUnit) pollLast(time, unit)}</td>
* </tr>
* <tr>
* <td><b>Examine</b></td>
* <td>{@link #getLast getLast()}</td>
* <td>{@link #peekLast peekLast()}</td>
* <td><em>not applicable</em></td>
* <td><em>not applicable</em></td>
* </tr>
* </table>
*
* <p>Like any {@link BlockingQueue}, a <tt>BlockingDeque</tt> is thread safe,
* does not permit null elements, and may (or may not) be
* capacity-constrained.
*
* <p>A <tt>BlockingDeque</tt> implementation may be used directly as a FIFO
* <tt>BlockingQueue</tt>. The methods inherited from the
* <tt>BlockingQueue</tt> interface are precisely equivalent to
* <tt>BlockingDeque</tt> methods as indicated in the following table:
*
* <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <tr>
* <td ALIGN=CENTER> <b><tt>BlockingQueue</tt> Method</b></td>
* <td ALIGN=CENTER> <b>Equivalent <tt>BlockingDeque</tt> Method</b></td>
* </tr>
* <tr>
* <td ALIGN=CENTER COLSPAN = 2> <b>Insert</b></td>
* </tr>
* <tr>
* <td>{@link #add add(e)}</td>
* <td>{@link #addLast addLast(e)}</td>
* </tr>
* <tr>
* <td>{@link #offer offer(e)}</td>
* <td>{@link #offerLast offerLast(e)}</td>
* </tr>
* <tr>
* <td>{@link #put put(e)}</td>
* <td>{@link #putLast putLast(e)}</td>
* </tr>
* <tr>
* <td>{@link #offer offer(e, time, unit)}</td>
* <td>{@link #offerLast offerLast(e, time, unit)}</td>
* </tr>
* <tr>
* <td ALIGN=CENTER COLSPAN = 2> <b>Remove</b></td>
* </tr>
* <tr>
* <td>{@link #remove() remove()}</td>
* <td>{@link #removeFirst() removeFirst()}</td>
* </tr>
* <tr>
* <td>{@link #poll() poll()}</td>
* <td>{@link #pollFirst() pollFirst()}</td>
* </tr>
* <tr>
* <td>{@link #take() take()}</td>
* <td>{@link #takeFirst() takeFirst()}</td>
* </tr>
* <tr>
* <td>{@link #poll(long, TimeUnit) poll(time, unit)}</td>
* <td>{@link #pollFirst(long, TimeUnit) pollFirst(time, unit)}</td>
* </tr>
* <tr>
* <td ALIGN=CENTER COLSPAN = 2> <b>Examine</b></td>
* </tr>
* <tr>
* <td>{@link #element() element()}</td>
* <td>{@link #getFirst() getFirst()}</td>
* </tr>
* <tr>
* <td>{@link #peek() peek()}</td>
* <td>{@link #peekFirst() peekFirst()}</td>
* </tr>
* </table>
*
* <p>Memory consistency effects: As with other concurrent
* collections, actions in a thread prior to placing an object into a
* {@code BlockingDeque}
* <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a>
* actions subsequent to the access or removal of that element from
* the {@code BlockingDeque} in another thread.
*
* <p>This interface is a member of the
* <a href="{@docRoot}/../technotes/guides/collections/index.html">
* Java Collections Framework</a>.
*
* @since 1.6
* @author Doug Lea
* @param <E> the type of elements held in this collection
*/
public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> {
/*
* 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 <tt>IllegalStateException</tt> 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 <tt>IllegalStateException</tt> 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 <tt>true</tt> upon success and <tt>false</tt> 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 <tt>true</tt> upon success and <tt>false</tt> 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
* <tt>unit</tt>
* @param unit a <tt>TimeUnit</tt> determining how to interpret the
* <tt>timeout</tt> parameter
* @return <tt>true</tt> if successful, or <tt>false</tt> 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
* <tt>unit</tt>
* @param unit a <tt>TimeUnit</tt> determining how to interpret the
* <tt>timeout</tt> parameter
* @return <tt>true</tt> if successful, or <tt>false</tt> 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
* <tt>unit</tt>
* @param unit a <tt>TimeUnit</tt> determining how to interpret the
* <tt>timeout</tt> parameter
* @return the head of this deque, or <tt>null</tt> 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
* <tt>unit</tt>
* @param unit a <tt>TimeUnit</tt> determining how to interpret the
* <tt>timeout</tt> parameter
* @return the tail of this deque, or <tt>null</tt> 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 <tt>e</tt> such that
* <tt>o.equals(e)</tt> (if such an element exists).
* Returns <tt>true</tt> 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 <tt>true</tt> 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 <tt>e</tt> such that
* <tt>o.equals(e)</tt> (if such an element exists).
* Returns <tt>true</tt> 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 <tt>true</tt> 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
* <tt>true</tt> upon success and throwing an
* <tt>IllegalStateException</tt> if no space is currently available.
* When using a capacity-restricted deque, it is generally preferable to
* use {@link #offer offer}.
*
* <p>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
* <tt>true</tt> upon success and <tt>false</tt> 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.
*
* <p>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.
*
* <p>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.
*
* <p>This method is equivalent to
* {@link #offerLast offerLast}.
*
* @param e the element to add
* @return <tt>true</tt> if the element was added to this deque, else
* <tt>false</tt>
* @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.
*
* <p>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
* <tt>null</tt> if this deque is empty.
*
* <p>This method is equivalent to {@link #pollFirst()}.
*
* @return the head of this deque, or <tt>null</tt> 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.
*
* <p>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.
*
* <p>This method is equivalent to
* {@link #pollFirst(long,TimeUnit) pollFirst}.
*
* @return the head of this deque, or <tt>null</tt> 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.
*
* <p>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 <tt>null</tt> if this deque is empty.
*
* <p>This method is equivalent to {@link #peekFirst() peekFirst}.
*
* @return the head of this deque, or <tt>null</tt> 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 <tt>e</tt> such that
* <tt>o.equals(e)</tt> (if such an element exists).
* Returns <tt>true</tt> if this deque contained the specified element
* (or equivalently, if this deque changed as a result of the call).
*
* <p>This method is equivalent to
* {@link #removeFirstOccurrence removeFirstOccurrence}.
*
* @param o element to be removed from this deque, if present
* @return <tt>true</tt> 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 <tt>true</tt> if this deque contains the specified element.
* More formally, returns <tt>true</tt> if and only if this deque contains
* at least one element <tt>e</tt> such that <tt>o.equals(e)</tt>.
*
* @param o object to be checked for containment in this deque
* @return <tt>true</tt> 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<E> 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.
*
* <p>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);
}

View File

@ -0,0 +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 <i>deque</i> is short for "double ended queue"
* and is usually pronounced "deck". Most <tt>Deque</tt>
* 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.
*
* <p>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 <tt>null</tt> or <tt>false</tt>, depending on
* the operation). The latter form of the insert operation is
* designed specifically for use with capacity-restricted
* <tt>Deque</tt> implementations; in most implementations, insert
* operations cannot fail.
*
* <p>The twelve methods described above are summarized in the
* following table:
*
* <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <tr>
* <td></td>
* <td ALIGN=CENTER COLSPAN = 2> <b>First Element (Head)</b></td>
* <td ALIGN=CENTER COLSPAN = 2> <b>Last Element (Tail)</b></td>
* </tr>
* <tr>
* <td></td>
* <td ALIGN=CENTER><em>Throws exception</em></td>
* <td ALIGN=CENTER><em>Special value</em></td>
* <td ALIGN=CENTER><em>Throws exception</em></td>
* <td ALIGN=CENTER><em>Special value</em></td>
* </tr>
* <tr>
* <td><b>Insert</b></td>
* <td>{@link #addFirst addFirst(e)}</td>
* <td>{@link #offerFirst offerFirst(e)}</td>
* <td>{@link #addLast addLast(e)}</td>
* <td>{@link #offerLast offerLast(e)}</td>
* </tr>
* <tr>
* <td><b>Remove</b></td>
* <td>{@link #removeFirst removeFirst()}</td>
* <td>{@link #pollFirst pollFirst()}</td>
* <td>{@link #removeLast removeLast()}</td>
* <td>{@link #pollLast pollLast()}</td>
* </tr>
* <tr>
* <td><b>Examine</b></td>
* <td>{@link #getFirst getFirst()}</td>
* <td>{@link #peekFirst peekFirst()}</td>
* <td>{@link #getLast getLast()}</td>
* <td>{@link #peekLast peekLast()}</td>
* </tr>
* </table>
*
* <p>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 <tt>Queue</tt> interface are precisely equivalent to
* <tt>Deque</tt> methods as indicated in the following table:
*
* <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <tr>
* <td ALIGN=CENTER> <b><tt>Queue</tt> Method</b></td>
* <td ALIGN=CENTER> <b>Equivalent <tt>Deque</tt> Method</b></td>
* </tr>
* <tr>
* <td>{@link java.util.Queue#add add(e)}</td>
* <td>{@link #addLast addLast(e)}</td>
* </tr>
* <tr>
* <td>{@link java.util.Queue#offer offer(e)}</td>
* <td>{@link #offerLast offerLast(e)}</td>
* </tr>
* <tr>
* <td>{@link java.util.Queue#remove remove()}</td>
* <td>{@link #removeFirst removeFirst()}</td>
* </tr>
* <tr>
* <td>{@link java.util.Queue#poll poll()}</td>
* <td>{@link #pollFirst pollFirst()}</td>
* </tr>
* <tr>
* <td>{@link java.util.Queue#element element()}</td>
* <td>{@link #getFirst getFirst()}</td>
* </tr>
* <tr>
* <td>{@link java.util.Queue#peek peek()}</td>
* <td>{@link #peek peekFirst()}</td>
* </tr>
* </table>
*
* <p>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
* <tt>Deque</tt> methods as indicated in the table below:
*
* <p>
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <tr>
* <td ALIGN=CENTER> <b>Stack Method</b></td>
* <td ALIGN=CENTER> <b>Equivalent <tt>Deque</tt> Method</b></td>
* </tr>
* <tr>
* <td>{@link #push push(e)}</td>
* <td>{@link #addFirst addFirst(e)}</td>
* </tr>
* <tr>
* <td>{@link #pop pop()}</td>
* <td>{@link #removeFirst removeFirst()}</td>
* </tr>
* <tr>
* <td>{@link #peek peek()}</td>
* <td>{@link #peekFirst peekFirst()}</td>
* </tr>
* </table>
*
* <p>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.
*
* <p>This interface provides two methods to remove interior
* elements, {@link #removeFirstOccurrence removeFirstOccurrence} and
* {@link #removeLastOccurrence removeLastOccurrence}.
*
* <p>Unlike the {@link List} interface, this interface does not
* provide support for indexed access to elements.
*
* <p>While <tt>Deque</tt> implementations are not strictly required
* to prohibit the insertion of null elements, they are strongly
* encouraged to do so. Users of any <tt>Deque</tt> implementations
* that do allow null elements are strongly encouraged <i>not</i> to
* take advantage of the ability to insert nulls. This is so because
* <tt>null</tt> is used as a special return value by various methods
* to indicated that the deque is empty.
*
* <p><tt>Deque</tt> implementations generally do not define
* element-based versions of the <tt>equals</tt> and <tt>hashCode</tt>
* methods, but instead inherit the identity-based versions from class
* <tt>Object</tt>.
*
* @author Doug Lea
* @author Josh Bloch
* @since 1.6
* @param <E> the type of elements held in this collection
*/
public interface Deque<E> extends Queue<E> {
/**
* 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}.
*
* <p>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 <tt>true</tt> if the element was added to this deque, else
* <tt>false</tt>
* @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 <tt>true</tt> if the element was added to this deque, else
* <tt>false</tt>
* @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 <tt>null</tt> if this deque is empty.
*
* @return the head of this deque, or <tt>null</tt> if this deque is empty
*/
E pollFirst();
/**
* Retrieves and removes the last element of this deque,
* or returns <tt>null</tt> if this deque is empty.
*
* @return the tail of this deque, or <tt>null</tt> 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 <tt>null</tt> if this deque is empty.
*
* @return the head of this deque, or <tt>null</tt> if this deque is empty
*/
E peekFirst();
/**
* Retrieves, but does not remove, the last element of this deque,
* or returns <tt>null</tt> if this deque is empty.
*
* @return the tail of this deque, or <tt>null</tt> 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 <tt>e</tt> such that
* <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>
* (if such an element exists).
* Returns <tt>true</tt> 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 <tt>true</tt> 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 <tt>e</tt> such that
* <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>
* (if such an element exists).
* Returns <tt>true</tt> 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 <tt>true</tt> 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
* <tt>true</tt> upon success and throwing an
* <tt>IllegalStateException</tt> if no space is currently available.
* When using a capacity-restricted deque, it is generally preferable to
* use {@link #offer offer}.
*
* <p>This method is equivalent to {@link #addLast}.
*
* @param e the element to add
* @return <tt>true</tt> (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
* <tt>true</tt> upon success and <tt>false</tt> 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.
*
* <p>This method is equivalent to {@link #offerLast}.
*
* @param e the element to add
* @return <tt>true</tt> if the element was added to this deque, else
* <tt>false</tt>
* @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.
*
* <p>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
* <tt>null</tt> if this deque is empty.
*
* <p>This method is equivalent to {@link #pollFirst()}.
*
* @return the first element of this deque, or <tt>null</tt> 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.
*
* <p>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 <tt>null</tt> if this deque is empty.
*
* <p>This method is equivalent to {@link #peekFirst()}.
*
* @return the head of the queue represented by this deque, or
* <tt>null</tt> 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
* <tt>true</tt> upon success and throwing an
* <tt>IllegalStateException</tt> if no space is currently available.
*
* <p>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.
*
* <p>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 <tt>e</tt> such that
* <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>
* (if such an element exists).
* Returns <tt>true</tt> if this deque contained the specified element
* (or equivalently, if this deque changed as a result of the call).
*
* <p>This method is equivalent to {@link #removeFirstOccurrence}.
*
* @param o element to be removed from this deque, if present
* @return <tt>true</tt> 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 <tt>true</tt> if this deque contains the specified element.
* More formally, returns <tt>true</tt> if and only if this deque contains
* at least one element <tt>e</tt> such that
* <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
*
* @param o element whose presence in this deque is to be tested
* @return <tt>true</tt> 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<E> 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<E> descendingIterator();
}

View File

@ -0,0 +1,47 @@
package com.nostra13.universalimageloader.core.assist.deque;
import java.util.NoSuchElementException;
/**
* {@link LinkedBlockingDeque} using LIFO algorithm
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.6.3
*/
public class LIFOLinkedBlockingDeque<T> extends LinkedBlockingDeque<T> {
private static final long serialVersionUID = -4114786347960826192L;
/**
* Inserts the specified element at the front of this deque if it is possible to do so immediately without violating
* capacity restrictions, returning <tt>true</tt> upon success and <tt>false</tt> 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}
*/
@Override
public boolean offer(T e) {
return super.offerFirst(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
*/
@Override
public T remove() {
return super.removeFirst();
}
}

View File

@ -0,0 +1,245 @@
/*******************************************************************************
* 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.decode;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Matrix;
import android.media.ExifInterface;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.assist.ImageSize;
import com.nostra13.universalimageloader.core.download.ImageDownloader.Scheme;
import com.nostra13.universalimageloader.utils.ImageSizeUtils;
import com.nostra13.universalimageloader.utils.IoUtils;
import com.nostra13.universalimageloader.utils.L;
import java.io.IOException;
import java.io.InputStream;
/**
* Decodes images to {@link Bitmap}, scales them to needed size
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @see ImageDecodingInfo
* @since 1.8.3
*/
public class BaseImageDecoder implements ImageDecoder {
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_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()}
*/
public BaseImageDecoder(boolean loggingEnabled) {
this.loggingEnabled = loggingEnabled;
}
/**
* Decodes image from URI into {@link Bitmap}. Image is scaled close to incoming {@linkplain ImageSize target size}
* during decoding (depend on incoming parameters).
*
* @param decodingInfo Needed data for decoding image
* @return Decoded bitmap
* @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);
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 = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
}
protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)
throws IOException {
Options options = new Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(imageStream, null, options);
ExifInfo exif;
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);
}
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;
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();
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 (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 InputStream resetStream(InputStream imageStream, ImageDecodingInfo decodingInfo) throws IOException {
try {
imageStream.reset();
} catch (IOException e) {
IoUtils.closeSilently(imageStream);
imageStream = getImageStream(decodingInfo);
}
return imageStream;
}
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);
if (Float.compare(scale, 1f) != 0) {
m.setScale(scale, scale);
if (loggingEnabled) {
L.d(LOG_SCALE_IMAGE, srcSize, srcSize.scale(scale), scale, decodingInfo.getImageKey());
}
}
}
// Flip bitmap if need
if (flipHorizontal) {
m.postScale(-1, 1);
if (loggingEnabled) L.d(LOG_FLIP_IMAGE, decodingInfo.getImageKey());
}
// Rotate bitmap if need
if (rotation != 0) {
m.postRotate(rotation);
if (loggingEnabled) L.d(LOG_ROTATE_IMAGE, rotation, decodingInfo.getImageKey());
}
Bitmap finalBitmap = Bitmap.createBitmap(subsampledBitmap, 0, 0, subsampledBitmap.getWidth(), subsampledBitmap
.getHeight(), m, true);
if (finalBitmap != subsampledBitmap) {
subsampledBitmap.recycle();
}
return finalBitmap;
}
protected static class ExifInfo {
public final int rotation;
public final boolean flipHorizontal;
protected ExifInfo() {
this.rotation = 0;
this.flipHorizontal = false;
}
protected ExifInfo(int rotation, boolean flipHorizontal) {
this.rotation = rotation;
this.flipHorizontal = flipHorizontal;
}
}
protected static class ImageFileInfo {
public final ImageSize imageSize;
public final ExifInfo exif;
protected ImageFileInfo(ImageSize imageSize, ExifInfo exif) {
this.imageSize = imageSize;
this.exif = exif;
}
}
}

View File

@ -0,0 +1,39 @@
/*******************************************************************************
* 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.decode;
import android.graphics.Bitmap;
import java.io.IOException;
/**
* Provide decoding image to result {@link Bitmap}.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @see ImageDecodingInfo
* @since 1.8.3
*/
public interface ImageDecoder {
/**
* Decodes image to {@link Bitmap} according target size and other parameters.
*
* @param imageDecodingInfo
* @return
* @throws IOException
*/
Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;
}

View File

@ -0,0 +1,150 @@
/*******************************************************************************
* 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.decode;
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.ViewScaleType;
import com.nostra13.universalimageloader.core.download.ImageDownloader;
/**
* Contains needed information for decoding image to Bitmap
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.8.3
*/
public class ImageDecodingInfo {
private final String imageKey;
private final String imageUri;
private final String originalImageUri;
private final ImageSize targetSize;
private final ImageScaleType imageScaleType;
private final ViewScaleType viewScaleType;
private final ImageDownloader downloader;
private final Object extraForDownloader;
private final boolean considerExifParams;
private final Options decodingOptions;
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();
this.viewScaleType = viewScaleType;
this.downloader = downloader;
this.extraForDownloader = displayOptions.getExtraForDownloader();
considerExifParams = displayOptions.isConsiderExifParams();
decodingOptions = new Options();
copyOptions(displayOptions.getDecodingOptions(), decodingOptions);
}
private void copyOptions(Options srcOptions, Options destOptions) {
destOptions.inDensity = srcOptions.inDensity;
destOptions.inDither = srcOptions.inDither;
destOptions.inInputShareable = srcOptions.inInputShareable;
destOptions.inJustDecodeBounds = srcOptions.inJustDecodeBounds;
destOptions.inPreferredConfig = srcOptions.inPreferredConfig;
destOptions.inPurgeable = srcOptions.inPurgeable;
destOptions.inSampleSize = srcOptions.inSampleSize;
destOptions.inScaled = srcOptions.inScaled;
destOptions.inScreenDensity = srcOptions.inScreenDensity;
destOptions.inTargetDensity = srcOptions.inTargetDensity;
destOptions.inTempStorage = srcOptions.inTempStorage;
if (Build.VERSION.SDK_INT >= 10) copyOptions10(srcOptions, destOptions);
if (Build.VERSION.SDK_INT >= 11) copyOptions11(srcOptions, destOptions);
}
@TargetApi(10)
private void copyOptions10(Options srcOptions, Options destOptions) {
destOptions.inPreferQualityOverSpeed = srcOptions.inPreferQualityOverSpeed;
}
@TargetApi(11)
private void copyOptions11(Options srcOptions, Options destOptions) {
destOptions.inBitmap = srcOptions.inBitmap;
destOptions.inMutable = srcOptions.inMutable;
}
/** @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 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}.
*/
public ImageSize getTargetSize() {
return targetSize;
}
/**
* @return {@linkplain ImageScaleType Scale type for image sampling and scaling}. This parameter affects result size
* of decoded bitmap.
*/
public ImageScaleType getImageScaleType() {
return imageScaleType;
}
/** @return {@linkplain ViewScaleType View scale type}. This parameter affects result size of decoded bitmap. */
public ViewScaleType getViewScaleType() {
return viewScaleType;
}
/** @return Downloader for image loading */
public ImageDownloader getDownloader() {
return downloader;
}
/** @return Auxiliary object for downloader */
public Object getExtraForDownloader() {
return extraForDownloader;
}
/** @return <b>true</b> - if EXIF params of image should be considered; <b>false</b> - otherwise */
public boolean shouldConsiderExifParams() {
return considerExifParams;
}
/** @return Decoding options */
public Options getDecodingOptions() {
return decodingOptions;
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* 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 com.nostra13.universalimageloader.core.assist.LoadedFrom;
import com.nostra13.universalimageloader.core.imageaware.ImageAware;
/**
* Displays {@link Bitmap} in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}. Implementations can
* apply some changes to Bitmap or any animation for displaying Bitmap.<br />
* 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 {
/**
* Displays bitmap in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware}.
* <b>NOTE:</b> 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 imageAware {@linkplain com.nostra13.universalimageloader.core.imageaware.ImageAware Image aware view} to
* display Bitmap
* @param loadedFrom Source of loaded image
*/
void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom);
}

View File

@ -0,0 +1,86 @@
/*******************************************************************************
* Copyright 2011-2013 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);
}
}
}

View File

@ -0,0 +1,118 @@
/*******************************************************************************
* 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.*;
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.
* <br />
* This implementation is inspired by
* <a href="http://www.curious-creature.org/2012/12/11/android-recipe-1-image-with-rounded-corners/">
* Romain Guy's article</a>. It rounds images using custom drawable drawing. Original bitmap isn't changed.
* <br />
* <br />
* If this implementation doesn't meet your needs then consider
* <a href="https://github.com/vinc3m1/RoundedImageView">RoundedImageView</a> or
* <a href="https://github.com/Pkmmte/CircularImageView">CircularImageView</a> 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);
}
@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);
}
}
}

View File

@ -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.
* <br />
* This implementation is inspired by
* <a href="http://www.curious-creature.org/2012/12/11/android-recipe-1-image-with-rounded-corners/">
* Romain Guy's article</a>. It rounds images using custom drawable drawing. Original bitmap isn't changed.
* <br />
* <br />
* If this implementation doesn't meet your needs then consider
* <a href="https://github.com/vinc3m1/RoundedImageView">this project</a> 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));
}
}
}

View File

@ -0,0 +1,33 @@
/*******************************************************************************
* 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 com.nostra13.universalimageloader.core.assist.LoadedFrom;
import com.nostra13.universalimageloader.core.imageaware.ImageAware;
/**
* 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 void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
}
}

View File

@ -0,0 +1,247 @@
/*******************************************************************************
* 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 android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.MediaStore;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.assist.ContentLengthInputStream;
import com.nostra13.universalimageloader.utils.IoUtils;
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;
/**
* Provides retrieving of {@link InputStream} of image by URI from network or file system or app resources.<br />
* {@link URLConnection} is used to retrieve image stream from network.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.8.0
*/
public class BaseImageDownloader implements ImageDownloader {
/** {@value} */
public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
/** {@value} */
public static final int DEFAULT_HTTP_READ_TIMEOUT = 20 * 1000; // milliseconds
/** {@value} */
protected static final int BUFFER_SIZE = 32 * 1024; // 32 Kb
/** {@value} */
protected static final String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%";
protected static final int MAX_REDIRECT_COUNT = 5;
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;
}
public BaseImageDownloader(Context context, int connectTimeout, int readTimeout) {
this.context = context.getApplicationContext();
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
}
@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
switch (Scheme.ofUri(imageUri)) {
case HTTP:
case HTTPS:
return getStreamFromNetwork(imageUri, extra);
case FILE:
return getStreamFromFile(imageUri, extra);
case CONTENT:
return getStreamFromContent(imageUri, extra);
case ASSETS:
return getStreamFromAssets(imageUri, extra);
case DRAWABLE:
return getStreamFromDrawable(imageUri, extra);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}
/**
* Retrieves {@link InputStream} of image by URI (image is located in the network).
*
* @param imageUri Image URI
* @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object)
* DisplayImageOptions.extraForDownloader(Object)}; can be null
* @return {@link InputStream} of image
* @throws IOException if some I/O error occurs during network request or if no InputStream could be created for
* URL.
*/
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
HttpURLConnection conn = createConnection(imageUri, extra);
int redirectCount = 0;
while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
conn = createConnection(conn.getHeaderField("Location"), extra);
redirectCount++;
}
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;
}
return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}
/**
* Create {@linkplain HttpURLConnection HTTP connection} for incoming URL
*
* @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, Object extra) throws IOException {
String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS);
HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection();
conn.setConnectTimeout(connectTimeout);
conn.setReadTimeout(readTimeout);
return conn;
}
/**
* Retrieves {@link InputStream} of image by URI (image is located on the local file system or SD card).
*
* @param imageUri Image URI
* @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object)
* DisplayImageOptions.extraForDownloader(Object)}; can be null
* @return {@link InputStream} of image
* @throws IOException if some I/O error occurs reading from file system
*/
protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {
String filePath = Scheme.FILE.crop(imageUri);
return new ContentLengthInputStream(new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE),
(int) new File(filePath).length());
}
/**
* Retrieves {@link InputStream} of image by URI (image is accessed using {@link ContentResolver}).
*
* @param imageUri Image URI
* @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object)
* DisplayImageOptions.extraForDownloader(Object)}; can be null
* @return {@link InputStream} of image
* @throws FileNotFoundException if the provided URI could not be opened
*/
protected InputStream getStreamFromContent(String imageUri, Object extra) throws FileNotFoundException {
ContentResolver res = context.getContentResolver();
Uri uri = Uri.parse(imageUri);
if (isVideoUri(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 ContactsContract.Contacts.openContactPhotoInputStream(res, uri);
}
return res.openInputStream(uri);
}
/**
* Retrieves {@link InputStream} of image by URI (image is located in assets of application).
*
* @param imageUri Image URI
* @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object)
* DisplayImageOptions.extraForDownloader(Object)}; can be null
* @return {@link InputStream} of image
* @throws IOException if some I/O error occurs file reading
*/
protected InputStream getStreamFromAssets(String imageUri, Object extra) throws IOException {
String filePath = Scheme.ASSETS.crop(imageUri);
return context.getAssets().open(filePath);
}
/**
* Retrieves {@link InputStream} of image by URI (image is located in drawable resources of application).
*
* @param imageUri Image URI
* @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object)
* DisplayImageOptions.extraForDownloader(Object)}; can be null
* @return {@link InputStream} of image
*/
protected InputStream getStreamFromDrawable(String imageUri, Object extra) {
String drawableIdString = Scheme.DRAWABLE.crop(imageUri);
int drawableId = Integer.parseInt(drawableIdString);
return context.getResources().openRawResource(drawableId);
}
/**
* Retrieves {@link InputStream} of image by URI from other source with unsupported scheme. Should be overriden by
* successors to implement image downloading from special sources.<br />
* This method is called only if image URI has unsupported scheme. Throws {@link UnsupportedOperationException} by
* default.
*
* @param imageUri Image URI
* @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object)
* DisplayImageOptions.extraForDownloader(Object)}; can be null
* @return {@link InputStream} of image
* @throws IOException if some I/O error occurs
* @throws UnsupportedOperationException if image URI has unsupported scheme(protocol)
*/
protected InputStream getStreamFromOtherSource(String imageUri, Object extra) throws IOException {
throw new UnsupportedOperationException(String.format(ERROR_UNSUPPORTED_SCHEME, imageUri));
}
private boolean isVideoUri(Uri uri) {
String mimeType = context.getContentResolver().getType(uri);
if (mimeType == null) {
return false;
}
return mimeType.startsWith("video/");
}
}

View File

@ -0,0 +1,90 @@
/*******************************************************************************
* 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 com.nostra13.universalimageloader.core.DisplayImageOptions;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
/**
* Provides retrieving of {@link InputStream} of image by URI.<br />
* Implementations have to be thread-safe.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.4.0
*/
public interface ImageDownloader {
/**
* Retrieves {@link InputStream} of image by URI.
*
* @param imageUri Image URI
* @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object)
* DisplayImageOptions.extraForDownloader(Object)}; can be null
* @return {@link InputStream} of image
* @throws IOException if some I/O error occurs during getting image stream
* @throws UnsupportedOperationException if image URI has unsupported scheme(protocol)
*/
InputStream getStream(String imageUri, Object extra) throws IOException;
/** Represents supported schemes(protocols) of URI. Provides convenient methods for work with schemes and URIs. */
public enum Scheme {
HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
private String scheme;
private String uriPrefix;
Scheme(String scheme) {
this.scheme = scheme;
uriPrefix = scheme + "://";
}
/**
* Defines scheme of incoming URI
*
* @param uri URI for scheme detection
* @return Scheme of incoming URI
*/
public static Scheme ofUri(String uri) {
if (uri != null) {
for (Scheme s : values()) {
if (s.belongsTo(uri)) {
return s;
}
}
}
return UNKNOWN;
}
private boolean belongsTo(String uri) {
return uri.toLowerCase(Locale.US).startsWith(uriPrefix);
}
/** Appends scheme to incoming path */
public String wrap(String path) {
return uriPrefix + path;
}
/** Removed scheme part ("scheme://") from incoming URI */
public String crop(String uri) {
if (!belongsTo(uri)) {
throw new IllegalArgumentException(String.format("URI [%1$s] doesn't have expected scheme [%2$s]", uri, scheme));
}
return uri.substring(uriPrefix.length());
}
}
}

View File

@ -0,0 +1,114 @@
/*******************************************************************************
* 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.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.<br />
* 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.<br />
* 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 <b>NOT</b> return <b>null</b>.
*/
ViewScaleType getScaleType();
/**
* Returns wrapped Android {@link android.view.View View}. Can return <b>null</b> if no view is wrapped or view was
* collected by GC.<br />
* 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.<br />
* Mey be called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread.
*
* @return <b>true</b> - if view is collected by GC and ImageLoader should stop processing this image aware view;
* <b>false</b> - 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.
* <p/>
* 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.<br />
* 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}.<br />
* 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 <b>true</b> if drawable was set successfully; <b>false</b> - otherwise
*/
boolean setImageDrawable(Drawable drawable);
/**
* Sets image bitmap into this image aware view.<br />
* 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 <b>true</b> if bitmap was set successfully; <b>false</b> - otherwise
*/
boolean setImageBitmap(Bitmap bitmap);
}

View File

@ -0,0 +1,143 @@
/*******************************************************************************
* 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.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. <br />
* 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 <b>true</b> - then {@link #getWidth()} and {@link #getHeight()} will check actual
* size of ImageView. It can cause known issues like
* <a href="https://github.com/nostra13/Android-Universal-Image-Loader/issues/376">this</a>.
* But it helps to save memory because memory cache keeps bitmaps of actual (less in
* general) size.
* <p/>
* <b>false</b> - then {@link #getWidth()} and {@link #getHeight()} will <b>NOT</b>
* consider actual size of ImageView, just layout parameters. <br /> 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.
* <p/>
*/
public ImageViewAware(ImageView imageView, boolean checkActualViewSize) {
super(imageView, checkActualViewSize);
}
/**
* {@inheritDoc}
* <br />
* 3) Get <b>maxWidth</b>.
*/
@Override
public int getWidth() {
int width = super.getWidth();
if (width <= 0) {
ImageView imageView = (ImageView) viewRef.get();
if (imageView != null) {
width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check maxWidth parameter
}
}
return width;
}
/**
* {@inheritDoc}
* <br />
* 3) Get <b>maxHeight</b>
*/
@Override
public int getHeight() {
int height = super.getHeight();
if (height <= 0) {
ImageView imageView = (ImageView) viewRef.get();
if (imageView != null) {
height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check maxHeight parameter
}
}
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);
}
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;
}
}

View File

@ -0,0 +1,92 @@
/*******************************************************************************
* 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.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;
}
}

View File

@ -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<View> viewRef;
protected boolean checkActualViewSize;
/**
* Constructor. <br />
* 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 <b>true</b> - then {@link #getWidth()} and {@link #getHeight()} will check actual
* size of View. It can cause known issues like
* <a href="https://github.com/nostra13/Android-Universal-Image-Loader/issues/376">this</a>.
* But it helps to save memory because memory cache keeps bitmaps of actual (less in
* general) size.
* <p/>
* <b>false</b> - then {@link #getWidth()} and {@link #getHeight()} will <b>NOT</b>
* consider actual size of View, just layout parameters. <br /> 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>(view);
this.checkActualViewSize = checkActualViewSize;
}
/**
* {@inheritDoc}
* <p/>
* Width is defined by target {@link android.view.View view} parameters, configuration
* parameters or device display dimensions.<br />
* Size computing algorithm (go by steps until get non-zero value):<br />
* 1) Get the actual drawn <b>getWidth()</b> of the View<br />
* 2) Get <b>layout_width</b>
*/
@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}
* <p/>
* Height is defined by target {@link android.view.View view} parameters, configuration
* parameters or device display dimensions.<br />
* Size computing algorithm (go by steps until get non-zero value):<br />
* 1) Get the actual drawn <b>getHeight()</b> of the View<br />
* 2) Get <b>layout_height</b>
*/
@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.<br />
* 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);
}

View File

@ -0,0 +1,67 @@
/*******************************************************************************
* 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.listener;
import android.graphics.Bitmap;
import android.view.View;
import com.nostra13.universalimageloader.core.assist.FailReason;
/**
* Listener for image loading process.<br />
* You can use {@link SimpleImageLoadingListener} for implementing only needed methods.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @see SimpleImageLoadingListener
* @see com.nostra13.universalimageloader.core.assist.FailReason
* @since 1.0.0
*/
public interface ImageLoadingListener {
/**
* Is called when image loading task was started
*
* @param imageUri Loading image URI
* @param view View for image
*/
void onLoadingStarted(String imageUri, View view);
/**
* Is called when an error was occurred during image loading
*
* @param imageUri Loading image URI
* @param view View for image. Can be <b>null</b>.
* @param failReason {@linkplain com.nostra13.universalimageloader.core.assist.FailReason The reason} why image
* loading was failed
*/
void onLoadingFailed(String imageUri, View view, FailReason failReason);
/**
* 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. Can be <b>null</b>.
* @param loadedImage Bitmap of loaded and decoded image
*/
void onLoadingComplete(String imageUri, View view, Bitmap loadedImage);
/**
* 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. Can be <b>null</b>.
*/
void onLoadingCancelled(String imageUri, View view);
}

View File

@ -0,0 +1,37 @@
/*******************************************************************************
* 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.listener;
import android.view.View;
/**
* Listener for image loading progress.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.9.1
*/
public interface ImageLoadingProgressListener {
/**
* Is called when image loading progress changed.
*
* @param imageUri Image URI
* @param view View for image. Can be <b>null</b>.
* @param current Downloaded size in bytes
* @param total Total size in bytes
*/
void onProgressUpdate(String imageUri, View view, int current, int total);
}

View File

@ -0,0 +1,98 @@
/*******************************************************************************
* 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.listener;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.GridView;
import android.widget.ListView;
import com.nostra13.universalimageloader.core.ImageLoader;
/**
* Listener-helper for {@linkplain AbsListView list views} ({@link ListView}, {@link GridView}) which can
* {@linkplain ImageLoader#pause() pause ImageLoader's tasks} while list view is scrolling (touch scrolling and/or
* fling). It prevents redundant loadings.<br />
* Set it to your list view's {@link AbsListView#setOnScrollListener(OnScrollListener) setOnScrollListener(...)}.<br />
* This listener can wrap your custom {@linkplain OnScrollListener listener}.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.7.0
*/
public class PauseOnScrollListener implements OnScrollListener {
private ImageLoader imageLoader;
private final boolean pauseOnScroll;
private final boolean pauseOnFling;
private final OnScrollListener externalListener;
/**
* Constructor
*
* @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
*/
public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling) {
this(imageLoader, pauseOnScroll, pauseOnFling, null);
}
/**
* Constructor
*
* @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
*/
public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling,
OnScrollListener customListener) {
this.imageLoader = imageLoader;
this.pauseOnScroll = pauseOnScroll;
this.pauseOnFling = pauseOnFling;
externalListener = customListener;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_IDLE:
imageLoader.resume();
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
if (pauseOnScroll) {
imageLoader.pause();
}
break;
case OnScrollListener.SCROLL_STATE_FLING:
if (pauseOnFling) {
imageLoader.pause();
}
break;
}
if (externalListener != null) {
externalListener.onScrollStateChanged(view, scrollState);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (externalListener != null) {
externalListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
}
}

View File

@ -0,0 +1,50 @@
/*******************************************************************************
* 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.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 com.nostra13.universalimageloader.core.listener.ImageLoadingListener} but does
* nothing.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.4.0
*/
public class SimpleImageLoadingListener implements ImageLoadingListener {
@Override
public void onLoadingStarted(String imageUri, View view) {
// Empty implementation
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
// Empty implementation
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
// Empty implementation
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
// Empty implementation
}
}

View File

@ -0,0 +1,40 @@
/*******************************************************************************
* 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.process;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
/**
* Makes some processing on {@link Bitmap}. Implementations can apply any changes to original {@link Bitmap}.<br />
* Implementations have to be thread-safe.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.8.0
*/
public interface BitmapProcessor {
/**
* Makes some processing of incoming bitmap.<br />
* This method is executing on additional thread (not on UI thread).<br />
* <b>Note:</b> If this processor is used as {@linkplain DisplayImageOptions.Builder#preProcessor(BitmapProcessor)
* pre-processor} then don't forget {@linkplain Bitmap#recycle() to recycle} incoming bitmap if you return a new
* created one.
*
* @param bitmap Original {@linkplain Bitmap bitmap}
* @return Processed {@linkplain Bitmap bitmap}
*/
Bitmap process(Bitmap bitmap);
}

View File

@ -0,0 +1,49 @@
/*******************************************************************************
* 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.utils;
import com.nostra13.universalimageloader.cache.disc.DiskCache;
import java.io.File;
/**
* Utility for convenient work with disk cache.<br />
* <b>NOTE:</b> 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 DiskCacheUtils {
private DiskCacheUtils() {
}
/** Returns {@link File} of cached image or <b>null</b> 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 disk cache (if image was cached in disk cache before)
*
* @return <b>true</b> - if cached image file existed and was deleted; <b>false</b> - otherwise.
*/
public static boolean removeFromCache(String imageUri, DiskCache diskCache) {
File image = diskCache.get(imageUri);
return image != null && image.exists() && image.delete();
}
}

View File

@ -0,0 +1,215 @@
/*******************************************************************************
* 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.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.<br />
*/
public static ImageSize defineTargetSizeForView(ImageAware imageAware, ImageSize maxImageSize) {
int width = imageAware.getWidth();
if (width <= 0) width = maxImageSize.getWidth();
int height = imageAware.getHeight();
if (height <= 0) height = maxImageSize.getHeight();
return new ImageSize(width, height);
}
/**
* Computes sample size for downscaling image size (<b>srcSize</b>) to view size (<b>targetSize</b>). This sample
* size is used during
* {@linkplain BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options)
* decoding image} to bitmap.<br />
* <br />
* <b>Examples:</b><br />
* <p/>
* <pre>
* 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
* </pre>
* <p/>
* <br />
* 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 <i>true</i> - 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.<br />
* 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 (<b>targetSize</b>) to source size (<b>srcSize</b>).<br />
* <br />
* <b>Examples:</b><br />
* <p/>
* <pre>
* 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
* </pre>
*
* @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 <b>false</b>
* 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;
}
}

View File

@ -0,0 +1,132 @@
/*******************************************************************************
* 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.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 <b>true</b> - if stream copied successfully; <b>false</b> - 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 <b>bufferSize</b> bytes
* @return <b>true</b> - if stream copied successfully; <b>false</b> - 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 e) {
// Do nothing
} finally {
closeSilently(is);
}
}
public static void closeSilently(Closeable closeable) {
try {
closeable.close();
} catch (Exception e) {
// Do nothing
}
}
/** Listener and controller for copy process */
public static interface CopyListener {
/**
* @param current Loaded bytes
* @param total Total bytes for loading
* @return <b>true</b> - if copying should be continued; <b>false</b> - if copying should be interrupted
*/
boolean onBytesCopied(int current, int total);
}
}

View File

@ -0,0 +1,113 @@
/*******************************************************************************
* 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.utils;
import android.util.Log;
import com.nostra13.universalimageloader.core.ImageLoader;
/**
* "Less-word" analog of Android {@link android.util.Log logger}
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.6.4
*/
public final class L {
private static final String LOG_FORMAT = "%1$s\n%2$s";
private static volatile boolean writeDebugLogs = false;
private static volatile boolean writeLogs = true;
private L() {
}
/**
* Enables logger (if {@link #disableLogging()} was called before)
*
* @deprecated Use {@link #writeLogs(boolean) writeLogs(true)} instead
*/
@Deprecated
public static void enableLogging() {
writeLogs(true);
}
/**
* 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() {
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)<br />
* 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) {
if (writeDebugLogs) {
log(Log.DEBUG, null, message, args);
}
}
public static void i(String message, Object... args) {
log(Log.INFO, null, message, args);
}
public static void w(String message, Object... args) {
log(Log.WARN, null, message, args);
}
public static void e(Throwable ex) {
log(Log.ERROR, ex, null);
}
public static void e(String message, Object... args) {
log(Log.ERROR, null, message, args);
}
public static void e(Throwable ex, String message, Object... args) {
log(Log.ERROR, ex, message, args);
}
private static void log(int priority, Throwable ex, String message, Object... args) {
if (!writeLogs) return;
if (args.length > 0) {
message = String.format(message, args);
}
String log;
if (ex == null) {
log = message;
} else {
String logMessage = message == null ? ex.getMessage() : message;
String logBody = Log.getStackTraceString(ex);
log = String.format(LOG_FORMAT, logMessage, logBody);
}
Log.println(priority, ImageLoader.TAG, log);
}
}

View File

@ -0,0 +1,110 @@
/*******************************************************************************
* 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.utils;
import android.graphics.Bitmap;
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;
import java.util.List;
/**
* Utility for generating of keys for memory cache, key comparing and other work with memory cache
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.6.3
*/
public final class MemoryCacheUtils {
private static final String URI_AND_SIZE_SEPARATOR = "_";
private static final String WIDTH_AND_HEIGHT_SEPARATOR = "x";
private MemoryCacheUtils() {
}
/**
* Generates key for memory cache for incoming image (URI + size).<br />
* Pattern for cache key - <b>[imageUri]_[width]x[height]</b>.
*/
public static String generateKey(String imageUri, ImageSize targetSize) {
return new StringBuilder(imageUri).append(URI_AND_SIZE_SEPARATOR).append(targetSize.getWidth()).append(WIDTH_AND_HEIGHT_SEPARATOR).append(targetSize.getHeight()).toString();
}
public static Comparator<String> createFuzzyKeyComparator() {
return new Comparator<String>() {
@Override
public int compare(String key1, String key2) {
String imageUri1 = key1.substring(0, key1.lastIndexOf(URI_AND_SIZE_SEPARATOR));
String imageUri2 = key2.substring(0, key2.lastIndexOf(URI_AND_SIZE_SEPARATOR));
return imageUri1.compareTo(imageUri2);
}
};
}
/**
* Searches all bitmaps in memory cache which are corresponded to incoming URI.<br />
* <b>Note:</b> Memory cache can contain multiple sizes of the same image if only you didn't set
* {@link ImageLoaderConfiguration.Builder#denyCacheImageMultipleSizesInMemory()
* denyCacheImageMultipleSizesInMemory()} option in {@linkplain ImageLoaderConfiguration configuration}
*/
public static List<Bitmap> findCachedBitmapsForImageUri(String imageUri, MemoryCache memoryCache) {
List<Bitmap> values = new ArrayList<Bitmap>();
for (String key : memoryCache.keys()) {
if (key.startsWith(imageUri)) {
values.add(memoryCache.get(key));
}
}
return values;
}
/**
* Searches all keys in memory cache which are corresponded to incoming URI.<br />
* <b>Note:</b> Memory cache can contain multiple sizes of the same image if only you didn't set
* {@link ImageLoaderConfiguration.Builder#denyCacheImageMultipleSizesInMemory()
* denyCacheImageMultipleSizesInMemory()} option in {@linkplain ImageLoaderConfiguration configuration}
*/
public static List<String> findCacheKeysForImageUri(String imageUri, MemoryCache memoryCache) {
List<String> values = new ArrayList<String>();
for (String key : memoryCache.keys()) {
if (key.startsWith(imageUri)) {
values.add(key);
}
}
return values;
}
/**
* Removes from memory cache all images for incoming URI.<br />
* <b>Note:</b> Memory cache can contain multiple sizes of the same image if only you didn't set
* {@link ImageLoaderConfiguration.Builder#denyCacheImageMultipleSizesInMemory()
* denyCacheImageMultipleSizesInMemory()} option in {@linkplain ImageLoaderConfiguration configuration}
*/
public static void removeFromCache(String imageUri, MemoryCache memoryCache) {
List<String> keysToRemove = new ArrayList<String>();
for (String key : memoryCache.keys()) {
if (key.startsWith(imageUri)) {
keysToRemove.add(key);
}
}
for (String keyToRemove : keysToRemove) {
memoryCache.remove(keyToRemove);
}
}
}

View File

@ -0,0 +1,147 @@
/*******************************************************************************
* 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.utils;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import java.io.File;
import java.io.IOException;
import static android.os.Environment.MEDIA_MOUNTED;
/**
* Provides application storage paths
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.0.0
*/
public final class StorageUtils {
private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE";
private static final String INDIVIDUAL_DIR_NAME = "uil-images";
private StorageUtils() {
}
/**
* Returns application cache directory. Cache directory will be created on SD card
* <i>("/Android/data/[app_package_name]/cache")</i> if card is mounted and app has appropriate permission. Else -
* Android defines cache directory on device's file system.
*
* @param context Application context
* @return Cache {@link File directory}.<br />
* <b>NOTE:</b> 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
* <i>("/Android/data/[app_package_name]/cache")</i> (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}.<br />
* <b>NOTE:</b> 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;
String externalStorageState;
try {
externalStorageState = Environment.getExternalStorageState();
} catch (NullPointerException e) { // (sh)it happens (Issue #660)
externalStorageState = "";
}
if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState) && hasExternalStoragePermission(context)) {
appCacheDir = getExternalCacheDir(context);
}
if (appCacheDir == null) {
appCacheDir = context.getCacheDir();
}
if (appCacheDir == null) {
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;
}
/**
* Returns individual application cache directory (for only image caching from ImageLoader). Cache directory will be
* created on SD card <i>("/Android/data/[app_package_name]/cache/uil-images")</i> if card is mounted and app has
* appropriate permission. Else - Android defines cache directory on device's file system.
*
* @param context Application context
* @return Cache {@link File directory}
*/
public static File getIndividualCacheDirectory(Context context) {
File cacheDir = getCacheDirectory(context);
File individualCacheDir = new File(cacheDir, INDIVIDUAL_DIR_NAME);
if (!individualCacheDir.exists()) {
if (!individualCacheDir.mkdir()) {
individualCacheDir = cacheDir;
}
}
return individualCacheDir;
}
/**
* 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) {
File appCacheDir = null;
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;
}
private static File getExternalCacheDir(Context context) {
File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
if (!appCacheDir.exists()) {
if (!appCacheDir.mkdirs()) {
L.w("Unable to create external cache directory");
return null;
}
try {
new File(appCacheDir, ".nomedia").createNewFile();
} catch (IOException e) {
L.i("Can't create \".nomedia\" file in application external cache directory");
}
}
return appCacheDir;
}
private static boolean hasExternalStoragePermission(Context context) {
int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION);
return perm == PackageManager.PERMISSION_GRANTED;
}
}