Showing
73 changed files
with
11823 additions
and
0 deletions
uil/.gitignore
0 → 100644
uil/build.gradle
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (c) 2016. wugian | |
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 | + * you may not use this file except in compliance with the License. | |
| 5 | + * You may obtain a copy of the License at | |
| 6 | + * | |
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | + * | |
| 9 | + * Unless required by applicable law or agreed to in writing, software | |
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 | + * See the License for the specific language governing permissions and | |
| 13 | + * limitations under the License. | |
| 14 | + * | |
| 15 | + */ | |
| 16 | + | |
| 17 | +apply plugin: 'com.android.library' | |
| 18 | + | |
| 19 | +android { | |
| 20 | + compileSdkVersion 25 | |
| 21 | + buildToolsVersion "25.0.0" | |
| 22 | + | |
| 23 | + defaultConfig { | |
| 24 | + minSdkVersion 18 | |
| 25 | + targetSdkVersion 25 | |
| 26 | + versionCode 1 | |
| 27 | + versionName "1.0" | |
| 28 | + | |
| 29 | + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | |
| 30 | + | |
| 31 | + } | |
| 32 | + buildTypes { | |
| 33 | + release { | |
| 34 | + minifyEnabled false | |
| 35 | + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | |
| 36 | + } | |
| 37 | + } | |
| 38 | +} | |
| 39 | + | |
| 40 | +dependencies { | |
| 41 | + compile fileTree(dir: 'libs', include: ['*.jar']) | |
| 42 | +} | ... | ... |
uil/proguard-rules.pro
0 → 100644
| 1 | +# Add project specific ProGuard rules here. | |
| 2 | +# By default, the flags in this file are appended to flags specified | |
| 3 | +# in D:\develope-file\android-sdk/tools/proguard/proguard-android.txt | |
| 4 | +# You can edit the include path and order by changing the proguardFiles | |
| 5 | +# directive in build.gradle. | |
| 6 | +# | |
| 7 | +# For more details, see | |
| 8 | +# http://developer.android.com/guide/developing/tools/proguard.html | |
| 9 | + | |
| 10 | +# Add any project specific keep options here: | |
| 11 | + | |
| 12 | +# If your project uses WebView with JS, uncomment the following | |
| 13 | +# and specify the fully qualified class name to the JavaScript interface | |
| 14 | +# class: | |
| 15 | +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | |
| 16 | +# public *; | |
| 17 | +#} | ... | ... |
uil/src/main/AndroidManifest.xml
0 → 100644
| 1 | +<!-- | |
| 2 | + ~ Copyright (c) 2016. wugian | |
| 3 | + ~ Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 | + ~ you may not use this file except in compliance with the License. | |
| 5 | + ~ You may obtain a copy of the License at | |
| 6 | + ~ | |
| 7 | + ~ http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | + ~ | |
| 9 | + ~ Unless required by applicable law or agreed to in writing, software | |
| 10 | + ~ distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 | + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 | + ~ See the License for the specific language governing permissions and | |
| 13 | + ~ limitations under the License. | |
| 14 | + ~ | |
| 15 | + --> | |
| 16 | + | |
| 17 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
| 18 | + package="com.nostra13.universalimageloader"> | |
| 19 | + | |
| 20 | + <application android:allowBackup="true" | |
| 21 | + android:supportsRtl="true" | |
| 22 | + > | |
| 23 | + | |
| 24 | + </application> | |
| 25 | + | |
| 26 | +</manifest> | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.utils.IoUtils; | |
| 20 | + | |
| 21 | +import java.io.File; | |
| 22 | +import java.io.IOException; | |
| 23 | +import java.io.InputStream; | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * Interface for disk cache | |
| 27 | + * | |
| 28 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 29 | + * @since 1.9.2 | |
| 30 | + */ | |
| 31 | +public interface DiskCache { | |
| 32 | + /** | |
| 33 | + * Returns root directory of disk cache | |
| 34 | + * | |
| 35 | + * @return Root directory of disk cache | |
| 36 | + */ | |
| 37 | + File getDirectory(); | |
| 38 | + | |
| 39 | + /** | |
| 40 | + * Returns file of cached image | |
| 41 | + * | |
| 42 | + * @param imageUri Original image URI | |
| 43 | + * @return File of cached image or <b>null</b> if image wasn't cached | |
| 44 | + */ | |
| 45 | + File get(String imageUri); | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * Saves image stream in disk cache. | |
| 49 | + * Incoming image stream shouldn't be closed in this method. | |
| 50 | + * | |
| 51 | + * @param imageUri Original image URI | |
| 52 | + * @param imageStream Input stream of image (shouldn't be closed in this method) | |
| 53 | + * @param listener Listener for saving progress, can be ignored if you don't use | |
| 54 | + * {@linkplain com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener | |
| 55 | + * progress listener} in ImageLoader calls | |
| 56 | + * @return <b>true</b> - if image was saved successfully; <b>false</b> - if image wasn't saved in disk cache. | |
| 57 | + * @throws IOException | |
| 58 | + */ | |
| 59 | + boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException; | |
| 60 | + | |
| 61 | + /** | |
| 62 | + * Saves image bitmap in disk cache. | |
| 63 | + * | |
| 64 | + * @param imageUri Original image URI | |
| 65 | + * @param bitmap Image bitmap | |
| 66 | + * @return <b>true</b> - if bitmap was saved successfully; <b>false</b> - if bitmap wasn't saved in disk cache. | |
| 67 | + * @throws IOException | |
| 68 | + */ | |
| 69 | + boolean save(String imageUri, Bitmap bitmap) throws IOException; | |
| 70 | + | |
| 71 | + /** | |
| 72 | + * Removes image file associated with incoming URI | |
| 73 | + * | |
| 74 | + * @param imageUri Image URI | |
| 75 | + * @return <b>true</b> - if image file is deleted successfully; <b>false</b> - if image file doesn't exist for | |
| 76 | + * incoming URI or image file can't be deleted. | |
| 77 | + */ | |
| 78 | + boolean remove(String imageUri); | |
| 79 | + | |
| 80 | + /** Closes disk cache, releases resources. */ | |
| 81 | + void close(); | |
| 82 | + | |
| 83 | + /** Clears disk cache. */ | |
| 84 | + void clear(); | |
| 85 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc.impl; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.cache.disc.DiskCache; | |
| 20 | +import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; | |
| 21 | +import com.nostra13.universalimageloader.core.DefaultConfigurationFactory; | |
| 22 | +import com.nostra13.universalimageloader.utils.IoUtils; | |
| 23 | + | |
| 24 | +import java.io.BufferedOutputStream; | |
| 25 | +import java.io.File; | |
| 26 | +import java.io.FileOutputStream; | |
| 27 | +import java.io.IOException; | |
| 28 | +import java.io.InputStream; | |
| 29 | +import java.io.OutputStream; | |
| 30 | + | |
| 31 | +/** | |
| 32 | + * Base disk cache. | |
| 33 | + * | |
| 34 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 35 | + * @see FileNameGenerator | |
| 36 | + * @since 1.0.0 | |
| 37 | + */ | |
| 38 | +public abstract class BaseDiskCache implements DiskCache { | |
| 39 | + /** {@value */ | |
| 40 | + public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 Kb | |
| 41 | + /** {@value */ | |
| 42 | + public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG; | |
| 43 | + /** {@value */ | |
| 44 | + public static final int DEFAULT_COMPRESS_QUALITY = 100; | |
| 45 | + | |
| 46 | + private static final String ERROR_ARG_NULL = " argument must be not null"; | |
| 47 | + private static final String TEMP_IMAGE_POSTFIX = ".tmp"; | |
| 48 | + | |
| 49 | + protected final File cacheDir; | |
| 50 | + protected final File reserveCacheDir; | |
| 51 | + | |
| 52 | + protected final FileNameGenerator fileNameGenerator; | |
| 53 | + | |
| 54 | + protected int bufferSize = DEFAULT_BUFFER_SIZE; | |
| 55 | + | |
| 56 | + protected Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT; | |
| 57 | + protected int compressQuality = DEFAULT_COMPRESS_QUALITY; | |
| 58 | + | |
| 59 | + /** @param cacheDir Directory for file caching */ | |
| 60 | + public BaseDiskCache(File cacheDir) { | |
| 61 | + this(cacheDir, null); | |
| 62 | + } | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * @param cacheDir Directory for file caching | |
| 66 | + * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available. | |
| 67 | + */ | |
| 68 | + public BaseDiskCache(File cacheDir, File reserveCacheDir) { | |
| 69 | + this(cacheDir, reserveCacheDir, DefaultConfigurationFactory.createFileNameGenerator()); | |
| 70 | + } | |
| 71 | + | |
| 72 | + /** | |
| 73 | + * @param cacheDir Directory for file caching | |
| 74 | + * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available. | |
| 75 | + * @param fileNameGenerator {@linkplain FileNameGenerator | |
| 76 | + * Name generator} for cached files | |
| 77 | + */ | |
| 78 | + public BaseDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) { | |
| 79 | + if (cacheDir == null) { | |
| 80 | + throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL); | |
| 81 | + } | |
| 82 | + if (fileNameGenerator == null) { | |
| 83 | + throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL); | |
| 84 | + } | |
| 85 | + | |
| 86 | + this.cacheDir = cacheDir; | |
| 87 | + this.reserveCacheDir = reserveCacheDir; | |
| 88 | + this.fileNameGenerator = fileNameGenerator; | |
| 89 | + } | |
| 90 | + | |
| 91 | + @Override | |
| 92 | + public File getDirectory() { | |
| 93 | + return cacheDir; | |
| 94 | + } | |
| 95 | + | |
| 96 | + @Override | |
| 97 | + public File get(String imageUri) { | |
| 98 | + return getFile(imageUri); | |
| 99 | + } | |
| 100 | + | |
| 101 | + @Override | |
| 102 | + public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException { | |
| 103 | + File imageFile = getFile(imageUri); | |
| 104 | + File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX); | |
| 105 | + boolean loaded = false; | |
| 106 | + try { | |
| 107 | + OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize); | |
| 108 | + try { | |
| 109 | + loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize); | |
| 110 | + } finally { | |
| 111 | + IoUtils.closeSilently(os); | |
| 112 | + } | |
| 113 | + } finally { | |
| 114 | + if (loaded && !tmpFile.renameTo(imageFile)) { | |
| 115 | + loaded = false; | |
| 116 | + } | |
| 117 | + if (!loaded) { | |
| 118 | + tmpFile.delete(); | |
| 119 | + } | |
| 120 | + } | |
| 121 | + return loaded; | |
| 122 | + } | |
| 123 | + | |
| 124 | + @Override | |
| 125 | + public boolean save(String imageUri, Bitmap bitmap) throws IOException { | |
| 126 | + File imageFile = getFile(imageUri); | |
| 127 | + File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX); | |
| 128 | + OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize); | |
| 129 | + boolean savedSuccessfully = false; | |
| 130 | + try { | |
| 131 | + savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os); | |
| 132 | + } finally { | |
| 133 | + IoUtils.closeSilently(os); | |
| 134 | + if (savedSuccessfully && !tmpFile.renameTo(imageFile)) { | |
| 135 | + savedSuccessfully = false; | |
| 136 | + } | |
| 137 | + if (!savedSuccessfully) { | |
| 138 | + tmpFile.delete(); | |
| 139 | + } | |
| 140 | + } | |
| 141 | + bitmap.recycle(); | |
| 142 | + return savedSuccessfully; | |
| 143 | + } | |
| 144 | + | |
| 145 | + @Override | |
| 146 | + public boolean remove(String imageUri) { | |
| 147 | + return getFile(imageUri).delete(); | |
| 148 | + } | |
| 149 | + | |
| 150 | + @Override | |
| 151 | + public void close() { | |
| 152 | + // Nothing to do | |
| 153 | + } | |
| 154 | + | |
| 155 | + @Override | |
| 156 | + public void clear() { | |
| 157 | + File[] files = cacheDir.listFiles(); | |
| 158 | + if (files != null) { | |
| 159 | + for (File f : files) { | |
| 160 | + f.delete(); | |
| 161 | + } | |
| 162 | + } | |
| 163 | + } | |
| 164 | + | |
| 165 | + /** Returns file object (not null) for incoming image URI. File object can reference to non-existing file. */ | |
| 166 | + protected File getFile(String imageUri) { | |
| 167 | + String fileName = fileNameGenerator.generate(imageUri); | |
| 168 | + File dir = cacheDir; | |
| 169 | + if (!cacheDir.exists() && !cacheDir.mkdirs()) { | |
| 170 | + if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) { | |
| 171 | + dir = reserveCacheDir; | |
| 172 | + } | |
| 173 | + } | |
| 174 | + return new File(dir, fileName); | |
| 175 | + } | |
| 176 | + | |
| 177 | + public void setBufferSize(int bufferSize) { | |
| 178 | + this.bufferSize = bufferSize; | |
| 179 | + } | |
| 180 | + | |
| 181 | + public void setCompressFormat(Bitmap.CompressFormat compressFormat) { | |
| 182 | + this.compressFormat = compressFormat; | |
| 183 | + } | |
| 184 | + | |
| 185 | + public void setCompressQuality(int compressQuality) { | |
| 186 | + this.compressQuality = compressQuality; | |
| 187 | + } | |
| 188 | +} | |
| \ No newline at end of file | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/LimitedAgeDiskCache.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc.impl; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; | |
| 20 | +import com.nostra13.universalimageloader.core.DefaultConfigurationFactory; | |
| 21 | +import com.nostra13.universalimageloader.utils.IoUtils; | |
| 22 | + | |
| 23 | +import java.io.File; | |
| 24 | +import java.io.IOException; | |
| 25 | +import java.io.InputStream; | |
| 26 | +import java.util.Collections; | |
| 27 | +import java.util.HashMap; | |
| 28 | +import java.util.Map; | |
| 29 | + | |
| 30 | +/** | |
| 31 | + * Cache which deletes files which were loaded more than defined time. Cache size is unlimited. | |
| 32 | + * | |
| 33 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 34 | + * @since 1.3.1 | |
| 35 | + */ | |
| 36 | +public class LimitedAgeDiskCache extends BaseDiskCache { | |
| 37 | + | |
| 38 | + private final long maxFileAge; | |
| 39 | + | |
| 40 | + private final Map<File, Long> loadingDates = Collections.synchronizedMap(new HashMap<File, Long>()); | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * @param cacheDir Directory for file caching | |
| 44 | + * @param maxAge Max file age (in seconds). If file age will exceed this value then it'll be removed on next | |
| 45 | + * treatment (and therefore be reloaded). | |
| 46 | + */ | |
| 47 | + public LimitedAgeDiskCache(File cacheDir, long maxAge) { | |
| 48 | + this(cacheDir, null, DefaultConfigurationFactory.createFileNameGenerator(), maxAge); | |
| 49 | + } | |
| 50 | + | |
| 51 | + /** | |
| 52 | + * @param cacheDir Directory for file caching | |
| 53 | + * @param maxAge Max file age (in seconds). If file age will exceed this value then it'll be removed on next | |
| 54 | + * treatment (and therefore be reloaded). | |
| 55 | + */ | |
| 56 | + public LimitedAgeDiskCache(File cacheDir, File reserveCacheDir, long maxAge) { | |
| 57 | + this(cacheDir, reserveCacheDir, DefaultConfigurationFactory.createFileNameGenerator(), maxAge); | |
| 58 | + } | |
| 59 | + | |
| 60 | + /** | |
| 61 | + * @param cacheDir Directory for file caching | |
| 62 | + * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available. | |
| 63 | + * @param fileNameGenerator Name generator for cached files | |
| 64 | + * @param maxAge Max file age (in seconds). If file age will exceed this value then it'll be removed on next | |
| 65 | + * treatment (and therefore be reloaded). | |
| 66 | + */ | |
| 67 | + public LimitedAgeDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator, long maxAge) { | |
| 68 | + super(cacheDir, reserveCacheDir, fileNameGenerator); | |
| 69 | + this.maxFileAge = maxAge * 1000; // to milliseconds | |
| 70 | + } | |
| 71 | + | |
| 72 | + @Override | |
| 73 | + public File get(String imageUri) { | |
| 74 | + File file = super.get(imageUri); | |
| 75 | + if (file != null && file.exists()) { | |
| 76 | + boolean cached; | |
| 77 | + Long loadingDate = loadingDates.get(file); | |
| 78 | + if (loadingDate == null) { | |
| 79 | + cached = false; | |
| 80 | + loadingDate = file.lastModified(); | |
| 81 | + } else { | |
| 82 | + cached = true; | |
| 83 | + } | |
| 84 | + | |
| 85 | + if (System.currentTimeMillis() - loadingDate > maxFileAge) { | |
| 86 | + file.delete(); | |
| 87 | + loadingDates.remove(file); | |
| 88 | + } else if (!cached) { | |
| 89 | + loadingDates.put(file, loadingDate); | |
| 90 | + } | |
| 91 | + } | |
| 92 | + return file; | |
| 93 | + } | |
| 94 | + | |
| 95 | + @Override | |
| 96 | + public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException { | |
| 97 | + boolean saved = super.save(imageUri, imageStream, listener); | |
| 98 | + rememberUsage(imageUri); | |
| 99 | + return saved; | |
| 100 | + } | |
| 101 | + | |
| 102 | + @Override | |
| 103 | + public boolean save(String imageUri, Bitmap bitmap) throws IOException { | |
| 104 | + boolean saved = super.save(imageUri, bitmap); | |
| 105 | + rememberUsage(imageUri); | |
| 106 | + return saved; | |
| 107 | + } | |
| 108 | + | |
| 109 | + @Override | |
| 110 | + public boolean remove(String imageUri) { | |
| 111 | + loadingDates.remove(getFile(imageUri)); | |
| 112 | + return super.remove(imageUri); | |
| 113 | + } | |
| 114 | + | |
| 115 | + @Override | |
| 116 | + public void clear() { | |
| 117 | + super.clear(); | |
| 118 | + loadingDates.clear(); | |
| 119 | + } | |
| 120 | + | |
| 121 | + private void rememberUsage(String imageUri) { | |
| 122 | + File file = getFile(imageUri); | |
| 123 | + long currentTime = System.currentTimeMillis(); | |
| 124 | + file.setLastModified(currentTime); | |
| 125 | + loadingDates.put(file, currentTime); | |
| 126 | + } | |
| 127 | +} | |
| \ No newline at end of file | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/UnlimitedDiskCache.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc.impl; | |
| 17 | + | |
| 18 | +import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; | |
| 19 | + | |
| 20 | +import java.io.File; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * Default implementation of {@linkplain com.nostra13.universalimageloader.cache.disc.DiskCache disk cache}. | |
| 24 | + * Cache size is unlimited. | |
| 25 | + * | |
| 26 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 27 | + * @since 1.0.0 | |
| 28 | + */ | |
| 29 | +public class UnlimitedDiskCache extends BaseDiskCache { | |
| 30 | + /** @param cacheDir Directory for file caching */ | |
| 31 | + public UnlimitedDiskCache(File cacheDir) { | |
| 32 | + super(cacheDir); | |
| 33 | + } | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * @param cacheDir Directory for file caching | |
| 37 | + * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available. | |
| 38 | + */ | |
| 39 | + public UnlimitedDiskCache(File cacheDir, File reserveCacheDir) { | |
| 40 | + super(cacheDir, reserveCacheDir); | |
| 41 | + } | |
| 42 | + | |
| 43 | + /** | |
| 44 | + * @param cacheDir Directory for file caching | |
| 45 | + * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available. | |
| 46 | + * @param fileNameGenerator {@linkplain FileNameGenerator | |
| 47 | + * Name generator} for cached files | |
| 48 | + */ | |
| 49 | + public UnlimitedDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) { | |
| 50 | + super(cacheDir, reserveCacheDir, fileNameGenerator); | |
| 51 | + } | |
| 52 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/DiskLruCache.java
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (C) 2011 The Android Open Source Project | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc.impl.ext; | |
| 17 | + | |
| 18 | +import java.io.BufferedWriter; | |
| 19 | +import java.io.Closeable; | |
| 20 | +import java.io.EOFException; | |
| 21 | +import java.io.File; | |
| 22 | +import java.io.FileInputStream; | |
| 23 | +import java.io.FileNotFoundException; | |
| 24 | +import java.io.FileOutputStream; | |
| 25 | +import java.io.FilterOutputStream; | |
| 26 | +import java.io.IOException; | |
| 27 | +import java.io.InputStream; | |
| 28 | +import java.io.InputStreamReader; | |
| 29 | +import java.io.OutputStream; | |
| 30 | +import java.io.OutputStreamWriter; | |
| 31 | +import java.io.Writer; | |
| 32 | +import java.util.ArrayList; | |
| 33 | +import java.util.Iterator; | |
| 34 | +import java.util.LinkedHashMap; | |
| 35 | +import java.util.Map; | |
| 36 | +import java.util.concurrent.Callable; | |
| 37 | +import java.util.concurrent.LinkedBlockingQueue; | |
| 38 | +import java.util.concurrent.ThreadPoolExecutor; | |
| 39 | +import java.util.concurrent.TimeUnit; | |
| 40 | +import java.util.regex.Matcher; | |
| 41 | +import java.util.regex.Pattern; | |
| 42 | + | |
| 43 | +/** | |
| 44 | + * A cache that uses a bounded amount of space on a filesystem. Each cache | |
| 45 | + * entry has a string key and a fixed number of values. Each key must match | |
| 46 | + * the regex <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences, | |
| 47 | + * accessible as streams or files. Each value must be between {@code 0} and | |
| 48 | + * {@code Integer.MAX_VALUE} bytes in length. | |
| 49 | + * | |
| 50 | + * <p>The cache stores its data in a directory on the filesystem. This | |
| 51 | + * directory must be exclusive to the cache; the cache may delete or overwrite | |
| 52 | + * files from its directory. It is an error for multiple processes to use the | |
| 53 | + * same cache directory at the same time. | |
| 54 | + * | |
| 55 | + * <p>This cache limits the number of bytes that it will store on the | |
| 56 | + * filesystem. When the number of stored bytes exceeds the limit, the cache will | |
| 57 | + * remove entries in the background until the limit is satisfied. The limit is | |
| 58 | + * not strict: the cache may temporarily exceed it while waiting for files to be | |
| 59 | + * deleted. The limit does not include filesystem overhead or the cache | |
| 60 | + * journal so space-sensitive applications should set a conservative limit. | |
| 61 | + * | |
| 62 | + * <p>Clients call {@link #edit} to create or update the values of an entry. An | |
| 63 | + * entry may have only one editor at one time; if a value is not available to be | |
| 64 | + * edited then {@link #edit} will return null. | |
| 65 | + * <ul> | |
| 66 | + * <li>When an entry is being <strong>created</strong> it is necessary to | |
| 67 | + * supply a full set of values; the empty value should be used as a | |
| 68 | + * placeholder if necessary. | |
| 69 | + * <li>When an entry is being <strong>edited</strong>, it is not necessary | |
| 70 | + * to supply data for every value; values default to their previous | |
| 71 | + * value. | |
| 72 | + * </ul> | |
| 73 | + * Every {@link #edit} call must be matched by a call to {@link Editor#commit} | |
| 74 | + * or {@link Editor#abort}. Committing is atomic: a read observes the full set | |
| 75 | + * of values as they were before or after the commit, but never a mix of values. | |
| 76 | + * | |
| 77 | + * <p>Clients call {@link #get} to read a snapshot of an entry. The read will | |
| 78 | + * observe the value at the time that {@link #get} was called. Updates and | |
| 79 | + * removals after the call do not impact ongoing reads. | |
| 80 | + * | |
| 81 | + * <p>This class is tolerant of some I/O errors. If files are missing from the | |
| 82 | + * filesystem, the corresponding entries will be dropped from the cache. If | |
| 83 | + * an error occurs while writing a cache value, the edit will fail silently. | |
| 84 | + * Callers should handle other problems by catching {@code IOException} and | |
| 85 | + * responding appropriately. | |
| 86 | + */ | |
| 87 | +final class DiskLruCache implements Closeable { | |
| 88 | + static final String JOURNAL_FILE = "journal"; | |
| 89 | + static final String JOURNAL_FILE_TEMP = "journal.tmp"; | |
| 90 | + static final String JOURNAL_FILE_BACKUP = "journal.bkp"; | |
| 91 | + static final String MAGIC = "libcore.io.DiskLruCache"; | |
| 92 | + static final String VERSION_1 = "1"; | |
| 93 | + static final long ANY_SEQUENCE_NUMBER = -1; | |
| 94 | + static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}"); | |
| 95 | + private static final String CLEAN = "CLEAN"; | |
| 96 | + private static final String DIRTY = "DIRTY"; | |
| 97 | + private static final String REMOVE = "REMOVE"; | |
| 98 | + private static final String READ = "READ"; | |
| 99 | + | |
| 100 | + /* | |
| 101 | + * This cache uses a journal file named "journal". A typical journal file | |
| 102 | + * looks like this: | |
| 103 | + * libcore.io.DiskLruCache | |
| 104 | + * 1 | |
| 105 | + * 100 | |
| 106 | + * 2 | |
| 107 | + * | |
| 108 | + * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 | |
| 109 | + * DIRTY 335c4c6028171cfddfbaae1a9c313c52 | |
| 110 | + * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 | |
| 111 | + * REMOVE 335c4c6028171cfddfbaae1a9c313c52 | |
| 112 | + * DIRTY 1ab96a171faeeee38496d8b330771a7a | |
| 113 | + * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 | |
| 114 | + * READ 335c4c6028171cfddfbaae1a9c313c52 | |
| 115 | + * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 | |
| 116 | + * | |
| 117 | + * The first five lines of the journal form its header. They are the | |
| 118 | + * constant string "libcore.io.DiskLruCache", the disk cache's version, | |
| 119 | + * the application's version, the value count, and a blank line. | |
| 120 | + * | |
| 121 | + * Each of the subsequent lines in the file is a record of the state of a | |
| 122 | + * cache entry. Each line contains space-separated values: a state, a key, | |
| 123 | + * and optional state-specific values. | |
| 124 | + * o DIRTY lines track that an entry is actively being created or updated. | |
| 125 | + * Every successful DIRTY action should be followed by a CLEAN or REMOVE | |
| 126 | + * action. DIRTY lines without a matching CLEAN or REMOVE indicate that | |
| 127 | + * temporary files may need to be deleted. | |
| 128 | + * o CLEAN lines track a cache entry that has been successfully published | |
| 129 | + * and may be read. A publish line is followed by the lengths of each of | |
| 130 | + * its values. | |
| 131 | + * o READ lines track accesses for LRU. | |
| 132 | + * o REMOVE lines track entries that have been deleted. | |
| 133 | + * | |
| 134 | + * The journal file is appended to as cache operations occur. The journal may | |
| 135 | + * occasionally be compacted by dropping redundant lines. A temporary file named | |
| 136 | + * "journal.tmp" will be used during compaction; that file should be deleted if | |
| 137 | + * it exists when the cache is opened. | |
| 138 | + */ | |
| 139 | + | |
| 140 | + private final File directory; | |
| 141 | + private final File journalFile; | |
| 142 | + private final File journalFileTmp; | |
| 143 | + private final File journalFileBackup; | |
| 144 | + private final int appVersion; | |
| 145 | + private long maxSize; | |
| 146 | + private int maxFileCount; | |
| 147 | + private final int valueCount; | |
| 148 | + private long size = 0; | |
| 149 | + private int fileCount = 0; | |
| 150 | + private Writer journalWriter; | |
| 151 | + private final LinkedHashMap<String, Entry> lruEntries = | |
| 152 | + new LinkedHashMap<String, Entry>(0, 0.75f, true); | |
| 153 | + private int redundantOpCount; | |
| 154 | + | |
| 155 | + /** | |
| 156 | + * To differentiate between old and current snapshots, each entry is given | |
| 157 | + * a sequence number each time an edit is committed. A snapshot is stale if | |
| 158 | + * its sequence number is not equal to its entry's sequence number. | |
| 159 | + */ | |
| 160 | + private long nextSequenceNumber = 0; | |
| 161 | + | |
| 162 | + /** This cache uses a single background thread to evict entries. */ | |
| 163 | + final ThreadPoolExecutor executorService = | |
| 164 | + new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); | |
| 165 | + private final Callable<Void> cleanupCallable = new Callable<Void>() { | |
| 166 | + public Void call() throws Exception { | |
| 167 | + synchronized (DiskLruCache.this) { | |
| 168 | + if (journalWriter == null) { | |
| 169 | + return null; // Closed. | |
| 170 | + } | |
| 171 | + trimToSize(); | |
| 172 | + trimToFileCount(); | |
| 173 | + if (journalRebuildRequired()) { | |
| 174 | + rebuildJournal(); | |
| 175 | + redundantOpCount = 0; | |
| 176 | + } | |
| 177 | + } | |
| 178 | + return null; | |
| 179 | + } | |
| 180 | + }; | |
| 181 | + | |
| 182 | + private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount) { | |
| 183 | + this.directory = directory; | |
| 184 | + this.appVersion = appVersion; | |
| 185 | + this.journalFile = new File(directory, JOURNAL_FILE); | |
| 186 | + this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); | |
| 187 | + this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); | |
| 188 | + this.valueCount = valueCount; | |
| 189 | + this.maxSize = maxSize; | |
| 190 | + this.maxFileCount = maxFileCount; | |
| 191 | + } | |
| 192 | + | |
| 193 | + /** | |
| 194 | + * Opens the cache in {@code directory}, creating a cache if none exists | |
| 195 | + * there. | |
| 196 | + * | |
| 197 | + * @param directory a writable directory | |
| 198 | + * @param valueCount the number of values per cache entry. Must be positive. | |
| 199 | + * @param maxSize the maximum number of bytes this cache should use to store | |
| 200 | + * @param maxFileCount the maximum file count this cache should store | |
| 201 | + * @throws IOException if reading or writing the cache directory fails | |
| 202 | + */ | |
| 203 | + public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount) | |
| 204 | + throws IOException { | |
| 205 | + if (maxSize <= 0) { | |
| 206 | + throw new IllegalArgumentException("maxSize <= 0"); | |
| 207 | + } | |
| 208 | + if (maxFileCount <= 0) { | |
| 209 | + throw new IllegalArgumentException("maxFileCount <= 0"); | |
| 210 | + } | |
| 211 | + if (valueCount <= 0) { | |
| 212 | + throw new IllegalArgumentException("valueCount <= 0"); | |
| 213 | + } | |
| 214 | + | |
| 215 | + // If a bkp file exists, use it instead. | |
| 216 | + File backupFile = new File(directory, JOURNAL_FILE_BACKUP); | |
| 217 | + if (backupFile.exists()) { | |
| 218 | + File journalFile = new File(directory, JOURNAL_FILE); | |
| 219 | + // If journal file also exists just delete backup file. | |
| 220 | + if (journalFile.exists()) { | |
| 221 | + backupFile.delete(); | |
| 222 | + } else { | |
| 223 | + renameTo(backupFile, journalFile, false); | |
| 224 | + } | |
| 225 | + } | |
| 226 | + | |
| 227 | + // Prefer to pick up where we left off. | |
| 228 | + DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount); | |
| 229 | + if (cache.journalFile.exists()) { | |
| 230 | + try { | |
| 231 | + cache.readJournal(); | |
| 232 | + cache.processJournal(); | |
| 233 | + cache.journalWriter = new BufferedWriter( | |
| 234 | + new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII)); | |
| 235 | + return cache; | |
| 236 | + } catch (IOException journalIsCorrupt) { | |
| 237 | + System.out | |
| 238 | + .println("DiskLruCache " | |
| 239 | + + directory | |
| 240 | + + " is corrupt: " | |
| 241 | + + journalIsCorrupt.getMessage() | |
| 242 | + + ", removing"); | |
| 243 | + cache.delete(); | |
| 244 | + } | |
| 245 | + } | |
| 246 | + | |
| 247 | + // Create a new empty cache. | |
| 248 | + directory.mkdirs(); | |
| 249 | + cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount); | |
| 250 | + cache.rebuildJournal(); | |
| 251 | + return cache; | |
| 252 | + } | |
| 253 | + | |
| 254 | + private void readJournal() throws IOException { | |
| 255 | + StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII); | |
| 256 | + try { | |
| 257 | + String magic = reader.readLine(); | |
| 258 | + String version = reader.readLine(); | |
| 259 | + String appVersionString = reader.readLine(); | |
| 260 | + String valueCountString = reader.readLine(); | |
| 261 | + String blank = reader.readLine(); | |
| 262 | + if (!MAGIC.equals(magic) | |
| 263 | + || !VERSION_1.equals(version) | |
| 264 | + || !Integer.toString(appVersion).equals(appVersionString) | |
| 265 | + || !Integer.toString(valueCount).equals(valueCountString) | |
| 266 | + || !"".equals(blank)) { | |
| 267 | + throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " | |
| 268 | + + valueCountString + ", " + blank + "]"); | |
| 269 | + } | |
| 270 | + | |
| 271 | + int lineCount = 0; | |
| 272 | + while (true) { | |
| 273 | + try { | |
| 274 | + readJournalLine(reader.readLine()); | |
| 275 | + lineCount++; | |
| 276 | + } catch (EOFException endOfJournal) { | |
| 277 | + break; | |
| 278 | + } | |
| 279 | + } | |
| 280 | + redundantOpCount = lineCount - lruEntries.size(); | |
| 281 | + } finally { | |
| 282 | + Util.closeQuietly(reader); | |
| 283 | + } | |
| 284 | + } | |
| 285 | + | |
| 286 | + private void readJournalLine(String line) throws IOException { | |
| 287 | + int firstSpace = line.indexOf(' '); | |
| 288 | + if (firstSpace == -1) { | |
| 289 | + throw new IOException("unexpected journal line: " + line); | |
| 290 | + } | |
| 291 | + | |
| 292 | + int keyBegin = firstSpace + 1; | |
| 293 | + int secondSpace = line.indexOf(' ', keyBegin); | |
| 294 | + final String key; | |
| 295 | + if (secondSpace == -1) { | |
| 296 | + key = line.substring(keyBegin); | |
| 297 | + if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) { | |
| 298 | + lruEntries.remove(key); | |
| 299 | + return; | |
| 300 | + } | |
| 301 | + } else { | |
| 302 | + key = line.substring(keyBegin, secondSpace); | |
| 303 | + } | |
| 304 | + | |
| 305 | + Entry entry = lruEntries.get(key); | |
| 306 | + if (entry == null) { | |
| 307 | + entry = new Entry(key); | |
| 308 | + lruEntries.put(key, entry); | |
| 309 | + } | |
| 310 | + | |
| 311 | + if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) { | |
| 312 | + String[] parts = line.substring(secondSpace + 1).split(" "); | |
| 313 | + entry.readable = true; | |
| 314 | + entry.currentEditor = null; | |
| 315 | + entry.setLengths(parts); | |
| 316 | + } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { | |
| 317 | + entry.currentEditor = new Editor(entry); | |
| 318 | + } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { | |
| 319 | + // This work was already done by calling lruEntries.get(). | |
| 320 | + } else { | |
| 321 | + throw new IOException("unexpected journal line: " + line); | |
| 322 | + } | |
| 323 | + } | |
| 324 | + | |
| 325 | + /** | |
| 326 | + * Computes the initial size and collects garbage as a part of opening the | |
| 327 | + * cache. Dirty entries are assumed to be inconsistent and will be deleted. | |
| 328 | + */ | |
| 329 | + private void processJournal() throws IOException { | |
| 330 | + deleteIfExists(journalFileTmp); | |
| 331 | + for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) { | |
| 332 | + Entry entry = i.next(); | |
| 333 | + if (entry.currentEditor == null) { | |
| 334 | + for (int t = 0; t < valueCount; t++) { | |
| 335 | + size += entry.lengths[t]; | |
| 336 | + fileCount++; | |
| 337 | + } | |
| 338 | + } else { | |
| 339 | + entry.currentEditor = null; | |
| 340 | + for (int t = 0; t < valueCount; t++) { | |
| 341 | + deleteIfExists(entry.getCleanFile(t)); | |
| 342 | + deleteIfExists(entry.getDirtyFile(t)); | |
| 343 | + } | |
| 344 | + i.remove(); | |
| 345 | + } | |
| 346 | + } | |
| 347 | + } | |
| 348 | + | |
| 349 | + /** | |
| 350 | + * Creates a new journal that omits redundant information. This replaces the | |
| 351 | + * current journal if it exists. | |
| 352 | + */ | |
| 353 | + private synchronized void rebuildJournal() throws IOException { | |
| 354 | + if (journalWriter != null) { | |
| 355 | + journalWriter.close(); | |
| 356 | + } | |
| 357 | + | |
| 358 | + Writer writer = new BufferedWriter( | |
| 359 | + new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII)); | |
| 360 | + try { | |
| 361 | + writer.write(MAGIC); | |
| 362 | + writer.write("\n"); | |
| 363 | + writer.write(VERSION_1); | |
| 364 | + writer.write("\n"); | |
| 365 | + writer.write(Integer.toString(appVersion)); | |
| 366 | + writer.write("\n"); | |
| 367 | + writer.write(Integer.toString(valueCount)); | |
| 368 | + writer.write("\n"); | |
| 369 | + writer.write("\n"); | |
| 370 | + | |
| 371 | + for (Entry entry : lruEntries.values()) { | |
| 372 | + if (entry.currentEditor != null) { | |
| 373 | + writer.write(DIRTY + ' ' + entry.key + '\n'); | |
| 374 | + } else { | |
| 375 | + writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); | |
| 376 | + } | |
| 377 | + } | |
| 378 | + } finally { | |
| 379 | + writer.close(); | |
| 380 | + } | |
| 381 | + | |
| 382 | + if (journalFile.exists()) { | |
| 383 | + renameTo(journalFile, journalFileBackup, true); | |
| 384 | + } | |
| 385 | + renameTo(journalFileTmp, journalFile, false); | |
| 386 | + journalFileBackup.delete(); | |
| 387 | + | |
| 388 | + journalWriter = new BufferedWriter( | |
| 389 | + new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII)); | |
| 390 | + } | |
| 391 | + | |
| 392 | + private static void deleteIfExists(File file) throws IOException { | |
| 393 | + if (file.exists() && !file.delete()) { | |
| 394 | + throw new IOException(); | |
| 395 | + } | |
| 396 | + } | |
| 397 | + | |
| 398 | + private static void renameTo(File from, File to, boolean deleteDestination) throws IOException { | |
| 399 | + if (deleteDestination) { | |
| 400 | + deleteIfExists(to); | |
| 401 | + } | |
| 402 | + if (!from.renameTo(to)) { | |
| 403 | + throw new IOException(); | |
| 404 | + } | |
| 405 | + } | |
| 406 | + | |
| 407 | + /** | |
| 408 | + * Returns a snapshot of the entry named {@code key}, or null if it doesn't | |
| 409 | + * exist is not currently readable. If a value is returned, it is moved to | |
| 410 | + * the head of the LRU queue. | |
| 411 | + */ | |
| 412 | + public synchronized Snapshot get(String key) throws IOException { | |
| 413 | + checkNotClosed(); | |
| 414 | + validateKey(key); | |
| 415 | + Entry entry = lruEntries.get(key); | |
| 416 | + if (entry == null) { | |
| 417 | + return null; | |
| 418 | + } | |
| 419 | + | |
| 420 | + if (!entry.readable) { | |
| 421 | + return null; | |
| 422 | + } | |
| 423 | + | |
| 424 | + // Open all streams eagerly to guarantee that we see a single published | |
| 425 | + // snapshot. If we opened streams lazily then the streams could come | |
| 426 | + // from different edits. | |
| 427 | + File[] files = new File[valueCount]; | |
| 428 | + InputStream[] ins = new InputStream[valueCount]; | |
| 429 | + try { | |
| 430 | + File file; | |
| 431 | + for (int i = 0; i < valueCount; i++) { | |
| 432 | + file = entry.getCleanFile(i); | |
| 433 | + files[i] = file; | |
| 434 | + ins[i] = new FileInputStream(file); | |
| 435 | + } | |
| 436 | + } catch (FileNotFoundException e) { | |
| 437 | + // A file must have been deleted manually! | |
| 438 | + for (int i = 0; i < valueCount; i++) { | |
| 439 | + if (ins[i] != null) { | |
| 440 | + Util.closeQuietly(ins[i]); | |
| 441 | + } else { | |
| 442 | + break; | |
| 443 | + } | |
| 444 | + } | |
| 445 | + return null; | |
| 446 | + } | |
| 447 | + | |
| 448 | + redundantOpCount++; | |
| 449 | + journalWriter.append(READ + ' ' + key + '\n'); | |
| 450 | + if (journalRebuildRequired()) { | |
| 451 | + executorService.submit(cleanupCallable); | |
| 452 | + } | |
| 453 | + | |
| 454 | + return new Snapshot(key, entry.sequenceNumber, files, ins, entry.lengths); | |
| 455 | + } | |
| 456 | + | |
| 457 | + /** | |
| 458 | + * Returns an editor for the entry named {@code key}, or null if another | |
| 459 | + * edit is in progress. | |
| 460 | + */ | |
| 461 | + public Editor edit(String key) throws IOException { | |
| 462 | + return edit(key, ANY_SEQUENCE_NUMBER); | |
| 463 | + } | |
| 464 | + | |
| 465 | + private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { | |
| 466 | + checkNotClosed(); | |
| 467 | + validateKey(key); | |
| 468 | + Entry entry = lruEntries.get(key); | |
| 469 | + if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null | |
| 470 | + || entry.sequenceNumber != expectedSequenceNumber)) { | |
| 471 | + return null; // Snapshot is stale. | |
| 472 | + } | |
| 473 | + if (entry == null) { | |
| 474 | + entry = new Entry(key); | |
| 475 | + lruEntries.put(key, entry); | |
| 476 | + } else if (entry.currentEditor != null) { | |
| 477 | + return null; // Another edit is in progress. | |
| 478 | + } | |
| 479 | + | |
| 480 | + Editor editor = new Editor(entry); | |
| 481 | + entry.currentEditor = editor; | |
| 482 | + | |
| 483 | + // Flush the journal before creating files to prevent file leaks. | |
| 484 | + journalWriter.write(DIRTY + ' ' + key + '\n'); | |
| 485 | + journalWriter.flush(); | |
| 486 | + return editor; | |
| 487 | + } | |
| 488 | + | |
| 489 | + /** Returns the directory where this cache stores its data. */ | |
| 490 | + public File getDirectory() { | |
| 491 | + return directory; | |
| 492 | + } | |
| 493 | + | |
| 494 | + /** | |
| 495 | + * Returns the maximum number of bytes that this cache should use to store | |
| 496 | + * its data. | |
| 497 | + */ | |
| 498 | + public synchronized long getMaxSize() { | |
| 499 | + return maxSize; | |
| 500 | + } | |
| 501 | + | |
| 502 | + /** Returns the maximum number of files that this cache should store */ | |
| 503 | + public synchronized int getMaxFileCount() { | |
| 504 | + return maxFileCount; | |
| 505 | + } | |
| 506 | + | |
| 507 | + /** | |
| 508 | + * Changes the maximum number of bytes the cache can store and queues a job | |
| 509 | + * to trim the existing store, if necessary. | |
| 510 | + */ | |
| 511 | + public synchronized void setMaxSize(long maxSize) { | |
| 512 | + this.maxSize = maxSize; | |
| 513 | + executorService.submit(cleanupCallable); | |
| 514 | + } | |
| 515 | + | |
| 516 | + /** | |
| 517 | + * Returns the number of bytes currently being used to store the values in | |
| 518 | + * this cache. This may be greater than the max size if a background | |
| 519 | + * deletion is pending. | |
| 520 | + */ | |
| 521 | + public synchronized long size() { | |
| 522 | + return size; | |
| 523 | + } | |
| 524 | + | |
| 525 | + /** | |
| 526 | + * Returns the number of files currently being used to store the values in | |
| 527 | + * this cache. This may be greater than the max file count if a background | |
| 528 | + * deletion is pending. | |
| 529 | + */ | |
| 530 | + public synchronized long fileCount() { | |
| 531 | + return fileCount; | |
| 532 | + } | |
| 533 | + | |
| 534 | + private synchronized void completeEdit(Editor editor, boolean success) throws IOException { | |
| 535 | + Entry entry = editor.entry; | |
| 536 | + if (entry.currentEditor != editor) { | |
| 537 | + throw new IllegalStateException(); | |
| 538 | + } | |
| 539 | + | |
| 540 | + // If this edit is creating the entry for the first time, every index must have a value. | |
| 541 | + if (success && !entry.readable) { | |
| 542 | + for (int i = 0; i < valueCount; i++) { | |
| 543 | + if (!editor.written[i]) { | |
| 544 | + editor.abort(); | |
| 545 | + throw new IllegalStateException("Newly created entry didn't create value for index " + i); | |
| 546 | + } | |
| 547 | + if (!entry.getDirtyFile(i).exists()) { | |
| 548 | + editor.abort(); | |
| 549 | + return; | |
| 550 | + } | |
| 551 | + } | |
| 552 | + } | |
| 553 | + | |
| 554 | + for (int i = 0; i < valueCount; i++) { | |
| 555 | + File dirty = entry.getDirtyFile(i); | |
| 556 | + if (success) { | |
| 557 | + if (dirty.exists()) { | |
| 558 | + File clean = entry.getCleanFile(i); | |
| 559 | + dirty.renameTo(clean); | |
| 560 | + long oldLength = entry.lengths[i]; | |
| 561 | + long newLength = clean.length(); | |
| 562 | + entry.lengths[i] = newLength; | |
| 563 | + size = size - oldLength + newLength; | |
| 564 | + fileCount++; | |
| 565 | + } | |
| 566 | + } else { | |
| 567 | + deleteIfExists(dirty); | |
| 568 | + } | |
| 569 | + } | |
| 570 | + | |
| 571 | + redundantOpCount++; | |
| 572 | + entry.currentEditor = null; | |
| 573 | + if (entry.readable | success) { | |
| 574 | + entry.readable = true; | |
| 575 | + journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); | |
| 576 | + if (success) { | |
| 577 | + entry.sequenceNumber = nextSequenceNumber++; | |
| 578 | + } | |
| 579 | + } else { | |
| 580 | + lruEntries.remove(entry.key); | |
| 581 | + journalWriter.write(REMOVE + ' ' + entry.key + '\n'); | |
| 582 | + } | |
| 583 | + journalWriter.flush(); | |
| 584 | + | |
| 585 | + if (size > maxSize || fileCount > maxFileCount || journalRebuildRequired()) { | |
| 586 | + executorService.submit(cleanupCallable); | |
| 587 | + } | |
| 588 | + } | |
| 589 | + | |
| 590 | + /** | |
| 591 | + * We only rebuild the journal when it will halve the size of the journal | |
| 592 | + * and eliminate at least 2000 ops. | |
| 593 | + */ | |
| 594 | + private boolean journalRebuildRequired() { | |
| 595 | + final int redundantOpCompactThreshold = 2000; | |
| 596 | + return redundantOpCount >= redundantOpCompactThreshold // | |
| 597 | + && redundantOpCount >= lruEntries.size(); | |
| 598 | + } | |
| 599 | + | |
| 600 | + /** | |
| 601 | + * Drops the entry for {@code key} if it exists and can be removed. Entries | |
| 602 | + * actively being edited cannot be removed. | |
| 603 | + * | |
| 604 | + * @return true if an entry was removed. | |
| 605 | + */ | |
| 606 | + public synchronized boolean remove(String key) throws IOException { | |
| 607 | + checkNotClosed(); | |
| 608 | + validateKey(key); | |
| 609 | + Entry entry = lruEntries.get(key); | |
| 610 | + if (entry == null || entry.currentEditor != null) { | |
| 611 | + return false; | |
| 612 | + } | |
| 613 | + | |
| 614 | + for (int i = 0; i < valueCount; i++) { | |
| 615 | + File file = entry.getCleanFile(i); | |
| 616 | + if (file.exists() && !file.delete()) { | |
| 617 | + throw new IOException("failed to delete " + file); | |
| 618 | + } | |
| 619 | + size -= entry.lengths[i]; | |
| 620 | + fileCount--; | |
| 621 | + entry.lengths[i] = 0; | |
| 622 | + } | |
| 623 | + | |
| 624 | + redundantOpCount++; | |
| 625 | + journalWriter.append(REMOVE + ' ' + key + '\n'); | |
| 626 | + lruEntries.remove(key); | |
| 627 | + | |
| 628 | + if (journalRebuildRequired()) { | |
| 629 | + executorService.submit(cleanupCallable); | |
| 630 | + } | |
| 631 | + | |
| 632 | + return true; | |
| 633 | + } | |
| 634 | + | |
| 635 | + /** Returns true if this cache has been closed. */ | |
| 636 | + public synchronized boolean isClosed() { | |
| 637 | + return journalWriter == null; | |
| 638 | + } | |
| 639 | + | |
| 640 | + private void checkNotClosed() { | |
| 641 | + if (journalWriter == null) { | |
| 642 | + throw new IllegalStateException("cache is closed"); | |
| 643 | + } | |
| 644 | + } | |
| 645 | + | |
| 646 | + /** Force buffered operations to the filesystem. */ | |
| 647 | + public synchronized void flush() throws IOException { | |
| 648 | + checkNotClosed(); | |
| 649 | + trimToSize(); | |
| 650 | + trimToFileCount(); | |
| 651 | + journalWriter.flush(); | |
| 652 | + } | |
| 653 | + | |
| 654 | + /** Closes this cache. Stored values will remain on the filesystem. */ | |
| 655 | + public synchronized void close() throws IOException { | |
| 656 | + if (journalWriter == null) { | |
| 657 | + return; // Already closed. | |
| 658 | + } | |
| 659 | + for (Entry entry : new ArrayList<Entry>(lruEntries.values())) { | |
| 660 | + if (entry.currentEditor != null) { | |
| 661 | + entry.currentEditor.abort(); | |
| 662 | + } | |
| 663 | + } | |
| 664 | + trimToSize(); | |
| 665 | + trimToFileCount(); | |
| 666 | + journalWriter.close(); | |
| 667 | + journalWriter = null; | |
| 668 | + } | |
| 669 | + | |
| 670 | + private void trimToSize() throws IOException { | |
| 671 | + while (size > maxSize) { | |
| 672 | + Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next(); | |
| 673 | + remove(toEvict.getKey()); | |
| 674 | + } | |
| 675 | + } | |
| 676 | + | |
| 677 | + private void trimToFileCount() throws IOException { | |
| 678 | + while (fileCount > maxFileCount) { | |
| 679 | + Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next(); | |
| 680 | + remove(toEvict.getKey()); | |
| 681 | + } | |
| 682 | + } | |
| 683 | + | |
| 684 | + /** | |
| 685 | + * Closes the cache and deletes all of its stored values. This will delete | |
| 686 | + * all files in the cache directory including files that weren't created by | |
| 687 | + * the cache. | |
| 688 | + */ | |
| 689 | + public void delete() throws IOException { | |
| 690 | + close(); | |
| 691 | + Util.deleteContents(directory); | |
| 692 | + } | |
| 693 | + | |
| 694 | + private void validateKey(String key) { | |
| 695 | + Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); | |
| 696 | + if (!matcher.matches()) { | |
| 697 | + throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\""); | |
| 698 | + } | |
| 699 | + } | |
| 700 | + | |
| 701 | + private static String inputStreamToString(InputStream in) throws IOException { | |
| 702 | + return Util.readFully(new InputStreamReader(in, Util.UTF_8)); | |
| 703 | + } | |
| 704 | + | |
| 705 | + /** A snapshot of the values for an entry. */ | |
| 706 | + public final class Snapshot implements Closeable { | |
| 707 | + private final String key; | |
| 708 | + private final long sequenceNumber; | |
| 709 | + private File[] files; | |
| 710 | + private final InputStream[] ins; | |
| 711 | + private final long[] lengths; | |
| 712 | + | |
| 713 | + private Snapshot(String key, long sequenceNumber, File[] files, InputStream[] ins, long[] lengths) { | |
| 714 | + this.key = key; | |
| 715 | + this.sequenceNumber = sequenceNumber; | |
| 716 | + this.files = files; | |
| 717 | + this.ins = ins; | |
| 718 | + this.lengths = lengths; | |
| 719 | + } | |
| 720 | + | |
| 721 | + /** | |
| 722 | + * Returns an editor for this snapshot's entry, or null if either the | |
| 723 | + * entry has changed since this snapshot was created or if another edit | |
| 724 | + * is in progress. | |
| 725 | + */ | |
| 726 | + public Editor edit() throws IOException { | |
| 727 | + return DiskLruCache.this.edit(key, sequenceNumber); | |
| 728 | + } | |
| 729 | + | |
| 730 | + /** Returns file with the value for {@code index}. */ | |
| 731 | + public File getFile(int index) { | |
| 732 | + return files[index]; | |
| 733 | + } | |
| 734 | + | |
| 735 | + /** Returns the unbuffered stream with the value for {@code index}. */ | |
| 736 | + public InputStream getInputStream(int index) { | |
| 737 | + return ins[index]; | |
| 738 | + } | |
| 739 | + | |
| 740 | + /** Returns the string value for {@code index}. */ | |
| 741 | + public String getString(int index) throws IOException { | |
| 742 | + return inputStreamToString(getInputStream(index)); | |
| 743 | + } | |
| 744 | + | |
| 745 | + /** Returns the byte length of the value for {@code index}. */ | |
| 746 | + public long getLength(int index) { | |
| 747 | + return lengths[index]; | |
| 748 | + } | |
| 749 | + | |
| 750 | + public void close() { | |
| 751 | + for (InputStream in : ins) { | |
| 752 | + Util.closeQuietly(in); | |
| 753 | + } | |
| 754 | + } | |
| 755 | + } | |
| 756 | + | |
| 757 | + private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { | |
| 758 | + @Override | |
| 759 | + public void write(int b) throws IOException { | |
| 760 | + // Eat all writes silently. Nom nom. | |
| 761 | + } | |
| 762 | + }; | |
| 763 | + | |
| 764 | + /** Edits the values for an entry. */ | |
| 765 | + public final class Editor { | |
| 766 | + private final Entry entry; | |
| 767 | + private final boolean[] written; | |
| 768 | + private boolean hasErrors; | |
| 769 | + private boolean committed; | |
| 770 | + | |
| 771 | + private Editor(Entry entry) { | |
| 772 | + this.entry = entry; | |
| 773 | + this.written = (entry.readable) ? null : new boolean[valueCount]; | |
| 774 | + } | |
| 775 | + | |
| 776 | + /** | |
| 777 | + * Returns an unbuffered input stream to read the last committed value, | |
| 778 | + * or null if no value has been committed. | |
| 779 | + */ | |
| 780 | + public InputStream newInputStream(int index) throws IOException { | |
| 781 | + synchronized (DiskLruCache.this) { | |
| 782 | + if (entry.currentEditor != this) { | |
| 783 | + throw new IllegalStateException(); | |
| 784 | + } | |
| 785 | + if (!entry.readable) { | |
| 786 | + return null; | |
| 787 | + } | |
| 788 | + try { | |
| 789 | + return new FileInputStream(entry.getCleanFile(index)); | |
| 790 | + } catch (FileNotFoundException e) { | |
| 791 | + return null; | |
| 792 | + } | |
| 793 | + } | |
| 794 | + } | |
| 795 | + | |
| 796 | + /** | |
| 797 | + * Returns the last committed value as a string, or null if no value | |
| 798 | + * has been committed. | |
| 799 | + */ | |
| 800 | + public String getString(int index) throws IOException { | |
| 801 | + InputStream in = newInputStream(index); | |
| 802 | + return in != null ? inputStreamToString(in) : null; | |
| 803 | + } | |
| 804 | + | |
| 805 | + /** | |
| 806 | + * Returns a new unbuffered output stream to write the value at | |
| 807 | + * {@code index}. If the underlying output stream encounters errors | |
| 808 | + * when writing to the filesystem, this edit will be aborted when | |
| 809 | + * {@link #commit} is called. The returned output stream does not throw | |
| 810 | + * IOExceptions. | |
| 811 | + */ | |
| 812 | + public OutputStream newOutputStream(int index) throws IOException { | |
| 813 | + synchronized (DiskLruCache.this) { | |
| 814 | + if (entry.currentEditor != this) { | |
| 815 | + throw new IllegalStateException(); | |
| 816 | + } | |
| 817 | + if (!entry.readable) { | |
| 818 | + written[index] = true; | |
| 819 | + } | |
| 820 | + File dirtyFile = entry.getDirtyFile(index); | |
| 821 | + FileOutputStream outputStream; | |
| 822 | + try { | |
| 823 | + outputStream = new FileOutputStream(dirtyFile); | |
| 824 | + } catch (FileNotFoundException e) { | |
| 825 | + // Attempt to recreate the cache directory. | |
| 826 | + directory.mkdirs(); | |
| 827 | + try { | |
| 828 | + outputStream = new FileOutputStream(dirtyFile); | |
| 829 | + } catch (FileNotFoundException e2) { | |
| 830 | + // We are unable to recover. Silently eat the writes. | |
| 831 | + return NULL_OUTPUT_STREAM; | |
| 832 | + } | |
| 833 | + } | |
| 834 | + return new FaultHidingOutputStream(outputStream); | |
| 835 | + } | |
| 836 | + } | |
| 837 | + | |
| 838 | + /** Sets the value at {@code index} to {@code value}. */ | |
| 839 | + public void set(int index, String value) throws IOException { | |
| 840 | + Writer writer = null; | |
| 841 | + try { | |
| 842 | + writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8); | |
| 843 | + writer.write(value); | |
| 844 | + } finally { | |
| 845 | + Util.closeQuietly(writer); | |
| 846 | + } | |
| 847 | + } | |
| 848 | + | |
| 849 | + /** | |
| 850 | + * Commits this edit so it is visible to readers. This releases the | |
| 851 | + * edit lock so another edit may be started on the same key. | |
| 852 | + */ | |
| 853 | + public void commit() throws IOException { | |
| 854 | + if (hasErrors) { | |
| 855 | + completeEdit(this, false); | |
| 856 | + remove(entry.key); // The previous entry is stale. | |
| 857 | + } else { | |
| 858 | + completeEdit(this, true); | |
| 859 | + } | |
| 860 | + committed = true; | |
| 861 | + } | |
| 862 | + | |
| 863 | + /** | |
| 864 | + * Aborts this edit. This releases the edit lock so another edit may be | |
| 865 | + * started on the same key. | |
| 866 | + */ | |
| 867 | + public void abort() throws IOException { | |
| 868 | + completeEdit(this, false); | |
| 869 | + } | |
| 870 | + | |
| 871 | + public void abortUnlessCommitted() { | |
| 872 | + if (!committed) { | |
| 873 | + try { | |
| 874 | + abort(); | |
| 875 | + } catch (IOException ignored) { | |
| 876 | + } | |
| 877 | + } | |
| 878 | + } | |
| 879 | + | |
| 880 | + private class FaultHidingOutputStream extends FilterOutputStream { | |
| 881 | + private FaultHidingOutputStream(OutputStream out) { | |
| 882 | + super(out); | |
| 883 | + } | |
| 884 | + | |
| 885 | + @Override public void write(int oneByte) { | |
| 886 | + try { | |
| 887 | + out.write(oneByte); | |
| 888 | + } catch (IOException e) { | |
| 889 | + hasErrors = true; | |
| 890 | + } | |
| 891 | + } | |
| 892 | + | |
| 893 | + @Override public void write(byte[] buffer, int offset, int length) { | |
| 894 | + try { | |
| 895 | + out.write(buffer, offset, length); | |
| 896 | + } catch (IOException e) { | |
| 897 | + hasErrors = true; | |
| 898 | + } | |
| 899 | + } | |
| 900 | + | |
| 901 | + @Override public void close() { | |
| 902 | + try { | |
| 903 | + out.close(); | |
| 904 | + } catch (IOException e) { | |
| 905 | + hasErrors = true; | |
| 906 | + } | |
| 907 | + } | |
| 908 | + | |
| 909 | + @Override public void flush() { | |
| 910 | + try { | |
| 911 | + out.flush(); | |
| 912 | + } catch (IOException e) { | |
| 913 | + hasErrors = true; | |
| 914 | + } | |
| 915 | + } | |
| 916 | + } | |
| 917 | + } | |
| 918 | + | |
| 919 | + private final class Entry { | |
| 920 | + private final String key; | |
| 921 | + | |
| 922 | + /** Lengths of this entry's files. */ | |
| 923 | + private final long[] lengths; | |
| 924 | + | |
| 925 | + /** True if this entry has ever been published. */ | |
| 926 | + private boolean readable; | |
| 927 | + | |
| 928 | + /** The ongoing edit or null if this entry is not being edited. */ | |
| 929 | + private Editor currentEditor; | |
| 930 | + | |
| 931 | + /** The sequence number of the most recently committed edit to this entry. */ | |
| 932 | + private long sequenceNumber; | |
| 933 | + | |
| 934 | + private Entry(String key) { | |
| 935 | + this.key = key; | |
| 936 | + this.lengths = new long[valueCount]; | |
| 937 | + } | |
| 938 | + | |
| 939 | + public String getLengths() throws IOException { | |
| 940 | + StringBuilder result = new StringBuilder(); | |
| 941 | + for (long size : lengths) { | |
| 942 | + result.append(' ').append(size); | |
| 943 | + } | |
| 944 | + return result.toString(); | |
| 945 | + } | |
| 946 | + | |
| 947 | + /** Set lengths using decimal numbers like "10123". */ | |
| 948 | + private void setLengths(String[] strings) throws IOException { | |
| 949 | + if (strings.length != valueCount) { | |
| 950 | + throw invalidLengths(strings); | |
| 951 | + } | |
| 952 | + | |
| 953 | + try { | |
| 954 | + for (int i = 0; i < strings.length; i++) { | |
| 955 | + lengths[i] = Long.parseLong(strings[i]); | |
| 956 | + } | |
| 957 | + } catch (NumberFormatException e) { | |
| 958 | + throw invalidLengths(strings); | |
| 959 | + } | |
| 960 | + } | |
| 961 | + | |
| 962 | + private IOException invalidLengths(String[] strings) throws IOException { | |
| 963 | + throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings)); | |
| 964 | + } | |
| 965 | + | |
| 966 | + public File getCleanFile(int i) { | |
| 967 | + return new File(directory, key + "." + i); | |
| 968 | + } | |
| 969 | + | |
| 970 | + public File getDirtyFile(int i) { | |
| 971 | + return new File(directory, key + "." + i + ".tmp"); | |
| 972 | + } | |
| 973 | + } | |
| 974 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/LruDiskCache.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc.impl.ext; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.cache.disc.DiskCache; | |
| 20 | +import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; | |
| 21 | +import com.nostra13.universalimageloader.utils.IoUtils; | |
| 22 | +import com.nostra13.universalimageloader.utils.L; | |
| 23 | + | |
| 24 | +import java.io.BufferedOutputStream; | |
| 25 | +import java.io.File; | |
| 26 | +import java.io.IOException; | |
| 27 | +import java.io.InputStream; | |
| 28 | +import java.io.OutputStream; | |
| 29 | + | |
| 30 | +/** | |
| 31 | + * Disk cache based on "Least-Recently Used" principle. Adapter pattern, adapts | |
| 32 | + * {@link DiskLruCache DiskLruCache} to | |
| 33 | + * {@link DiskCache DiskCache} | |
| 34 | + * | |
| 35 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 36 | + * @see FileNameGenerator | |
| 37 | + * @since 1.9.2 | |
| 38 | + */ | |
| 39 | +public class LruDiskCache implements DiskCache { | |
| 40 | + /** {@value */ | |
| 41 | + public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 Kb | |
| 42 | + /** {@value */ | |
| 43 | + public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG; | |
| 44 | + /** {@value */ | |
| 45 | + public static final int DEFAULT_COMPRESS_QUALITY = 100; | |
| 46 | + | |
| 47 | + private static final String ERROR_ARG_NULL = " argument must be not null"; | |
| 48 | + private static final String ERROR_ARG_NEGATIVE = " argument must be positive number"; | |
| 49 | + | |
| 50 | + protected DiskLruCache cache; | |
| 51 | + private File reserveCacheDir; | |
| 52 | + | |
| 53 | + protected final FileNameGenerator fileNameGenerator; | |
| 54 | + | |
| 55 | + protected int bufferSize = DEFAULT_BUFFER_SIZE; | |
| 56 | + | |
| 57 | + protected Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT; | |
| 58 | + protected int compressQuality = DEFAULT_COMPRESS_QUALITY; | |
| 59 | + | |
| 60 | + /** | |
| 61 | + * @param cacheDir Directory for file caching | |
| 62 | + * @param fileNameGenerator {@linkplain FileNameGenerator | |
| 63 | + * Name generator} for cached files. Generated names must match the regex | |
| 64 | + * <strong>[a-z0-9_-]{1,64}</strong> | |
| 65 | + * @param cacheMaxSize Max cache size in bytes. <b>0</b> means cache size is unlimited. | |
| 66 | + * @throws IOException if cache can't be initialized (e.g. "No space left on device") | |
| 67 | + */ | |
| 68 | + public LruDiskCache(File cacheDir, FileNameGenerator fileNameGenerator, long cacheMaxSize) throws IOException { | |
| 69 | + this(cacheDir, null, fileNameGenerator, cacheMaxSize, 0); | |
| 70 | + } | |
| 71 | + | |
| 72 | + /** | |
| 73 | + * @param cacheDir Directory for file caching | |
| 74 | + * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available. | |
| 75 | + * @param fileNameGenerator {@linkplain FileNameGenerator | |
| 76 | + * Name generator} for cached files. Generated names must match the regex | |
| 77 | + * <strong>[a-z0-9_-]{1,64}</strong> | |
| 78 | + * @param cacheMaxSize Max cache size in bytes. <b>0</b> means cache size is unlimited. | |
| 79 | + * @param cacheMaxFileCount Max file count in cache. <b>0</b> means file count is unlimited. | |
| 80 | + * @throws IOException if cache can't be initialized (e.g. "No space left on device") | |
| 81 | + */ | |
| 82 | + public LruDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator, long cacheMaxSize, | |
| 83 | + int cacheMaxFileCount) throws IOException { | |
| 84 | + if (cacheDir == null) { | |
| 85 | + throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL); | |
| 86 | + } | |
| 87 | + if (cacheMaxSize < 0) { | |
| 88 | + throw new IllegalArgumentException("cacheMaxSize" + ERROR_ARG_NEGATIVE); | |
| 89 | + } | |
| 90 | + if (cacheMaxFileCount < 0) { | |
| 91 | + throw new IllegalArgumentException("cacheMaxFileCount" + ERROR_ARG_NEGATIVE); | |
| 92 | + } | |
| 93 | + if (fileNameGenerator == null) { | |
| 94 | + throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL); | |
| 95 | + } | |
| 96 | + | |
| 97 | + if (cacheMaxSize == 0) { | |
| 98 | + cacheMaxSize = Long.MAX_VALUE; | |
| 99 | + } | |
| 100 | + if (cacheMaxFileCount == 0) { | |
| 101 | + cacheMaxFileCount = Integer.MAX_VALUE; | |
| 102 | + } | |
| 103 | + | |
| 104 | + this.reserveCacheDir = reserveCacheDir; | |
| 105 | + this.fileNameGenerator = fileNameGenerator; | |
| 106 | + initCache(cacheDir, reserveCacheDir, cacheMaxSize, cacheMaxFileCount); | |
| 107 | + } | |
| 108 | + | |
| 109 | + private void initCache(File cacheDir, File reserveCacheDir, long cacheMaxSize, int cacheMaxFileCount) | |
| 110 | + throws IOException { | |
| 111 | + try { | |
| 112 | + cache = DiskLruCache.open(cacheDir, 1, 1, cacheMaxSize, cacheMaxFileCount); | |
| 113 | + } catch (IOException e) { | |
| 114 | + L.e(e); | |
| 115 | + if (reserveCacheDir != null) { | |
| 116 | + initCache(reserveCacheDir, null, cacheMaxSize, cacheMaxFileCount); | |
| 117 | + } | |
| 118 | + if (cache == null) { | |
| 119 | + throw e; //new RuntimeException("Can't initialize disk cache", e); | |
| 120 | + } | |
| 121 | + } | |
| 122 | + } | |
| 123 | + | |
| 124 | + @Override | |
| 125 | + public File getDirectory() { | |
| 126 | + return cache.getDirectory(); | |
| 127 | + } | |
| 128 | + | |
| 129 | + @Override | |
| 130 | + public File get(String imageUri) { | |
| 131 | + DiskLruCache.Snapshot snapshot = null; | |
| 132 | + try { | |
| 133 | + snapshot = cache.get(getKey(imageUri)); | |
| 134 | + return snapshot == null ? null : snapshot.getFile(0); | |
| 135 | + } catch (IOException e) { | |
| 136 | + L.e(e); | |
| 137 | + return null; | |
| 138 | + } finally { | |
| 139 | + if (snapshot != null) { | |
| 140 | + snapshot.close(); | |
| 141 | + } | |
| 142 | + } | |
| 143 | + } | |
| 144 | + | |
| 145 | + @Override | |
| 146 | + public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException { | |
| 147 | + DiskLruCache.Editor editor = cache.edit(getKey(imageUri)); | |
| 148 | + if (editor == null) { | |
| 149 | + return false; | |
| 150 | + } | |
| 151 | + | |
| 152 | + OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize); | |
| 153 | + boolean copied = false; | |
| 154 | + try { | |
| 155 | + copied = IoUtils.copyStream(imageStream, os, listener, bufferSize); | |
| 156 | + } finally { | |
| 157 | + IoUtils.closeSilently(os); | |
| 158 | + if (copied) { | |
| 159 | + editor.commit(); | |
| 160 | + } else { | |
| 161 | + editor.abort(); | |
| 162 | + } | |
| 163 | + } | |
| 164 | + return copied; | |
| 165 | + } | |
| 166 | + | |
| 167 | + @Override | |
| 168 | + public boolean save(String imageUri, Bitmap bitmap) throws IOException { | |
| 169 | + DiskLruCache.Editor editor = cache.edit(getKey(imageUri)); | |
| 170 | + if (editor == null) { | |
| 171 | + return false; | |
| 172 | + } | |
| 173 | + | |
| 174 | + OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize); | |
| 175 | + boolean savedSuccessfully = false; | |
| 176 | + try { | |
| 177 | + savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os); | |
| 178 | + } finally { | |
| 179 | + IoUtils.closeSilently(os); | |
| 180 | + } | |
| 181 | + if (savedSuccessfully) { | |
| 182 | + editor.commit(); | |
| 183 | + } else { | |
| 184 | + editor.abort(); | |
| 185 | + } | |
| 186 | + return savedSuccessfully; | |
| 187 | + } | |
| 188 | + | |
| 189 | + @Override | |
| 190 | + public boolean remove(String imageUri) { | |
| 191 | + try { | |
| 192 | + return cache.remove(getKey(imageUri)); | |
| 193 | + } catch (IOException e) { | |
| 194 | + L.e(e); | |
| 195 | + return false; | |
| 196 | + } | |
| 197 | + } | |
| 198 | + | |
| 199 | + @Override | |
| 200 | + public void close() { | |
| 201 | + try { | |
| 202 | + cache.close(); | |
| 203 | + } catch (IOException e) { | |
| 204 | + L.e(e); | |
| 205 | + } | |
| 206 | + cache = null; | |
| 207 | + } | |
| 208 | + | |
| 209 | + @Override | |
| 210 | + public void clear() { | |
| 211 | + try { | |
| 212 | + cache.delete(); | |
| 213 | + } catch (IOException e) { | |
| 214 | + L.e(e); | |
| 215 | + } | |
| 216 | + try { | |
| 217 | + initCache(cache.getDirectory(), reserveCacheDir, cache.getMaxSize(), cache.getMaxFileCount()); | |
| 218 | + } catch (IOException e) { | |
| 219 | + L.e(e); | |
| 220 | + } | |
| 221 | + } | |
| 222 | + | |
| 223 | + private String getKey(String imageUri) { | |
| 224 | + return fileNameGenerator.generate(imageUri); | |
| 225 | + } | |
| 226 | + | |
| 227 | + public void setBufferSize(int bufferSize) { | |
| 228 | + this.bufferSize = bufferSize; | |
| 229 | + } | |
| 230 | + | |
| 231 | + public void setCompressFormat(Bitmap.CompressFormat compressFormat) { | |
| 232 | + this.compressFormat = compressFormat; | |
| 233 | + } | |
| 234 | + | |
| 235 | + public void setCompressQuality(int compressQuality) { | |
| 236 | + this.compressQuality = compressQuality; | |
| 237 | + } | |
| 238 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/StrictLineReader.java
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 The Android Open Source Project | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc.impl.ext; | |
| 17 | + | |
| 18 | +import java.io.ByteArrayOutputStream; | |
| 19 | +import java.io.Closeable; | |
| 20 | +import java.io.EOFException; | |
| 21 | +import java.io.IOException; | |
| 22 | +import java.io.InputStream; | |
| 23 | +import java.io.UnsupportedEncodingException; | |
| 24 | +import java.nio.charset.Charset; | |
| 25 | + | |
| 26 | +/** | |
| 27 | + * Buffers input from an {@link InputStream} for reading lines. | |
| 28 | + * | |
| 29 | + * <p>This class is used for buffered reading of lines. For purposes of this class, a line ends | |
| 30 | + * with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated | |
| 31 | + * line at end of input is invalid and will be ignored, the caller may use {@code | |
| 32 | + * hasUnterminatedLine()} to detect it after catching the {@code EOFException}. | |
| 33 | + * | |
| 34 | + * <p>This class is intended for reading input that strictly consists of lines, such as line-based | |
| 35 | + * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction | |
| 36 | + * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different | |
| 37 | + * end-of-input reporting and a more restrictive definition of a line. | |
| 38 | + * | |
| 39 | + * <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13 | |
| 40 | + * and 10, respectively, and the representation of no other character contains these values. | |
| 41 | + * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1. | |
| 42 | + * The default charset is US_ASCII. | |
| 43 | + */ | |
| 44 | +class StrictLineReader implements Closeable { | |
| 45 | + private static final byte CR = (byte) '\r'; | |
| 46 | + private static final byte LF = (byte) '\n'; | |
| 47 | + | |
| 48 | + private final InputStream in; | |
| 49 | + private final Charset charset; | |
| 50 | + | |
| 51 | + /* | |
| 52 | + * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end | |
| 53 | + * and the data in the range [pos, end) is buffered for reading. At end of input, if there is | |
| 54 | + * an unterminated line, we set end == -1, otherwise end == pos. If the underlying | |
| 55 | + * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1. | |
| 56 | + */ | |
| 57 | + private byte[] buf; | |
| 58 | + private int pos; | |
| 59 | + private int end; | |
| 60 | + | |
| 61 | + /** | |
| 62 | + * Constructs a new {@code LineReader} with the specified charset and the default capacity. | |
| 63 | + * | |
| 64 | + * @param in the {@code InputStream} to read data from. | |
| 65 | + * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are | |
| 66 | + * supported. | |
| 67 | + * @throws NullPointerException if {@code in} or {@code charset} is null. | |
| 68 | + * @throws IllegalArgumentException if the specified charset is not supported. | |
| 69 | + */ | |
| 70 | + public StrictLineReader(InputStream in, Charset charset) { | |
| 71 | + this(in, 8192, charset); | |
| 72 | + } | |
| 73 | + | |
| 74 | + /** | |
| 75 | + * Constructs a new {@code LineReader} with the specified capacity and charset. | |
| 76 | + * | |
| 77 | + * @param in the {@code InputStream} to read data from. | |
| 78 | + * @param capacity the capacity of the buffer. | |
| 79 | + * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are | |
| 80 | + * supported. | |
| 81 | + * @throws NullPointerException if {@code in} or {@code charset} is null. | |
| 82 | + * @throws IllegalArgumentException if {@code capacity} is negative or zero | |
| 83 | + * or the specified charset is not supported. | |
| 84 | + */ | |
| 85 | + public StrictLineReader(InputStream in, int capacity, Charset charset) { | |
| 86 | + if (in == null || charset == null) { | |
| 87 | + throw new NullPointerException(); | |
| 88 | + } | |
| 89 | + if (capacity < 0) { | |
| 90 | + throw new IllegalArgumentException("capacity <= 0"); | |
| 91 | + } | |
| 92 | + if (!(charset.equals(Util.US_ASCII))) { | |
| 93 | + throw new IllegalArgumentException("Unsupported encoding"); | |
| 94 | + } | |
| 95 | + | |
| 96 | + this.in = in; | |
| 97 | + this.charset = charset; | |
| 98 | + buf = new byte[capacity]; | |
| 99 | + } | |
| 100 | + | |
| 101 | + /** | |
| 102 | + * Closes the reader by closing the underlying {@code InputStream} and | |
| 103 | + * marking this reader as closed. | |
| 104 | + * | |
| 105 | + * @throws IOException for errors when closing the underlying {@code InputStream}. | |
| 106 | + */ | |
| 107 | + public void close() throws IOException { | |
| 108 | + synchronized (in) { | |
| 109 | + if (buf != null) { | |
| 110 | + buf = null; | |
| 111 | + in.close(); | |
| 112 | + } | |
| 113 | + } | |
| 114 | + } | |
| 115 | + | |
| 116 | + /** | |
| 117 | + * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"}, | |
| 118 | + * this end of line marker is not included in the result. | |
| 119 | + * | |
| 120 | + * @return the next line from the input. | |
| 121 | + * @throws IOException for underlying {@code InputStream} errors. | |
| 122 | + * @throws EOFException for the end of source stream. | |
| 123 | + */ | |
| 124 | + public String readLine() throws IOException { | |
| 125 | + synchronized (in) { | |
| 126 | + if (buf == null) { | |
| 127 | + throw new IOException("LineReader is closed"); | |
| 128 | + } | |
| 129 | + | |
| 130 | + // Read more data if we are at the end of the buffered data. | |
| 131 | + // Though it's an error to read after an exception, we will let {@code fillBuf()} | |
| 132 | + // throw again if that happens; thus we need to handle end == -1 as well as end == pos. | |
| 133 | + if (pos >= end) { | |
| 134 | + fillBuf(); | |
| 135 | + } | |
| 136 | + // Try to find LF in the buffered data and return the line if successful. | |
| 137 | + for (int i = pos; i != end; ++i) { | |
| 138 | + if (buf[i] == LF) { | |
| 139 | + int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i; | |
| 140 | + String res = new String(buf, pos, lineEnd - pos, charset.name()); | |
| 141 | + pos = i + 1; | |
| 142 | + return res; | |
| 143 | + } | |
| 144 | + } | |
| 145 | + | |
| 146 | + // Let's anticipate up to 80 characters on top of those already read. | |
| 147 | + ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { | |
| 148 | + @Override | |
| 149 | + public String toString() { | |
| 150 | + int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; | |
| 151 | + try { | |
| 152 | + return new String(buf, 0, length, charset.name()); | |
| 153 | + } catch (UnsupportedEncodingException e) { | |
| 154 | + throw new AssertionError(e); // Since we control the charset this will never happen. | |
| 155 | + } | |
| 156 | + } | |
| 157 | + }; | |
| 158 | + | |
| 159 | + while (true) { | |
| 160 | + out.write(buf, pos, end - pos); | |
| 161 | + // Mark unterminated line in case fillBuf throws EOFException or IOException. | |
| 162 | + end = -1; | |
| 163 | + fillBuf(); | |
| 164 | + // Try to find LF in the buffered data and return the line if successful. | |
| 165 | + for (int i = pos; i != end; ++i) { | |
| 166 | + if (buf[i] == LF) { | |
| 167 | + if (i != pos) { | |
| 168 | + out.write(buf, pos, i - pos); | |
| 169 | + } | |
| 170 | + pos = i + 1; | |
| 171 | + return out.toString(); | |
| 172 | + } | |
| 173 | + } | |
| 174 | + } | |
| 175 | + } | |
| 176 | + } | |
| 177 | + | |
| 178 | + /** | |
| 179 | + * Reads new input data into the buffer. Call only with pos == end or end == -1, | |
| 180 | + * depending on the desired outcome if the function throws. | |
| 181 | + */ | |
| 182 | + private void fillBuf() throws IOException { | |
| 183 | + int result = in.read(buf, 0, buf.length); | |
| 184 | + if (result == -1) { | |
| 185 | + throw new EOFException(); | |
| 186 | + } | |
| 187 | + pos = 0; | |
| 188 | + end = result; | |
| 189 | + } | |
| 190 | +} | |
| 191 | + | ... | ... |
| 1 | +/* | |
| 2 | + * Copyright (C) 2010 The Android Open Source Project | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc.impl.ext; | |
| 17 | + | |
| 18 | +import java.io.Closeable; | |
| 19 | +import java.io.File; | |
| 20 | +import java.io.IOException; | |
| 21 | +import java.io.Reader; | |
| 22 | +import java.io.StringWriter; | |
| 23 | +import java.nio.charset.Charset; | |
| 24 | + | |
| 25 | +/** Junk drawer of utility methods. */ | |
| 26 | +final class Util { | |
| 27 | + static final Charset US_ASCII = Charset.forName("US-ASCII"); | |
| 28 | + static final Charset UTF_8 = Charset.forName("UTF-8"); | |
| 29 | + | |
| 30 | + private Util() { | |
| 31 | + } | |
| 32 | + | |
| 33 | + static String readFully(Reader reader) throws IOException { | |
| 34 | + try { | |
| 35 | + StringWriter writer = new StringWriter(); | |
| 36 | + char[] buffer = new char[1024]; | |
| 37 | + int count; | |
| 38 | + while ((count = reader.read(buffer)) != -1) { | |
| 39 | + writer.write(buffer, 0, count); | |
| 40 | + } | |
| 41 | + return writer.toString(); | |
| 42 | + } finally { | |
| 43 | + reader.close(); | |
| 44 | + } | |
| 45 | + } | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * Deletes the contents of {@code dir}. Throws an IOException if any file | |
| 49 | + * could not be deleted, or if {@code dir} is not a readable directory. | |
| 50 | + */ | |
| 51 | + static void deleteContents(File dir) throws IOException { | |
| 52 | + File[] files = dir.listFiles(); | |
| 53 | + if (files == null) { | |
| 54 | + throw new IOException("not a readable directory: " + dir); | |
| 55 | + } | |
| 56 | + for (File file : files) { | |
| 57 | + if (file.isDirectory()) { | |
| 58 | + deleteContents(file); | |
| 59 | + } | |
| 60 | + if (!file.delete()) { | |
| 61 | + throw new IOException("failed to delete file: " + file); | |
| 62 | + } | |
| 63 | + } | |
| 64 | + } | |
| 65 | + | |
| 66 | + static void closeQuietly(/*Auto*/Closeable closeable) { | |
| 67 | + if (closeable != null) { | |
| 68 | + try { | |
| 69 | + closeable.close(); | |
| 70 | + } catch (RuntimeException rethrown) { | |
| 71 | + throw rethrown; | |
| 72 | + } catch (Exception ignored) { | |
| 73 | + } | |
| 74 | + } | |
| 75 | + } | |
| 76 | +} | |
| \ No newline at end of file | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/disc/naming/FileNameGenerator.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc.naming; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * Generates names for files at disk cache | |
| 20 | + * | |
| 21 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 22 | + * @since 1.3.1 | |
| 23 | + */ | |
| 24 | +public interface FileNameGenerator { | |
| 25 | + | |
| 26 | + /** Generates unique file name for image defined by URI */ | |
| 27 | + String generate(String imageUri); | |
| 28 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/disc/naming/HashCodeFileNameGenerator.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc.naming; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * Names image file as image URI {@linkplain String#hashCode() hashcode} | |
| 20 | + * | |
| 21 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 22 | + * @since 1.3.1 | |
| 23 | + */ | |
| 24 | +public class HashCodeFileNameGenerator implements FileNameGenerator { | |
| 25 | + @Override | |
| 26 | + public String generate(String imageUri) { | |
| 27 | + return String.valueOf(imageUri.hashCode()); | |
| 28 | + } | |
| 29 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/disc/naming/Md5FileNameGenerator.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.disc.naming; | |
| 17 | + | |
| 18 | +import com.nostra13.universalimageloader.utils.L; | |
| 19 | + | |
| 20 | +import java.math.BigInteger; | |
| 21 | +import java.security.MessageDigest; | |
| 22 | +import java.security.NoSuchAlgorithmException; | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * Names image file as MD5 hash of image URI | |
| 26 | + * | |
| 27 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 28 | + * @since 1.4.0 | |
| 29 | + */ | |
| 30 | +public class Md5FileNameGenerator implements FileNameGenerator { | |
| 31 | + | |
| 32 | + private static final String HASH_ALGORITHM = "MD5"; | |
| 33 | + private static final int RADIX = 10 + 26; // 10 digits + 26 letters | |
| 34 | + | |
| 35 | + @Override | |
| 36 | + public String generate(String imageUri) { | |
| 37 | + byte[] md5 = getMD5(imageUri.getBytes()); | |
| 38 | + BigInteger bi = new BigInteger(md5).abs(); | |
| 39 | + return bi.toString(RADIX); | |
| 40 | + } | |
| 41 | + | |
| 42 | + private byte[] getMD5(byte[] data) { | |
| 43 | + byte[] hash = null; | |
| 44 | + try { | |
| 45 | + MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); | |
| 46 | + digest.update(data); | |
| 47 | + hash = digest.digest(); | |
| 48 | + } catch (NoSuchAlgorithmException e) { | |
| 49 | + L.e(e); | |
| 50 | + } | |
| 51 | + return hash; | |
| 52 | + } | |
| 53 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.memory; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | + | |
| 20 | +import java.lang.ref.Reference; | |
| 21 | +import java.util.*; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * Base memory cache. Implements common functionality for memory cache. Provides object references ( | |
| 25 | + * {@linkplain Reference not strong}) storing. | |
| 26 | + * | |
| 27 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 28 | + * @since 1.0.0 | |
| 29 | + */ | |
| 30 | +public abstract class BaseMemoryCache implements MemoryCache { | |
| 31 | + | |
| 32 | + /** Stores not strong references to objects */ | |
| 33 | + private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(new HashMap<String, Reference<Bitmap>>()); | |
| 34 | + | |
| 35 | + @Override | |
| 36 | + public Bitmap get(String key) { | |
| 37 | + Bitmap result = null; | |
| 38 | + Reference<Bitmap> reference = softMap.get(key); | |
| 39 | + if (reference != null) { | |
| 40 | + result = reference.get(); | |
| 41 | + } | |
| 42 | + return result; | |
| 43 | + } | |
| 44 | + | |
| 45 | + @Override | |
| 46 | + public boolean put(String key, Bitmap value) { | |
| 47 | + softMap.put(key, createReference(value)); | |
| 48 | + return true; | |
| 49 | + } | |
| 50 | + | |
| 51 | + @Override | |
| 52 | + public Bitmap remove(String key) { | |
| 53 | + Reference<Bitmap> bmpRef = softMap.remove(key); | |
| 54 | + return bmpRef == null ? null : bmpRef.get(); | |
| 55 | + } | |
| 56 | + | |
| 57 | + @Override | |
| 58 | + public Collection<String> keys() { | |
| 59 | + synchronized (softMap) { | |
| 60 | + return new HashSet<String>(softMap.keySet()); | |
| 61 | + } | |
| 62 | + } | |
| 63 | + | |
| 64 | + @Override | |
| 65 | + public void clear() { | |
| 66 | + softMap.clear(); | |
| 67 | + } | |
| 68 | + | |
| 69 | + /** Creates {@linkplain Reference not strong} reference of value */ | |
| 70 | + protected abstract Reference<Bitmap> createReference(Bitmap value); | |
| 71 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.memory; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | + | |
| 20 | +import com.nostra13.universalimageloader.utils.L; | |
| 21 | + | |
| 22 | +import java.util.Collections; | |
| 23 | +import java.util.LinkedList; | |
| 24 | +import java.util.List; | |
| 25 | +import java.util.concurrent.atomic.AtomicInteger; | |
| 26 | + | |
| 27 | +/** | |
| 28 | + * Limited cache. Provides object storing. Size of all stored bitmaps will not to exceed size limit ( | |
| 29 | + * {@link #getSizeLimit()}).<br /> | |
| 30 | + * <br /> | |
| 31 | + * <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of | |
| 32 | + * Bitmaps (depends on cache size), weak references - for all other cached Bitmaps. | |
| 33 | + * | |
| 34 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 35 | + * @see BaseMemoryCache | |
| 36 | + * @since 1.0.0 | |
| 37 | + */ | |
| 38 | +public abstract class LimitedMemoryCache extends BaseMemoryCache { | |
| 39 | + | |
| 40 | + private static final int MAX_NORMAL_CACHE_SIZE_IN_MB = 16; | |
| 41 | + private static final int MAX_NORMAL_CACHE_SIZE = MAX_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024; | |
| 42 | + | |
| 43 | + private final int sizeLimit; | |
| 44 | + | |
| 45 | + private final AtomicInteger cacheSize; | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * Contains strong references to stored objects. Each next object is added last. If hard cache size will exceed | |
| 49 | + * limit then first object is deleted (but it continue exist at {@link #softMap} and can be collected by GC at any | |
| 50 | + * time) | |
| 51 | + */ | |
| 52 | + private final List<Bitmap> hardCache = Collections.synchronizedList(new LinkedList<Bitmap>()); | |
| 53 | + | |
| 54 | + /** @param sizeLimit Maximum size for cache (in bytes) */ | |
| 55 | + public LimitedMemoryCache(int sizeLimit) { | |
| 56 | + this.sizeLimit = sizeLimit; | |
| 57 | + cacheSize = new AtomicInteger(); | |
| 58 | + if (sizeLimit > MAX_NORMAL_CACHE_SIZE) { | |
| 59 | + L.w("You set too large memory cache size (more than %1$d Mb)", MAX_NORMAL_CACHE_SIZE_IN_MB); | |
| 60 | + } | |
| 61 | + } | |
| 62 | + | |
| 63 | + @Override | |
| 64 | + public boolean put(String key, Bitmap value) { | |
| 65 | + boolean putSuccessfully = false; | |
| 66 | + // Try to add value to hard cache | |
| 67 | + int valueSize = getSize(value); | |
| 68 | + int sizeLimit = getSizeLimit(); | |
| 69 | + int curCacheSize = cacheSize.get(); | |
| 70 | + if (valueSize < sizeLimit) { | |
| 71 | + while (curCacheSize + valueSize > sizeLimit) { | |
| 72 | + Bitmap removedValue = removeNext(); | |
| 73 | + if (hardCache.remove(removedValue)) { | |
| 74 | + curCacheSize = cacheSize.addAndGet(-getSize(removedValue)); | |
| 75 | + } | |
| 76 | + } | |
| 77 | + hardCache.add(value); | |
| 78 | + cacheSize.addAndGet(valueSize); | |
| 79 | + | |
| 80 | + putSuccessfully = true; | |
| 81 | + } | |
| 82 | + // Add value to soft cache | |
| 83 | + super.put(key, value); | |
| 84 | + return putSuccessfully; | |
| 85 | + } | |
| 86 | + | |
| 87 | + @Override | |
| 88 | + public Bitmap remove(String key) { | |
| 89 | + Bitmap value = super.get(key); | |
| 90 | + if (value != null) { | |
| 91 | + if (hardCache.remove(value)) { | |
| 92 | + cacheSize.addAndGet(-getSize(value)); | |
| 93 | + } | |
| 94 | + } | |
| 95 | + return super.remove(key); | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Override | |
| 99 | + public void clear() { | |
| 100 | + hardCache.clear(); | |
| 101 | + cacheSize.set(0); | |
| 102 | + super.clear(); | |
| 103 | + } | |
| 104 | + | |
| 105 | + protected int getSizeLimit() { | |
| 106 | + return sizeLimit; | |
| 107 | + } | |
| 108 | + | |
| 109 | + protected abstract int getSize(Bitmap value); | |
| 110 | + | |
| 111 | + protected abstract Bitmap removeNext(); | |
| 112 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.memory; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | + | |
| 20 | +import java.util.Collection; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * Interface for memory cache | |
| 24 | + * | |
| 25 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 26 | + * @since 1.9.2 | |
| 27 | + */ | |
| 28 | +public interface MemoryCache { | |
| 29 | + /** | |
| 30 | + * Puts value into cache by key | |
| 31 | + * | |
| 32 | + * @return <b>true</b> - if value was put into cache successfully, <b>false</b> - if value was <b>not</b> put into | |
| 33 | + * cache | |
| 34 | + */ | |
| 35 | + boolean put(String key, Bitmap value); | |
| 36 | + | |
| 37 | + /** Returns value by key. If there is no value for key then null will be returned. */ | |
| 38 | + Bitmap get(String key); | |
| 39 | + | |
| 40 | + /** Removes item by key */ | |
| 41 | + Bitmap remove(String key); | |
| 42 | + | |
| 43 | + /** Returns all keys of cache */ | |
| 44 | + Collection<String> keys(); | |
| 45 | + | |
| 46 | + /** Remove all items from cache */ | |
| 47 | + void clear(); | |
| 48 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/FIFOLimitedMemoryCache.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.memory.impl; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache; | |
| 20 | + | |
| 21 | +import java.lang.ref.Reference; | |
| 22 | +import java.lang.ref.WeakReference; | |
| 23 | +import java.util.Collections; | |
| 24 | +import java.util.LinkedList; | |
| 25 | +import java.util.List; | |
| 26 | + | |
| 27 | +/** | |
| 28 | + * Limited {@link Bitmap bitmap} cache. Provides {@link Bitmap bitmaps} storing. Size of all stored bitmaps will not to | |
| 29 | + * exceed size limit. When cache reaches limit size then cache clearing is processed by FIFO principle.<br /> | |
| 30 | + * <br /> | |
| 31 | + * <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of | |
| 32 | + * Bitmaps (depends on cache size), weak references - for all other cached Bitmaps. | |
| 33 | + * | |
| 34 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 35 | + * @since 1.0.0 | |
| 36 | + */ | |
| 37 | +public class FIFOLimitedMemoryCache extends LimitedMemoryCache { | |
| 38 | + | |
| 39 | + private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>()); | |
| 40 | + | |
| 41 | + public FIFOLimitedMemoryCache(int sizeLimit) { | |
| 42 | + super(sizeLimit); | |
| 43 | + } | |
| 44 | + | |
| 45 | + @Override | |
| 46 | + public boolean put(String key, Bitmap value) { | |
| 47 | + if (super.put(key, value)) { | |
| 48 | + queue.add(value); | |
| 49 | + return true; | |
| 50 | + } else { | |
| 51 | + return false; | |
| 52 | + } | |
| 53 | + } | |
| 54 | + | |
| 55 | + @Override | |
| 56 | + public Bitmap remove(String key) { | |
| 57 | + Bitmap value = super.get(key); | |
| 58 | + if (value != null) { | |
| 59 | + queue.remove(value); | |
| 60 | + } | |
| 61 | + return super.remove(key); | |
| 62 | + } | |
| 63 | + | |
| 64 | + @Override | |
| 65 | + public void clear() { | |
| 66 | + queue.clear(); | |
| 67 | + super.clear(); | |
| 68 | + } | |
| 69 | + | |
| 70 | + @Override | |
| 71 | + protected int getSize(Bitmap value) { | |
| 72 | + return value.getRowBytes() * value.getHeight(); | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Override | |
| 76 | + protected Bitmap removeNext() { | |
| 77 | + return queue.remove(0); | |
| 78 | + } | |
| 79 | + | |
| 80 | + @Override | |
| 81 | + protected Reference<Bitmap> createReference(Bitmap value) { | |
| 82 | + return new WeakReference<Bitmap>(value); | |
| 83 | + } | |
| 84 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/FuzzyKeyMemoryCache.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.memory.impl; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | + | |
| 20 | +import com.nostra13.universalimageloader.cache.memory.MemoryCache; | |
| 21 | + | |
| 22 | +import java.util.Collection; | |
| 23 | +import java.util.Comparator; | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * Decorator for {@link MemoryCache}. Provides special feature for cache: some different keys are considered as | |
| 27 | + * equals (using {@link Comparator comparator}). And when you try to put some value into cache by key so entries with | |
| 28 | + * "equals" keys will be removed from cache before.<br /> | |
| 29 | + * <b>NOTE:</b> Used for internal needs. Normally you don't need to use this class. | |
| 30 | + * | |
| 31 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 32 | + * @since 1.0.0 | |
| 33 | + */ | |
| 34 | +public class FuzzyKeyMemoryCache implements MemoryCache { | |
| 35 | + | |
| 36 | + private final MemoryCache cache; | |
| 37 | + private final Comparator<String> keyComparator; | |
| 38 | + | |
| 39 | + public FuzzyKeyMemoryCache(MemoryCache cache, Comparator<String> keyComparator) { | |
| 40 | + this.cache = cache; | |
| 41 | + this.keyComparator = keyComparator; | |
| 42 | + } | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + public boolean put(String key, Bitmap value) { | |
| 46 | + // Search equal key and remove this entry | |
| 47 | + synchronized (cache) { | |
| 48 | + String keyToRemove = null; | |
| 49 | + for (String cacheKey : cache.keys()) { | |
| 50 | + if (keyComparator.compare(key, cacheKey) == 0) { | |
| 51 | + keyToRemove = cacheKey; | |
| 52 | + break; | |
| 53 | + } | |
| 54 | + } | |
| 55 | + if (keyToRemove != null) { | |
| 56 | + cache.remove(keyToRemove); | |
| 57 | + } | |
| 58 | + } | |
| 59 | + return cache.put(key, value); | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Override | |
| 63 | + public Bitmap get(String key) { | |
| 64 | + return cache.get(key); | |
| 65 | + } | |
| 66 | + | |
| 67 | + @Override | |
| 68 | + public Bitmap remove(String key) { | |
| 69 | + return cache.remove(key); | |
| 70 | + } | |
| 71 | + | |
| 72 | + @Override | |
| 73 | + public void clear() { | |
| 74 | + cache.clear(); | |
| 75 | + } | |
| 76 | + | |
| 77 | + @Override | |
| 78 | + public Collection<String> keys() { | |
| 79 | + return cache.keys(); | |
| 80 | + } | |
| 81 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LRULimitedMemoryCache.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.memory.impl; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache; | |
| 20 | + | |
| 21 | +import java.lang.ref.Reference; | |
| 22 | +import java.lang.ref.WeakReference; | |
| 23 | +import java.util.Collections; | |
| 24 | +import java.util.Iterator; | |
| 25 | +import java.util.LinkedHashMap; | |
| 26 | +import java.util.Map; | |
| 27 | +import java.util.Map.Entry; | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * Limited {@link Bitmap bitmap} cache. Provides {@link Bitmap bitmaps} storing. Size of all stored bitmaps will not to | |
| 31 | + * exceed size limit. When cache reaches limit size then the least recently used bitmap is deleted from cache.<br /> | |
| 32 | + * <br /> | |
| 33 | + * <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of | |
| 34 | + * Bitmaps (depends on cache size), weak references - for all other cached Bitmaps. | |
| 35 | + * | |
| 36 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 37 | + * @since 1.3.0 | |
| 38 | + */ | |
| 39 | +public class LRULimitedMemoryCache extends LimitedMemoryCache { | |
| 40 | + | |
| 41 | + private static final int INITIAL_CAPACITY = 10; | |
| 42 | + private static final float LOAD_FACTOR = 1.1f; | |
| 43 | + | |
| 44 | + /** Cache providing Least-Recently-Used logic */ | |
| 45 | + private final Map<String, Bitmap> lruCache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(INITIAL_CAPACITY, LOAD_FACTOR, true)); | |
| 46 | + | |
| 47 | + /** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */ | |
| 48 | + public LRULimitedMemoryCache(int maxSize) { | |
| 49 | + super(maxSize); | |
| 50 | + } | |
| 51 | + | |
| 52 | + @Override | |
| 53 | + public boolean put(String key, Bitmap value) { | |
| 54 | + if (super.put(key, value)) { | |
| 55 | + lruCache.put(key, value); | |
| 56 | + return true; | |
| 57 | + } else { | |
| 58 | + return false; | |
| 59 | + } | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Override | |
| 63 | + public Bitmap get(String key) { | |
| 64 | + lruCache.get(key); // call "get" for LRU logic | |
| 65 | + return super.get(key); | |
| 66 | + } | |
| 67 | + | |
| 68 | + @Override | |
| 69 | + public Bitmap remove(String key) { | |
| 70 | + lruCache.remove(key); | |
| 71 | + return super.remove(key); | |
| 72 | + } | |
| 73 | + | |
| 74 | + @Override | |
| 75 | + public void clear() { | |
| 76 | + lruCache.clear(); | |
| 77 | + super.clear(); | |
| 78 | + } | |
| 79 | + | |
| 80 | + @Override | |
| 81 | + protected int getSize(Bitmap value) { | |
| 82 | + return value.getRowBytes() * value.getHeight(); | |
| 83 | + } | |
| 84 | + | |
| 85 | + @Override | |
| 86 | + protected Bitmap removeNext() { | |
| 87 | + Bitmap mostLongUsedValue = null; | |
| 88 | + synchronized (lruCache) { | |
| 89 | + Iterator<Entry<String, Bitmap>> it = lruCache.entrySet().iterator(); | |
| 90 | + if (it.hasNext()) { | |
| 91 | + Entry<String, Bitmap> entry = it.next(); | |
| 92 | + mostLongUsedValue = entry.getValue(); | |
| 93 | + it.remove(); | |
| 94 | + } | |
| 95 | + } | |
| 96 | + return mostLongUsedValue; | |
| 97 | + } | |
| 98 | + | |
| 99 | + @Override | |
| 100 | + protected Reference<Bitmap> createReference(Bitmap value) { | |
| 101 | + return new WeakReference<Bitmap>(value); | |
| 102 | + } | |
| 103 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LargestLimitedMemoryCache.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.memory.impl; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache; | |
| 20 | + | |
| 21 | +import java.lang.ref.Reference; | |
| 22 | +import java.lang.ref.WeakReference; | |
| 23 | +import java.util.Collections; | |
| 24 | +import java.util.HashMap; | |
| 25 | +import java.util.Map; | |
| 26 | +import java.util.Map.Entry; | |
| 27 | +import java.util.Set; | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * Limited {@link Bitmap bitmap} cache. Provides {@link Bitmap bitmaps} storing. Size of all stored bitmaps will not to | |
| 31 | + * exceed size limit. When cache reaches limit size then the bitmap which has the largest size is deleted from | |
| 32 | + * cache.<br /> | |
| 33 | + * <br /> | |
| 34 | + * <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of | |
| 35 | + * Bitmaps (depends on cache size), weak references - for all other cached Bitmaps. | |
| 36 | + * | |
| 37 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 38 | + * @since 1.0.0 | |
| 39 | + */ | |
| 40 | +public class LargestLimitedMemoryCache extends LimitedMemoryCache { | |
| 41 | + /** | |
| 42 | + * Contains strong references to stored objects (keys) and sizes of the objects. If hard cache | |
| 43 | + * size will exceed limit then object with the largest size is deleted (but it continue exist at | |
| 44 | + * {@link #softMap} and can be collected by GC at any time) | |
| 45 | + */ | |
| 46 | + private final Map<Bitmap, Integer> valueSizes = Collections.synchronizedMap(new HashMap<Bitmap, Integer>()); | |
| 47 | + | |
| 48 | + public LargestLimitedMemoryCache(int sizeLimit) { | |
| 49 | + super(sizeLimit); | |
| 50 | + } | |
| 51 | + | |
| 52 | + @Override | |
| 53 | + public boolean put(String key, Bitmap value) { | |
| 54 | + if (super.put(key, value)) { | |
| 55 | + valueSizes.put(value, getSize(value)); | |
| 56 | + return true; | |
| 57 | + } else { | |
| 58 | + return false; | |
| 59 | + } | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Override | |
| 63 | + public Bitmap remove(String key) { | |
| 64 | + Bitmap value = super.get(key); | |
| 65 | + if (value != null) { | |
| 66 | + valueSizes.remove(value); | |
| 67 | + } | |
| 68 | + return super.remove(key); | |
| 69 | + } | |
| 70 | + | |
| 71 | + @Override | |
| 72 | + public void clear() { | |
| 73 | + valueSizes.clear(); | |
| 74 | + super.clear(); | |
| 75 | + } | |
| 76 | + | |
| 77 | + @Override | |
| 78 | + protected int getSize(Bitmap value) { | |
| 79 | + return value.getRowBytes() * value.getHeight(); | |
| 80 | + } | |
| 81 | + | |
| 82 | + @Override | |
| 83 | + protected Bitmap removeNext() { | |
| 84 | + Integer maxSize = null; | |
| 85 | + Bitmap largestValue = null; | |
| 86 | + Set<Entry<Bitmap, Integer>> entries = valueSizes.entrySet(); | |
| 87 | + synchronized (valueSizes) { | |
| 88 | + for (Entry<Bitmap, Integer> entry : entries) { | |
| 89 | + if (largestValue == null) { | |
| 90 | + largestValue = entry.getKey(); | |
| 91 | + maxSize = entry.getValue(); | |
| 92 | + } else { | |
| 93 | + Integer size = entry.getValue(); | |
| 94 | + if (size > maxSize) { | |
| 95 | + maxSize = size; | |
| 96 | + largestValue = entry.getKey(); | |
| 97 | + } | |
| 98 | + } | |
| 99 | + } | |
| 100 | + } | |
| 101 | + valueSizes.remove(largestValue); | |
| 102 | + return largestValue; | |
| 103 | + } | |
| 104 | + | |
| 105 | + @Override | |
| 106 | + protected Reference<Bitmap> createReference(Bitmap value) { | |
| 107 | + return new WeakReference<Bitmap>(value); | |
| 108 | + } | |
| 109 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LimitedAgeMemoryCache.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.memory.impl; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | + | |
| 20 | +import com.nostra13.universalimageloader.cache.memory.MemoryCache; | |
| 21 | + | |
| 22 | +import java.util.Collection; | |
| 23 | +import java.util.Collections; | |
| 24 | +import java.util.HashMap; | |
| 25 | +import java.util.Map; | |
| 26 | + | |
| 27 | +/** | |
| 28 | + * Decorator for {@link MemoryCache}. Provides special feature for cache: if some cached object age exceeds defined | |
| 29 | + * value then this object will be removed from cache. | |
| 30 | + * | |
| 31 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 32 | + * @see MemoryCache | |
| 33 | + * @since 1.3.1 | |
| 34 | + */ | |
| 35 | +public class LimitedAgeMemoryCache implements MemoryCache { | |
| 36 | + | |
| 37 | + private final MemoryCache cache; | |
| 38 | + | |
| 39 | + private final long maxAge; | |
| 40 | + private final Map<String, Long> loadingDates = Collections.synchronizedMap(new HashMap<String, Long>()); | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * @param cache Wrapped memory cache | |
| 44 | + * @param maxAge Max object age <b>(in seconds)</b>. If object age will exceed this value then it'll be removed from | |
| 45 | + * cache on next treatment (and therefore be reloaded). | |
| 46 | + */ | |
| 47 | + public LimitedAgeMemoryCache(MemoryCache cache, long maxAge) { | |
| 48 | + this.cache = cache; | |
| 49 | + this.maxAge = maxAge * 1000; // to milliseconds | |
| 50 | + } | |
| 51 | + | |
| 52 | + @Override | |
| 53 | + public boolean put(String key, Bitmap value) { | |
| 54 | + boolean putSuccesfully = cache.put(key, value); | |
| 55 | + if (putSuccesfully) { | |
| 56 | + loadingDates.put(key, System.currentTimeMillis()); | |
| 57 | + } | |
| 58 | + return putSuccesfully; | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Override | |
| 62 | + public Bitmap get(String key) { | |
| 63 | + Long loadingDate = loadingDates.get(key); | |
| 64 | + if (loadingDate != null && System.currentTimeMillis() - loadingDate > maxAge) { | |
| 65 | + cache.remove(key); | |
| 66 | + loadingDates.remove(key); | |
| 67 | + } | |
| 68 | + | |
| 69 | + return cache.get(key); | |
| 70 | + } | |
| 71 | + | |
| 72 | + @Override | |
| 73 | + public Bitmap remove(String key) { | |
| 74 | + loadingDates.remove(key); | |
| 75 | + return cache.remove(key); | |
| 76 | + } | |
| 77 | + | |
| 78 | + @Override | |
| 79 | + public Collection<String> keys() { | |
| 80 | + return cache.keys(); | |
| 81 | + } | |
| 82 | + | |
| 83 | + @Override | |
| 84 | + public void clear() { | |
| 85 | + cache.clear(); | |
| 86 | + loadingDates.clear(); | |
| 87 | + } | |
| 88 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LruMemoryCache.java
0 → 100644
| 1 | +package com.nostra13.universalimageloader.cache.memory.impl; | |
| 2 | + | |
| 3 | +import android.graphics.Bitmap; | |
| 4 | + | |
| 5 | +import com.nostra13.universalimageloader.cache.memory.MemoryCache; | |
| 6 | + | |
| 7 | +import java.util.Collection; | |
| 8 | +import java.util.HashSet; | |
| 9 | +import java.util.LinkedHashMap; | |
| 10 | +import java.util.Map; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to | |
| 14 | + * 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 | |
| 15 | + * become eligible for garbage collection.<br /> | |
| 16 | + * <br /> | |
| 17 | + * <b>NOTE:</b> This cache uses only strong references for stored Bitmaps. | |
| 18 | + * | |
| 19 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 20 | + * @since 1.8.1 | |
| 21 | + */ | |
| 22 | +public class LruMemoryCache implements MemoryCache { | |
| 23 | + | |
| 24 | + private final LinkedHashMap<String, Bitmap> map; | |
| 25 | + | |
| 26 | + private final int maxSize; | |
| 27 | + /** Size of this cache in bytes */ | |
| 28 | + private int size; | |
| 29 | + | |
| 30 | + /** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */ | |
| 31 | + public LruMemoryCache(int maxSize) { | |
| 32 | + if (maxSize <= 0) { | |
| 33 | + throw new IllegalArgumentException("maxSize <= 0"); | |
| 34 | + } | |
| 35 | + this.maxSize = maxSize; | |
| 36 | + this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true); | |
| 37 | + } | |
| 38 | + | |
| 39 | + /** | |
| 40 | + * Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head | |
| 41 | + * of the queue. This returns null if a Bitmap is not cached. | |
| 42 | + */ | |
| 43 | + @Override | |
| 44 | + public final Bitmap get(String key) { | |
| 45 | + if (key == null) { | |
| 46 | + throw new NullPointerException("key == null"); | |
| 47 | + } | |
| 48 | + | |
| 49 | + synchronized (this) { | |
| 50 | + return map.get(key); | |
| 51 | + } | |
| 52 | + } | |
| 53 | + | |
| 54 | + /** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue. */ | |
| 55 | + @Override | |
| 56 | + public final boolean put(String key, Bitmap value) { | |
| 57 | + if (key == null || value == null) { | |
| 58 | + throw new NullPointerException("key == null || value == null"); | |
| 59 | + } | |
| 60 | + | |
| 61 | + synchronized (this) { | |
| 62 | + size += sizeOf(key, value); | |
| 63 | + Bitmap previous = map.put(key, value); | |
| 64 | + if (previous != null) { | |
| 65 | + size -= sizeOf(key, previous); | |
| 66 | + } | |
| 67 | + } | |
| 68 | + | |
| 69 | + trimToSize(maxSize); | |
| 70 | + return true; | |
| 71 | + } | |
| 72 | + | |
| 73 | + /** | |
| 74 | + * Remove the eldest entries until the total of remaining entries is at or below the requested size. | |
| 75 | + * | |
| 76 | + * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements. | |
| 77 | + */ | |
| 78 | + private void trimToSize(int maxSize) { | |
| 79 | + while (true) { | |
| 80 | + String key; | |
| 81 | + Bitmap value; | |
| 82 | + synchronized (this) { | |
| 83 | + if (size < 0 || (map.isEmpty() && size != 0)) { | |
| 84 | + throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!"); | |
| 85 | + } | |
| 86 | + | |
| 87 | + if (size <= maxSize || map.isEmpty()) { | |
| 88 | + break; | |
| 89 | + } | |
| 90 | + | |
| 91 | + Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next(); | |
| 92 | + if (toEvict == null) { | |
| 93 | + break; | |
| 94 | + } | |
| 95 | + key = toEvict.getKey(); | |
| 96 | + value = toEvict.getValue(); | |
| 97 | + map.remove(key); | |
| 98 | + size -= sizeOf(key, value); | |
| 99 | + } | |
| 100 | + } | |
| 101 | + } | |
| 102 | + | |
| 103 | + /** Removes the entry for {@code key} if it exists. */ | |
| 104 | + @Override | |
| 105 | + public final Bitmap remove(String key) { | |
| 106 | + if (key == null) { | |
| 107 | + throw new NullPointerException("key == null"); | |
| 108 | + } | |
| 109 | + | |
| 110 | + synchronized (this) { | |
| 111 | + Bitmap previous = map.remove(key); | |
| 112 | + if (previous != null) { | |
| 113 | + size -= sizeOf(key, previous); | |
| 114 | + } | |
| 115 | + return previous; | |
| 116 | + } | |
| 117 | + } | |
| 118 | + | |
| 119 | + @Override | |
| 120 | + public Collection<String> keys() { | |
| 121 | + synchronized (this) { | |
| 122 | + return new HashSet<String>(map.keySet()); | |
| 123 | + } | |
| 124 | + } | |
| 125 | + | |
| 126 | + @Override | |
| 127 | + public void clear() { | |
| 128 | + trimToSize(-1); // -1 will evict 0-sized elements | |
| 129 | + } | |
| 130 | + | |
| 131 | + /** | |
| 132 | + * Returns the size {@code Bitmap} in bytes. | |
| 133 | + * <p/> | |
| 134 | + * An entry's size must not change while it is in the cache. | |
| 135 | + */ | |
| 136 | + private int sizeOf(String key, Bitmap value) { | |
| 137 | + return value.getRowBytes() * value.getHeight(); | |
| 138 | + } | |
| 139 | + | |
| 140 | + @Override | |
| 141 | + public synchronized final String toString() { | |
| 142 | + return String.format("LruCache[maxSize=%d]", maxSize); | |
| 143 | + } | |
| 144 | +} | |
| \ No newline at end of file | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.memory.impl; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.cache.memory.LimitedMemoryCache; | |
| 20 | + | |
| 21 | +import java.lang.ref.Reference; | |
| 22 | +import java.lang.ref.WeakReference; | |
| 23 | +import java.util.Collections; | |
| 24 | +import java.util.HashMap; | |
| 25 | +import java.util.Map; | |
| 26 | +import java.util.Map.Entry; | |
| 27 | +import java.util.Set; | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * Limited {@link Bitmap bitmap} cache. Provides {@link Bitmap bitmaps} storing. Size of all stored bitmaps will not to | |
| 31 | + * exceed size limit. When cache reaches limit size then the bitmap which used the least frequently is deleted from | |
| 32 | + * cache.<br /> | |
| 33 | + * <br /> | |
| 34 | + * <b>NOTE:</b> This cache uses strong and weak references for stored Bitmaps. Strong references - for limited count of | |
| 35 | + * Bitmaps (depends on cache size), weak references - for all other cached Bitmaps. | |
| 36 | + * | |
| 37 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 38 | + * @since 1.0.0 | |
| 39 | + */ | |
| 40 | +public class UsingFreqLimitedMemoryCache extends LimitedMemoryCache { | |
| 41 | + /** | |
| 42 | + * Contains strong references to stored objects (keys) and last object usage date (in milliseconds). If hard cache | |
| 43 | + * size will exceed limit then object with the least frequently usage is deleted (but it continue exist at | |
| 44 | + * {@link #softMap} and can be collected by GC at any time) | |
| 45 | + */ | |
| 46 | + private final Map<Bitmap, Integer> usingCounts = Collections.synchronizedMap(new HashMap<Bitmap, Integer>()); | |
| 47 | + | |
| 48 | + public UsingFreqLimitedMemoryCache(int sizeLimit) { | |
| 49 | + super(sizeLimit); | |
| 50 | + } | |
| 51 | + | |
| 52 | + @Override | |
| 53 | + public boolean put(String key, Bitmap value) { | |
| 54 | + if (super.put(key, value)) { | |
| 55 | + usingCounts.put(value, 0); | |
| 56 | + return true; | |
| 57 | + } else { | |
| 58 | + return false; | |
| 59 | + } | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Override | |
| 63 | + public Bitmap get(String key) { | |
| 64 | + Bitmap value = super.get(key); | |
| 65 | + // Increment usage count for value if value is contained in hardCahe | |
| 66 | + if (value != null) { | |
| 67 | + Integer usageCount = usingCounts.get(value); | |
| 68 | + if (usageCount != null) { | |
| 69 | + usingCounts.put(value, usageCount + 1); | |
| 70 | + } | |
| 71 | + } | |
| 72 | + return value; | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Override | |
| 76 | + public Bitmap remove(String key) { | |
| 77 | + Bitmap value = super.get(key); | |
| 78 | + if (value != null) { | |
| 79 | + usingCounts.remove(value); | |
| 80 | + } | |
| 81 | + return super.remove(key); | |
| 82 | + } | |
| 83 | + | |
| 84 | + @Override | |
| 85 | + public void clear() { | |
| 86 | + usingCounts.clear(); | |
| 87 | + super.clear(); | |
| 88 | + } | |
| 89 | + | |
| 90 | + @Override | |
| 91 | + protected int getSize(Bitmap value) { | |
| 92 | + return value.getRowBytes() * value.getHeight(); | |
| 93 | + } | |
| 94 | + | |
| 95 | + @Override | |
| 96 | + protected Bitmap removeNext() { | |
| 97 | + Integer minUsageCount = null; | |
| 98 | + Bitmap leastUsedValue = null; | |
| 99 | + Set<Entry<Bitmap, Integer>> entries = usingCounts.entrySet(); | |
| 100 | + synchronized (usingCounts) { | |
| 101 | + for (Entry<Bitmap, Integer> entry : entries) { | |
| 102 | + if (leastUsedValue == null) { | |
| 103 | + leastUsedValue = entry.getKey(); | |
| 104 | + minUsageCount = entry.getValue(); | |
| 105 | + } else { | |
| 106 | + Integer lastValueUsage = entry.getValue(); | |
| 107 | + if (lastValueUsage < minUsageCount) { | |
| 108 | + minUsageCount = lastValueUsage; | |
| 109 | + leastUsedValue = entry.getKey(); | |
| 110 | + } | |
| 111 | + } | |
| 112 | + } | |
| 113 | + } | |
| 114 | + usingCounts.remove(leastUsedValue); | |
| 115 | + return leastUsedValue; | |
| 116 | + } | |
| 117 | + | |
| 118 | + @Override | |
| 119 | + protected Reference<Bitmap> createReference(Bitmap value) { | |
| 120 | + return new WeakReference<Bitmap>(value); | |
| 121 | + } | |
| 122 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/WeakMemoryCache.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.cache.memory.impl; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.cache.memory.BaseMemoryCache; | |
| 20 | + | |
| 21 | +import java.lang.ref.Reference; | |
| 22 | +import java.lang.ref.WeakReference; | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * Memory cache with {@linkplain WeakReference weak references} to {@linkplain Bitmap bitmaps}<br /> | |
| 26 | + * <br /> | |
| 27 | + * <b>NOTE:</b> This cache uses only weak references for stored Bitmaps. | |
| 28 | + * | |
| 29 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 30 | + * @since 1.5.3 | |
| 31 | + */ | |
| 32 | +public class WeakMemoryCache extends BaseMemoryCache { | |
| 33 | + @Override | |
| 34 | + protected Reference<Bitmap> createReference(Bitmap value) { | |
| 35 | + return new WeakReference<Bitmap>(value); | |
| 36 | + } | |
| 37 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/DefaultConfigurationFactory.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core; | |
| 17 | + | |
| 18 | +import android.annotation.TargetApi; | |
| 19 | +import android.app.ActivityManager; | |
| 20 | +import android.content.Context; | |
| 21 | +import android.content.pm.ApplicationInfo; | |
| 22 | +import android.os.Build; | |
| 23 | +import com.nostra13.universalimageloader.cache.disc.DiskCache; | |
| 24 | +import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache; | |
| 25 | +import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache; | |
| 26 | +import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; | |
| 27 | +import com.nostra13.universalimageloader.cache.disc.naming.HashCodeFileNameGenerator; | |
| 28 | +import com.nostra13.universalimageloader.cache.memory.MemoryCache; | |
| 29 | +import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache; | |
| 30 | +import com.nostra13.universalimageloader.core.assist.QueueProcessingType; | |
| 31 | +import com.nostra13.universalimageloader.core.assist.deque.LIFOLinkedBlockingDeque; | |
| 32 | +import com.nostra13.universalimageloader.core.decode.BaseImageDecoder; | |
| 33 | +import com.nostra13.universalimageloader.core.decode.ImageDecoder; | |
| 34 | +import com.nostra13.universalimageloader.core.display.BitmapDisplayer; | |
| 35 | +import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer; | |
| 36 | +import com.nostra13.universalimageloader.core.download.BaseImageDownloader; | |
| 37 | +import com.nostra13.universalimageloader.core.download.ImageDownloader; | |
| 38 | +import com.nostra13.universalimageloader.utils.L; | |
| 39 | +import com.nostra13.universalimageloader.utils.StorageUtils; | |
| 40 | + | |
| 41 | +import java.io.File; | |
| 42 | +import java.io.IOException; | |
| 43 | +import java.util.concurrent.BlockingQueue; | |
| 44 | +import java.util.concurrent.Executor; | |
| 45 | +import java.util.concurrent.Executors; | |
| 46 | +import java.util.concurrent.LinkedBlockingQueue; | |
| 47 | +import java.util.concurrent.ThreadFactory; | |
| 48 | +import java.util.concurrent.ThreadPoolExecutor; | |
| 49 | +import java.util.concurrent.TimeUnit; | |
| 50 | +import java.util.concurrent.atomic.AtomicInteger; | |
| 51 | + | |
| 52 | +/** | |
| 53 | + * Factory for providing of default options for {@linkplain ImageLoaderConfiguration configuration} | |
| 54 | + * | |
| 55 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 56 | + * @since 1.5.6 | |
| 57 | + */ | |
| 58 | +public class DefaultConfigurationFactory { | |
| 59 | + | |
| 60 | + /** Creates default implementation of task executor */ | |
| 61 | + public static Executor createExecutor(int threadPoolSize, int threadPriority, | |
| 62 | + QueueProcessingType tasksProcessingType) { | |
| 63 | + boolean lifo = tasksProcessingType == QueueProcessingType.LIFO; | |
| 64 | + BlockingQueue<Runnable> taskQueue = | |
| 65 | + lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>(); | |
| 66 | + return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue, | |
| 67 | + createThreadFactory(threadPriority, "uil-pool-")); | |
| 68 | + } | |
| 69 | + | |
| 70 | + /** Creates default implementation of task distributor */ | |
| 71 | + public static Executor createTaskDistributor() { | |
| 72 | + return Executors.newCachedThreadPool(createThreadFactory(Thread.NORM_PRIORITY, "uil-pool-d-")); | |
| 73 | + } | |
| 74 | + | |
| 75 | + /** Creates {@linkplain HashCodeFileNameGenerator default implementation} of FileNameGenerator */ | |
| 76 | + public static FileNameGenerator createFileNameGenerator() { | |
| 77 | + return new HashCodeFileNameGenerator(); | |
| 78 | + } | |
| 79 | + | |
| 80 | + /** | |
| 81 | + * Creates default implementation of {@link DiskCache} depends on incoming parameters | |
| 82 | + */ | |
| 83 | + public static DiskCache createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator, | |
| 84 | + long diskCacheSize, int diskCacheFileCount) { | |
| 85 | + File reserveCacheDir = createReserveDiskCacheDir(context); | |
| 86 | + if (diskCacheSize > 0 || diskCacheFileCount > 0) { | |
| 87 | + File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context); | |
| 88 | + try { | |
| 89 | + return new LruDiskCache(individualCacheDir, reserveCacheDir, diskCacheFileNameGenerator, diskCacheSize, | |
| 90 | + diskCacheFileCount); | |
| 91 | + } catch (IOException e) { | |
| 92 | + L.e(e); | |
| 93 | + // continue and create unlimited cache | |
| 94 | + } | |
| 95 | + } | |
| 96 | + File cacheDir = StorageUtils.getCacheDirectory(context); | |
| 97 | + return new UnlimitedDiskCache(cacheDir, reserveCacheDir, diskCacheFileNameGenerator); | |
| 98 | + } | |
| 99 | + | |
| 100 | + /** Creates reserve disk cache folder which will be used if primary disk cache folder becomes unavailable */ | |
| 101 | + private static File createReserveDiskCacheDir(Context context) { | |
| 102 | + File cacheDir = StorageUtils.getCacheDirectory(context, false); | |
| 103 | + File individualDir = new File(cacheDir, "uil-images"); | |
| 104 | + if (individualDir.exists() || individualDir.mkdir()) { | |
| 105 | + cacheDir = individualDir; | |
| 106 | + } | |
| 107 | + return cacheDir; | |
| 108 | + } | |
| 109 | + | |
| 110 | + /** | |
| 111 | + * Creates default implementation of {@link MemoryCache} - {@link LruMemoryCache}<br /> | |
| 112 | + * Default cache size = 1/8 of available app memory. | |
| 113 | + */ | |
| 114 | + public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) { | |
| 115 | + if (memoryCacheSize == 0) { | |
| 116 | + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); | |
| 117 | + int memoryClass = am.getMemoryClass(); | |
| 118 | + if (hasHoneycomb() && isLargeHeap(context)) { | |
| 119 | + memoryClass = getLargeMemoryClass(am); | |
| 120 | + } | |
| 121 | + memoryCacheSize = 1024 * 1024 * memoryClass / 8; | |
| 122 | + } | |
| 123 | + return new LruMemoryCache(memoryCacheSize); | |
| 124 | + } | |
| 125 | + | |
| 126 | + private static boolean hasHoneycomb() { | |
| 127 | + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; | |
| 128 | + } | |
| 129 | + | |
| 130 | + @TargetApi(Build.VERSION_CODES.HONEYCOMB) | |
| 131 | + private static boolean isLargeHeap(Context context) { | |
| 132 | + return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_LARGE_HEAP) != 0; | |
| 133 | + } | |
| 134 | + | |
| 135 | + @TargetApi(Build.VERSION_CODES.HONEYCOMB) | |
| 136 | + private static int getLargeMemoryClass(ActivityManager am) { | |
| 137 | + return am.getLargeMemoryClass(); | |
| 138 | + } | |
| 139 | + | |
| 140 | + /** Creates default implementation of {@link ImageDownloader} - {@link BaseImageDownloader} */ | |
| 141 | + public static ImageDownloader createImageDownloader(Context context) { | |
| 142 | + return new BaseImageDownloader(context); | |
| 143 | + } | |
| 144 | + | |
| 145 | + /** Creates default implementation of {@link ImageDecoder} - {@link BaseImageDecoder} */ | |
| 146 | + public static ImageDecoder createImageDecoder(boolean loggingEnabled) { | |
| 147 | + return new BaseImageDecoder(loggingEnabled); | |
| 148 | + } | |
| 149 | + | |
| 150 | + /** Creates default implementation of {@link BitmapDisplayer} - {@link SimpleBitmapDisplayer} */ | |
| 151 | + public static BitmapDisplayer createBitmapDisplayer() { | |
| 152 | + return new SimpleBitmapDisplayer(); | |
| 153 | + } | |
| 154 | + | |
| 155 | + /** Creates default implementation of {@linkplain ThreadFactory thread factory} for task executor */ | |
| 156 | + private static ThreadFactory createThreadFactory(int threadPriority, String threadNamePrefix) { | |
| 157 | + return new DefaultThreadFactory(threadPriority, threadNamePrefix); | |
| 158 | + } | |
| 159 | + | |
| 160 | + private static class DefaultThreadFactory implements ThreadFactory { | |
| 161 | + | |
| 162 | + private static final AtomicInteger poolNumber = new AtomicInteger(1); | |
| 163 | + | |
| 164 | + private final ThreadGroup group; | |
| 165 | + private final AtomicInteger threadNumber = new AtomicInteger(1); | |
| 166 | + private final String namePrefix; | |
| 167 | + private final int threadPriority; | |
| 168 | + | |
| 169 | + DefaultThreadFactory(int threadPriority, String threadNamePrefix) { | |
| 170 | + this.threadPriority = threadPriority; | |
| 171 | + group = Thread.currentThread().getThreadGroup(); | |
| 172 | + namePrefix = threadNamePrefix + poolNumber.getAndIncrement() + "-thread-"; | |
| 173 | + } | |
| 174 | + | |
| 175 | + @Override | |
| 176 | + public Thread newThread(Runnable r) { | |
| 177 | + Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); | |
| 178 | + if (t.isDaemon()) t.setDaemon(false); | |
| 179 | + t.setPriority(threadPriority); | |
| 180 | + return t; | |
| 181 | + } | |
| 182 | + } | |
| 183 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.core.assist.LoadedFrom; | |
| 20 | +import com.nostra13.universalimageloader.core.display.BitmapDisplayer; | |
| 21 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 22 | +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; | |
| 23 | +import com.nostra13.universalimageloader.utils.L; | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * Displays bitmap in {@link ImageAware}. Must be called on UI thread. | |
| 27 | + * | |
| 28 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 29 | + * @see ImageLoadingListener | |
| 30 | + * @see BitmapDisplayer | |
| 31 | + * @since 1.3.1 | |
| 32 | + */ | |
| 33 | +final class DisplayBitmapTask implements Runnable { | |
| 34 | + | |
| 35 | + private static final String LOG_DISPLAY_IMAGE_IN_IMAGEAWARE = "Display image in ImageAware (loaded from %1$s) [%2$s]"; | |
| 36 | + private static final String LOG_TASK_CANCELLED_IMAGEAWARE_REUSED = "ImageAware is reused for another image. Task is cancelled. [%s]"; | |
| 37 | + private static final String LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED = "ImageAware was collected by GC. Task is cancelled. [%s]"; | |
| 38 | + | |
| 39 | + private final Bitmap bitmap; | |
| 40 | + private final String imageUri; | |
| 41 | + private final ImageAware imageAware; | |
| 42 | + private final String memoryCacheKey; | |
| 43 | + private final BitmapDisplayer displayer; | |
| 44 | + private final ImageLoadingListener listener; | |
| 45 | + private final ImageLoaderEngine engine; | |
| 46 | + private final LoadedFrom loadedFrom; | |
| 47 | + | |
| 48 | + public DisplayBitmapTask(Bitmap bitmap, ImageLoadingInfo imageLoadingInfo, ImageLoaderEngine engine, | |
| 49 | + LoadedFrom loadedFrom) { | |
| 50 | + this.bitmap = bitmap; | |
| 51 | + imageUri = imageLoadingInfo.uri; | |
| 52 | + imageAware = imageLoadingInfo.imageAware; | |
| 53 | + memoryCacheKey = imageLoadingInfo.memoryCacheKey; | |
| 54 | + displayer = imageLoadingInfo.options.getDisplayer(); | |
| 55 | + listener = imageLoadingInfo.listener; | |
| 56 | + this.engine = engine; | |
| 57 | + this.loadedFrom = loadedFrom; | |
| 58 | + } | |
| 59 | + | |
| 60 | + @Override | |
| 61 | + public void run() { | |
| 62 | + if (imageAware.isCollected()) { | |
| 63 | + L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey); | |
| 64 | + listener.onLoadingCancelled(imageUri, imageAware.getWrappedView()); | |
| 65 | + } else if (isViewWasReused()) { | |
| 66 | + L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey); | |
| 67 | + listener.onLoadingCancelled(imageUri, imageAware.getWrappedView()); | |
| 68 | + } else { | |
| 69 | + L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey); | |
| 70 | + displayer.display(bitmap, imageAware, loadedFrom); | |
| 71 | + engine.cancelDisplayTaskFor(imageAware); | |
| 72 | + listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap); | |
| 73 | + } | |
| 74 | + } | |
| 75 | + | |
| 76 | + /** Checks whether memory cache key (image URI) for current ImageAware is actual */ | |
| 77 | + private boolean isViewWasReused() { | |
| 78 | + String currentCacheKey = engine.getLoadingUriForView(imageAware); | |
| 79 | + return !memoryCacheKey.equals(currentCacheKey); | |
| 80 | + } | |
| 81 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core; | |
| 17 | + | |
| 18 | +import android.content.res.Resources; | |
| 19 | +import android.graphics.Bitmap; | |
| 20 | +import android.graphics.BitmapFactory.Options; | |
| 21 | +import android.graphics.drawable.Drawable; | |
| 22 | +import android.os.Handler; | |
| 23 | +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; | |
| 24 | +import com.nostra13.universalimageloader.core.assist.ImageScaleType; | |
| 25 | +import com.nostra13.universalimageloader.core.display.BitmapDisplayer; | |
| 26 | +import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer; | |
| 27 | +import com.nostra13.universalimageloader.core.download.ImageDownloader; | |
| 28 | +import com.nostra13.universalimageloader.core.process.BitmapProcessor; | |
| 29 | + | |
| 30 | +/** | |
| 31 | + * Contains options for image display. Defines: | |
| 32 | + * <ul> | |
| 33 | + * <li>whether stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 34 | + * image aware view} during image loading</li> | |
| 35 | + * <li>whether stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 36 | + * image aware view} if empty URI is passed</li> | |
| 37 | + * <li>whether stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 38 | + * image aware view} if image loading fails</li> | |
| 39 | + * <li>whether {@link com.nostra13.universalimageloader.core.imageaware.ImageAware image aware view} should be reset | |
| 40 | + * before image loading start</li> | |
| 41 | + * <li>whether loaded image will be cached in memory</li> | |
| 42 | + * <li>whether loaded image will be cached on disk</li> | |
| 43 | + * <li>image scale type</li> | |
| 44 | + * <li>decoding options (including bitmap decoding configuration)</li> | |
| 45 | + * <li>delay before loading of image</li> | |
| 46 | + * <li>whether consider EXIF parameters of image</li> | |
| 47 | + * <li>auxiliary object which will be passed to {@link ImageDownloader#getStream(String, Object) ImageDownloader}</li> | |
| 48 | + * <li>pre-processor for image Bitmap (before caching in memory)</li> | |
| 49 | + * <li>post-processor for image Bitmap (after caching in memory, before displaying)</li> | |
| 50 | + * <li>how decoded {@link Bitmap} will be displayed</li> | |
| 51 | + * </ul> | |
| 52 | + * <p/> | |
| 53 | + * You can create instance: | |
| 54 | + * <ul> | |
| 55 | + * <li>with {@link Builder}:<br /> | |
| 56 | + * <b>i.e.</b> : | |
| 57 | + * <code>new {@link DisplayImageOptions}.{@link Builder#Builder() Builder()}.{@link Builder#cacheInMemory() cacheInMemory()}. | |
| 58 | + * {@link Builder#showImageOnLoading(int) showImageOnLoading()}.{@link Builder#build() build()}</code><br /> | |
| 59 | + * </li> | |
| 60 | + * <li>or by static method: {@link #createSimple()}</li> <br /> | |
| 61 | + * | |
| 62 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 63 | + * @since 1.0.0 | |
| 64 | + */ | |
| 65 | +public final class DisplayImageOptions { | |
| 66 | + | |
| 67 | + private final int imageResOnLoading; | |
| 68 | + private final int imageResForEmptyUri; | |
| 69 | + private final int imageResOnFail; | |
| 70 | + private final Drawable imageOnLoading; | |
| 71 | + private final Drawable imageForEmptyUri; | |
| 72 | + private final Drawable imageOnFail; | |
| 73 | + private final boolean resetViewBeforeLoading; | |
| 74 | + private final boolean cacheInMemory; | |
| 75 | + private final boolean cacheOnDisk; | |
| 76 | + private final ImageScaleType imageScaleType; | |
| 77 | + private final Options decodingOptions; | |
| 78 | + private final int delayBeforeLoading; | |
| 79 | + private final boolean considerExifParams; | |
| 80 | + private final Object extraForDownloader; | |
| 81 | + private final BitmapProcessor preProcessor; | |
| 82 | + private final BitmapProcessor postProcessor; | |
| 83 | + private final BitmapDisplayer displayer; | |
| 84 | + private final Handler handler; | |
| 85 | + private final boolean isSyncLoading; | |
| 86 | + | |
| 87 | + private DisplayImageOptions(Builder builder) { | |
| 88 | + imageResOnLoading = builder.imageResOnLoading; | |
| 89 | + imageResForEmptyUri = builder.imageResForEmptyUri; | |
| 90 | + imageResOnFail = builder.imageResOnFail; | |
| 91 | + imageOnLoading = builder.imageOnLoading; | |
| 92 | + imageForEmptyUri = builder.imageForEmptyUri; | |
| 93 | + imageOnFail = builder.imageOnFail; | |
| 94 | + resetViewBeforeLoading = builder.resetViewBeforeLoading; | |
| 95 | + cacheInMemory = builder.cacheInMemory; | |
| 96 | + cacheOnDisk = builder.cacheOnDisk; | |
| 97 | + imageScaleType = builder.imageScaleType; | |
| 98 | + decodingOptions = builder.decodingOptions; | |
| 99 | + delayBeforeLoading = builder.delayBeforeLoading; | |
| 100 | + considerExifParams = builder.considerExifParams; | |
| 101 | + extraForDownloader = builder.extraForDownloader; | |
| 102 | + preProcessor = builder.preProcessor; | |
| 103 | + postProcessor = builder.postProcessor; | |
| 104 | + displayer = builder.displayer; | |
| 105 | + handler = builder.handler; | |
| 106 | + isSyncLoading = builder.isSyncLoading; | |
| 107 | + } | |
| 108 | + | |
| 109 | + public boolean shouldShowImageOnLoading() { | |
| 110 | + return imageOnLoading != null || imageResOnLoading != 0; | |
| 111 | + } | |
| 112 | + | |
| 113 | + public boolean shouldShowImageForEmptyUri() { | |
| 114 | + return imageForEmptyUri != null || imageResForEmptyUri != 0; | |
| 115 | + } | |
| 116 | + | |
| 117 | + public boolean shouldShowImageOnFail() { | |
| 118 | + return imageOnFail != null || imageResOnFail != 0; | |
| 119 | + } | |
| 120 | + | |
| 121 | + public boolean shouldPreProcess() { | |
| 122 | + return preProcessor != null; | |
| 123 | + } | |
| 124 | + | |
| 125 | + public boolean shouldPostProcess() { | |
| 126 | + return postProcessor != null; | |
| 127 | + } | |
| 128 | + | |
| 129 | + public boolean shouldDelayBeforeLoading() { | |
| 130 | + return delayBeforeLoading > 0; | |
| 131 | + } | |
| 132 | + | |
| 133 | + public Drawable getImageOnLoading(Resources res) { | |
| 134 | + return imageResOnLoading != 0 ? res.getDrawable(imageResOnLoading) : imageOnLoading; | |
| 135 | + } | |
| 136 | + | |
| 137 | + public Drawable getImageForEmptyUri(Resources res) { | |
| 138 | + return imageResForEmptyUri != 0 ? res.getDrawable(imageResForEmptyUri) : imageForEmptyUri; | |
| 139 | + } | |
| 140 | + | |
| 141 | + public Drawable getImageOnFail(Resources res) { | |
| 142 | + return imageResOnFail != 0 ? res.getDrawable(imageResOnFail) : imageOnFail; | |
| 143 | + } | |
| 144 | + | |
| 145 | + public boolean isResetViewBeforeLoading() { | |
| 146 | + return resetViewBeforeLoading; | |
| 147 | + } | |
| 148 | + | |
| 149 | + public boolean isCacheInMemory() { | |
| 150 | + return cacheInMemory; | |
| 151 | + } | |
| 152 | + | |
| 153 | + public boolean isCacheOnDisk() { | |
| 154 | + return cacheOnDisk; | |
| 155 | + } | |
| 156 | + | |
| 157 | + public ImageScaleType getImageScaleType() { | |
| 158 | + return imageScaleType; | |
| 159 | + } | |
| 160 | + | |
| 161 | + public Options getDecodingOptions() { | |
| 162 | + return decodingOptions; | |
| 163 | + } | |
| 164 | + | |
| 165 | + public int getDelayBeforeLoading() { | |
| 166 | + return delayBeforeLoading; | |
| 167 | + } | |
| 168 | + | |
| 169 | + public boolean isConsiderExifParams() { | |
| 170 | + return considerExifParams; | |
| 171 | + } | |
| 172 | + | |
| 173 | + public Object getExtraForDownloader() { | |
| 174 | + return extraForDownloader; | |
| 175 | + } | |
| 176 | + | |
| 177 | + public BitmapProcessor getPreProcessor() { | |
| 178 | + return preProcessor; | |
| 179 | + } | |
| 180 | + | |
| 181 | + public BitmapProcessor getPostProcessor() { | |
| 182 | + return postProcessor; | |
| 183 | + } | |
| 184 | + | |
| 185 | + public BitmapDisplayer getDisplayer() { | |
| 186 | + return displayer; | |
| 187 | + } | |
| 188 | + | |
| 189 | + public Handler getHandler() { | |
| 190 | + return handler; | |
| 191 | + } | |
| 192 | + | |
| 193 | + boolean isSyncLoading() { | |
| 194 | + return isSyncLoading; | |
| 195 | + } | |
| 196 | + | |
| 197 | + /** | |
| 198 | + * Builder for {@link DisplayImageOptions} | |
| 199 | + * | |
| 200 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 201 | + */ | |
| 202 | + public static class Builder { | |
| 203 | + private int imageResOnLoading = 0; | |
| 204 | + private int imageResForEmptyUri = 0; | |
| 205 | + private int imageResOnFail = 0; | |
| 206 | + private Drawable imageOnLoading = null; | |
| 207 | + private Drawable imageForEmptyUri = null; | |
| 208 | + private Drawable imageOnFail = null; | |
| 209 | + private boolean resetViewBeforeLoading = false; | |
| 210 | + private boolean cacheInMemory = false; | |
| 211 | + private boolean cacheOnDisk = false; | |
| 212 | + private ImageScaleType imageScaleType = ImageScaleType.IN_SAMPLE_POWER_OF_2; | |
| 213 | + private Options decodingOptions = new Options(); | |
| 214 | + private int delayBeforeLoading = 0; | |
| 215 | + private boolean considerExifParams = false; | |
| 216 | + private Object extraForDownloader = null; | |
| 217 | + private BitmapProcessor preProcessor = null; | |
| 218 | + private BitmapProcessor postProcessor = null; | |
| 219 | + private BitmapDisplayer displayer = DefaultConfigurationFactory.createBitmapDisplayer(); | |
| 220 | + private Handler handler = null; | |
| 221 | + private boolean isSyncLoading = false; | |
| 222 | + | |
| 223 | + public Builder() { | |
| 224 | + decodingOptions.inPurgeable = true; | |
| 225 | + decodingOptions.inInputShareable = true; | |
| 226 | + } | |
| 227 | + | |
| 228 | + /** | |
| 229 | + * Stub image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 230 | + * image aware view} during image loading | |
| 231 | + * | |
| 232 | + * @param imageRes Stub image resource | |
| 233 | + * @deprecated Use {@link #showImageOnLoading(int)} instead | |
| 234 | + */ | |
| 235 | + @Deprecated | |
| 236 | + public Builder showStubImage(int imageRes) { | |
| 237 | + imageResOnLoading = imageRes; | |
| 238 | + return this; | |
| 239 | + } | |
| 240 | + | |
| 241 | + /** | |
| 242 | + * Incoming image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 243 | + * image aware view} during image loading | |
| 244 | + * | |
| 245 | + * @param imageRes Image resource | |
| 246 | + */ | |
| 247 | + public Builder showImageOnLoading(int imageRes) { | |
| 248 | + imageResOnLoading = imageRes; | |
| 249 | + return this; | |
| 250 | + } | |
| 251 | + | |
| 252 | + /** | |
| 253 | + * Incoming drawable will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 254 | + * image aware view} during image loading. | |
| 255 | + * This option will be ignored if {@link Builder#showImageOnLoading(int)} is set. | |
| 256 | + */ | |
| 257 | + public Builder showImageOnLoading(Drawable drawable) { | |
| 258 | + imageOnLoading = drawable; | |
| 259 | + return this; | |
| 260 | + } | |
| 261 | + | |
| 262 | + /** | |
| 263 | + * Incoming image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 264 | + * image aware view} if empty URI (null or empty | |
| 265 | + * string) will be passed to <b>ImageLoader.displayImage(...)</b> method. | |
| 266 | + * | |
| 267 | + * @param imageRes Image resource | |
| 268 | + */ | |
| 269 | + public Builder showImageForEmptyUri(int imageRes) { | |
| 270 | + imageResForEmptyUri = imageRes; | |
| 271 | + return this; | |
| 272 | + } | |
| 273 | + | |
| 274 | + /** | |
| 275 | + * Incoming drawable will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 276 | + * image aware view} if empty URI (null or empty | |
| 277 | + * string) will be passed to <b>ImageLoader.displayImage(...)</b> method. | |
| 278 | + * This option will be ignored if {@link Builder#showImageForEmptyUri(int)} is set. | |
| 279 | + */ | |
| 280 | + public Builder showImageForEmptyUri(Drawable drawable) { | |
| 281 | + imageForEmptyUri = drawable; | |
| 282 | + return this; | |
| 283 | + } | |
| 284 | + | |
| 285 | + /** | |
| 286 | + * Incoming image will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 287 | + * image aware view} if some error occurs during | |
| 288 | + * requested image loading/decoding. | |
| 289 | + * | |
| 290 | + * @param imageRes Image resource | |
| 291 | + */ | |
| 292 | + public Builder showImageOnFail(int imageRes) { | |
| 293 | + imageResOnFail = imageRes; | |
| 294 | + return this; | |
| 295 | + } | |
| 296 | + | |
| 297 | + /** | |
| 298 | + * Incoming drawable will be displayed in {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 299 | + * image aware view} if some error occurs during | |
| 300 | + * requested image loading/decoding. | |
| 301 | + * This option will be ignored if {@link Builder#showImageOnFail(int)} is set. | |
| 302 | + */ | |
| 303 | + public Builder showImageOnFail(Drawable drawable) { | |
| 304 | + imageOnFail = drawable; | |
| 305 | + return this; | |
| 306 | + } | |
| 307 | + | |
| 308 | + /** | |
| 309 | + * {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 310 | + * image aware view} will be reset (set <b>null</b>) before image loading start | |
| 311 | + * | |
| 312 | + * @deprecated Use {@link #resetViewBeforeLoading(boolean) resetViewBeforeLoading(true)} instead | |
| 313 | + */ | |
| 314 | + public Builder resetViewBeforeLoading() { | |
| 315 | + resetViewBeforeLoading = true; | |
| 316 | + return this; | |
| 317 | + } | |
| 318 | + | |
| 319 | + /** | |
| 320 | + * Sets whether {@link com.nostra13.universalimageloader.core.imageaware.ImageAware | |
| 321 | + * image aware view} will be reset (set <b>null</b>) before image loading start | |
| 322 | + */ | |
| 323 | + public Builder resetViewBeforeLoading(boolean resetViewBeforeLoading) { | |
| 324 | + this.resetViewBeforeLoading = resetViewBeforeLoading; | |
| 325 | + return this; | |
| 326 | + } | |
| 327 | + | |
| 328 | + /** | |
| 329 | + * Loaded image will be cached in memory | |
| 330 | + * | |
| 331 | + * @deprecated Use {@link #cacheInMemory(boolean) cacheInMemory(true)} instead | |
| 332 | + */ | |
| 333 | + @Deprecated | |
| 334 | + public Builder cacheInMemory() { | |
| 335 | + cacheInMemory = true; | |
| 336 | + return this; | |
| 337 | + } | |
| 338 | + | |
| 339 | + /** Sets whether loaded image will be cached in memory */ | |
| 340 | + public Builder cacheInMemory(boolean cacheInMemory) { | |
| 341 | + this.cacheInMemory = cacheInMemory; | |
| 342 | + return this; | |
| 343 | + } | |
| 344 | + | |
| 345 | + /** | |
| 346 | + * Loaded image will be cached on disk | |
| 347 | + * | |
| 348 | + * @deprecated Use {@link #cacheOnDisk(boolean) cacheOnDisk(true)} instead | |
| 349 | + */ | |
| 350 | + @Deprecated | |
| 351 | + public Builder cacheOnDisc() { | |
| 352 | + return cacheOnDisk(true); | |
| 353 | + } | |
| 354 | + | |
| 355 | + /** | |
| 356 | + * Sets whether loaded image will be cached on disk | |
| 357 | + * | |
| 358 | + * @deprecated Use {@link #cacheOnDisk(boolean)} instead | |
| 359 | + */ | |
| 360 | + @Deprecated | |
| 361 | + public Builder cacheOnDisc(boolean cacheOnDisk) { | |
| 362 | + return cacheOnDisk(cacheOnDisk); | |
| 363 | + } | |
| 364 | + | |
| 365 | + /** Sets whether loaded image will be cached on disk */ | |
| 366 | + public Builder cacheOnDisk(boolean cacheOnDisk) { | |
| 367 | + this.cacheOnDisk = cacheOnDisk; | |
| 368 | + return this; | |
| 369 | + } | |
| 370 | + | |
| 371 | + /** | |
| 372 | + * Sets {@linkplain ImageScaleType scale type} for decoding image. This parameter is used while define scale | |
| 373 | + * size for decoding image to Bitmap. Default value - {@link ImageScaleType#IN_SAMPLE_POWER_OF_2} | |
| 374 | + */ | |
| 375 | + public Builder imageScaleType(ImageScaleType imageScaleType) { | |
| 376 | + this.imageScaleType = imageScaleType; | |
| 377 | + return this; | |
| 378 | + } | |
| 379 | + | |
| 380 | + /** Sets {@link Bitmap.Config bitmap config} for image decoding. Default value - {@link Bitmap.Config#ARGB_8888} */ | |
| 381 | + public Builder bitmapConfig(Bitmap.Config bitmapConfig) { | |
| 382 | + if (bitmapConfig == null) throw new IllegalArgumentException("bitmapConfig can't be null"); | |
| 383 | + decodingOptions.inPreferredConfig = bitmapConfig; | |
| 384 | + return this; | |
| 385 | + } | |
| 386 | + | |
| 387 | + /** | |
| 388 | + * Sets options for image decoding.<br /> | |
| 389 | + * <b>NOTE:</b> {@link Options#inSampleSize} of incoming options will <b>NOT</b> be considered. Library | |
| 390 | + * calculate the most appropriate sample size itself according yo {@link #imageScaleType(ImageScaleType)} | |
| 391 | + * options.<br /> | |
| 392 | + * <b>NOTE:</b> This option overlaps {@link #bitmapConfig(Bitmap.Config) bitmapConfig()} | |
| 393 | + * option. | |
| 394 | + */ | |
| 395 | + public Builder decodingOptions(Options decodingOptions) { | |
| 396 | + if (decodingOptions == null) throw new IllegalArgumentException("decodingOptions can't be null"); | |
| 397 | + this.decodingOptions = decodingOptions; | |
| 398 | + return this; | |
| 399 | + } | |
| 400 | + | |
| 401 | + /** Sets delay time before starting loading task. Default - no delay. */ | |
| 402 | + public Builder delayBeforeLoading(int delayInMillis) { | |
| 403 | + this.delayBeforeLoading = delayInMillis; | |
| 404 | + return this; | |
| 405 | + } | |
| 406 | + | |
| 407 | + /** Sets auxiliary object which will be passed to {@link ImageDownloader#getStream(String, Object)} */ | |
| 408 | + public Builder extraForDownloader(Object extra) { | |
| 409 | + this.extraForDownloader = extra; | |
| 410 | + return this; | |
| 411 | + } | |
| 412 | + | |
| 413 | + /** Sets whether ImageLoader will consider EXIF parameters of JPEG image (rotate, flip) */ | |
| 414 | + public Builder considerExifParams(boolean considerExifParams) { | |
| 415 | + this.considerExifParams = considerExifParams; | |
| 416 | + return this; | |
| 417 | + } | |
| 418 | + | |
| 419 | + /** | |
| 420 | + * Sets bitmap processor which will be process bitmaps before they will be cached in memory. So memory cache | |
| 421 | + * will contain bitmap processed by incoming preProcessor.<br /> | |
| 422 | + * Image will be pre-processed even if caching in memory is disabled. | |
| 423 | + */ | |
| 424 | + public Builder preProcessor(BitmapProcessor preProcessor) { | |
| 425 | + this.preProcessor = preProcessor; | |
| 426 | + return this; | |
| 427 | + } | |
| 428 | + | |
| 429 | + /** | |
| 430 | + * Sets bitmap processor which will be process bitmaps before they will be displayed in | |
| 431 | + * {@link com.nostra13.universalimageloader.core.imageaware.ImageAware image aware view} but | |
| 432 | + * after they'll have been saved in memory cache. | |
| 433 | + */ | |
| 434 | + public Builder postProcessor(BitmapProcessor postProcessor) { | |
| 435 | + this.postProcessor = postProcessor; | |
| 436 | + return this; | |
| 437 | + } | |
| 438 | + | |
| 439 | + /** | |
| 440 | + * Sets custom {@link BitmapDisplayer displayer} for image loading task. Default value - | |
| 441 | + * {@link DefaultConfigurationFactory#createBitmapDisplayer()} | |
| 442 | + */ | |
| 443 | + public Builder displayer(BitmapDisplayer displayer) { | |
| 444 | + if (displayer == null) throw new IllegalArgumentException("displayer can't be null"); | |
| 445 | + this.displayer = displayer; | |
| 446 | + return this; | |
| 447 | + } | |
| 448 | + | |
| 449 | + Builder syncLoading(boolean isSyncLoading) { | |
| 450 | + this.isSyncLoading = isSyncLoading; | |
| 451 | + return this; | |
| 452 | + } | |
| 453 | + | |
| 454 | + /** | |
| 455 | + * Sets custom {@linkplain Handler handler} for displaying images and firing {@linkplain ImageLoadingListener | |
| 456 | + * listener} events. | |
| 457 | + */ | |
| 458 | + public Builder handler(Handler handler) { | |
| 459 | + this.handler = handler; | |
| 460 | + return this; | |
| 461 | + } | |
| 462 | + | |
| 463 | + /** Sets all options equal to incoming options */ | |
| 464 | + public Builder cloneFrom(DisplayImageOptions options) { | |
| 465 | + imageResOnLoading = options.imageResOnLoading; | |
| 466 | + imageResForEmptyUri = options.imageResForEmptyUri; | |
| 467 | + imageResOnFail = options.imageResOnFail; | |
| 468 | + imageOnLoading = options.imageOnLoading; | |
| 469 | + imageForEmptyUri = options.imageForEmptyUri; | |
| 470 | + imageOnFail = options.imageOnFail; | |
| 471 | + resetViewBeforeLoading = options.resetViewBeforeLoading; | |
| 472 | + cacheInMemory = options.cacheInMemory; | |
| 473 | + cacheOnDisk = options.cacheOnDisk; | |
| 474 | + imageScaleType = options.imageScaleType; | |
| 475 | + decodingOptions = options.decodingOptions; | |
| 476 | + delayBeforeLoading = options.delayBeforeLoading; | |
| 477 | + considerExifParams = options.considerExifParams; | |
| 478 | + extraForDownloader = options.extraForDownloader; | |
| 479 | + preProcessor = options.preProcessor; | |
| 480 | + postProcessor = options.postProcessor; | |
| 481 | + displayer = options.displayer; | |
| 482 | + handler = options.handler; | |
| 483 | + isSyncLoading = options.isSyncLoading; | |
| 484 | + return this; | |
| 485 | + } | |
| 486 | + | |
| 487 | + /** Builds configured {@link DisplayImageOptions} object */ | |
| 488 | + public DisplayImageOptions build() { | |
| 489 | + return new DisplayImageOptions(this); | |
| 490 | + } | |
| 491 | + } | |
| 492 | + | |
| 493 | + /** | |
| 494 | + * Creates options appropriate for single displaying: | |
| 495 | + * <ul> | |
| 496 | + * <li>View will <b>not</b> be reset before loading</li> | |
| 497 | + * <li>Loaded image will <b>not</b> be cached in memory</li> | |
| 498 | + * <li>Loaded image will <b>not</b> be cached on disk</li> | |
| 499 | + * <li>{@link ImageScaleType#IN_SAMPLE_POWER_OF_2} decoding type will be used</li> | |
| 500 | + * <li>{@link Bitmap.Config#ARGB_8888} bitmap config will be used for image decoding</li> | |
| 501 | + * <li>{@link SimpleBitmapDisplayer} will be used for image displaying</li> | |
| 502 | + * </ul> | |
| 503 | + * <p/> | |
| 504 | + * These option are appropriate for simple single-use image (from drawables or from Internet) displaying. | |
| 505 | + */ | |
| 506 | + public static DisplayImageOptions createSimple() { | |
| 507 | + return new Builder().build(); | |
| 508 | + } | |
| 509 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.os.Handler; | |
| 20 | +import android.os.Looper; | |
| 21 | +import android.text.TextUtils; | |
| 22 | +import android.view.View; | |
| 23 | +import android.widget.ImageView; | |
| 24 | +import com.nostra13.universalimageloader.cache.disc.DiskCache; | |
| 25 | +import com.nostra13.universalimageloader.cache.memory.MemoryCache; | |
| 26 | +import com.nostra13.universalimageloader.core.assist.FailReason; | |
| 27 | +import com.nostra13.universalimageloader.core.assist.FlushedInputStream; | |
| 28 | +import com.nostra13.universalimageloader.core.assist.ImageSize; | |
| 29 | +import com.nostra13.universalimageloader.core.assist.LoadedFrom; | |
| 30 | +import com.nostra13.universalimageloader.core.assist.ViewScaleType; | |
| 31 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 32 | +import com.nostra13.universalimageloader.core.imageaware.ImageViewAware; | |
| 33 | +import com.nostra13.universalimageloader.core.imageaware.NonViewAware; | |
| 34 | +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; | |
| 35 | +import com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener; | |
| 36 | +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; | |
| 37 | +import com.nostra13.universalimageloader.utils.ImageSizeUtils; | |
| 38 | +import com.nostra13.universalimageloader.utils.L; | |
| 39 | +import com.nostra13.universalimageloader.utils.MemoryCacheUtils; | |
| 40 | + | |
| 41 | +/** | |
| 42 | + * Singletone for image loading and displaying at {@link ImageView ImageViews}<br /> | |
| 43 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before any other method. | |
| 44 | + * | |
| 45 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 46 | + * @since 1.0.0 | |
| 47 | + */ | |
| 48 | +public class ImageLoader { | |
| 49 | + | |
| 50 | + public static final String TAG = ImageLoader.class.getSimpleName(); | |
| 51 | + | |
| 52 | + static final String LOG_INIT_CONFIG = "Initialize ImageLoader with configuration"; | |
| 53 | + static final String LOG_DESTROY = "Destroy ImageLoader"; | |
| 54 | + static final String LOG_LOAD_IMAGE_FROM_MEMORY_CACHE = "Load image from memory cache [%s]"; | |
| 55 | + | |
| 56 | + 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."; | |
| 57 | + private static final String ERROR_WRONG_ARGUMENTS = "Wrong arguments were passed to displayImage() method (ImageView reference must not be null)"; | |
| 58 | + private static final String ERROR_NOT_INIT = "ImageLoader must be init with configuration before using"; | |
| 59 | + private static final String ERROR_INIT_CONFIG_WITH_NULL = "ImageLoader configuration can not be initialized with null"; | |
| 60 | + | |
| 61 | + private ImageLoaderConfiguration configuration; | |
| 62 | + private ImageLoaderEngine engine; | |
| 63 | + | |
| 64 | + private ImageLoadingListener defaultListener = new SimpleImageLoadingListener(); | |
| 65 | + | |
| 66 | + private volatile static ImageLoader instance; | |
| 67 | + | |
| 68 | + /** Returns singleton class instance */ | |
| 69 | + public static ImageLoader getInstance() { | |
| 70 | + if (instance == null) { | |
| 71 | + synchronized (ImageLoader.class) { | |
| 72 | + if (instance == null) { | |
| 73 | + instance = new ImageLoader(); | |
| 74 | + } | |
| 75 | + } | |
| 76 | + } | |
| 77 | + return instance; | |
| 78 | + } | |
| 79 | + | |
| 80 | + protected ImageLoader() { | |
| 81 | + } | |
| 82 | + | |
| 83 | + /** | |
| 84 | + * Initializes ImageLoader instance with configuration.<br /> | |
| 85 | + * If configurations was set before ( {@link #isInited()} == true) then this method does nothing.<br /> | |
| 86 | + * To force initialization with new configuration you should {@linkplain #destroy() destroy ImageLoader} at first. | |
| 87 | + * | |
| 88 | + * @param configuration {@linkplain ImageLoaderConfiguration ImageLoader configuration} | |
| 89 | + * @throws IllegalArgumentException if <b>configuration</b> parameter is null | |
| 90 | + */ | |
| 91 | + public synchronized void init(ImageLoaderConfiguration configuration) { | |
| 92 | + if (configuration == null) { | |
| 93 | + throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL); | |
| 94 | + } | |
| 95 | + if (this.configuration == null) { | |
| 96 | + L.d(LOG_INIT_CONFIG); | |
| 97 | + engine = new ImageLoaderEngine(configuration); | |
| 98 | + this.configuration = configuration; | |
| 99 | + } else { | |
| 100 | + L.w(WARNING_RE_INIT_CONFIG); | |
| 101 | + } | |
| 102 | + } | |
| 103 | + | |
| 104 | + /** | |
| 105 | + * Returns <b>true</b> - if ImageLoader {@linkplain #init(ImageLoaderConfiguration) is initialized with | |
| 106 | + * configuration}; <b>false</b> - otherwise | |
| 107 | + */ | |
| 108 | + public boolean isInited() { | |
| 109 | + return configuration != null; | |
| 110 | + } | |
| 111 | + | |
| 112 | + /** | |
| 113 | + * Adds display image task to execution pool. Image will be set to ImageAware when it's turn. <br/> | |
| 114 | + * Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration | |
| 115 | + * configuration} will be used.<br /> | |
| 116 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 117 | + * | |
| 118 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 119 | + * @param imageAware {@linkplain ImageAware Image aware view} | |
| 120 | + * which should display image | |
| 121 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 122 | + * @throws IllegalArgumentException if passed <b>imageAware</b> is null | |
| 123 | + */ | |
| 124 | + public void displayImage(String uri, ImageAware imageAware) { | |
| 125 | + displayImage(uri, imageAware, null, null, null); | |
| 126 | + } | |
| 127 | + | |
| 128 | + /** | |
| 129 | + * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.<br /> | |
| 130 | + * Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration | |
| 131 | + * configuration} will be used.<br /> | |
| 132 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 133 | + * | |
| 134 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 135 | + * @param imageAware {@linkplain ImageAware Image aware view} | |
| 136 | + * which should display image | |
| 137 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on | |
| 138 | + * UI thread if this method is called on UI thread. | |
| 139 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 140 | + * @throws IllegalArgumentException if passed <b>imageAware</b> is null | |
| 141 | + */ | |
| 142 | + public void displayImage(String uri, ImageAware imageAware, ImageLoadingListener listener) { | |
| 143 | + displayImage(uri, imageAware, null, listener, null); | |
| 144 | + } | |
| 145 | + | |
| 146 | + /** | |
| 147 | + * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.<br /> | |
| 148 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 149 | + * | |
| 150 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 151 | + * @param imageAware {@linkplain ImageAware Image aware view} | |
| 152 | + * which should display image | |
| 153 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 154 | + * decoding and displaying. If <b>null</b> - default display image options | |
| 155 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) | |
| 156 | + * from configuration} will be used. | |
| 157 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 158 | + * @throws IllegalArgumentException if passed <b>imageAware</b> is null | |
| 159 | + */ | |
| 160 | + public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options) { | |
| 161 | + displayImage(uri, imageAware, options, null, null); | |
| 162 | + } | |
| 163 | + | |
| 164 | + /** | |
| 165 | + * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.<br /> | |
| 166 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 167 | + * | |
| 168 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 169 | + * @param imageAware {@linkplain ImageAware Image aware view} | |
| 170 | + * which should display image | |
| 171 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 172 | + * decoding and displaying. If <b>null</b> - default display image options | |
| 173 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) | |
| 174 | + * from configuration} will be used. | |
| 175 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on | |
| 176 | + * UI thread if this method is called on UI thread. | |
| 177 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 178 | + * @throws IllegalArgumentException if passed <b>imageAware</b> is null | |
| 179 | + */ | |
| 180 | + public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, | |
| 181 | + ImageLoadingListener listener) { | |
| 182 | + displayImage(uri, imageAware, options, listener, null); | |
| 183 | + } | |
| 184 | + | |
| 185 | + /** | |
| 186 | + * Adds display image task to execution pool. Image will be set to ImageAware when it's turn.<br /> | |
| 187 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 188 | + * | |
| 189 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 190 | + * @param imageAware {@linkplain ImageAware Image aware view} | |
| 191 | + * which should display image | |
| 192 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 193 | + * decoding and displaying. If <b>null</b> - default display image options | |
| 194 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) | |
| 195 | + * from configuration} will be used. | |
| 196 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires | |
| 197 | + * events on UI thread if this method is called on UI thread. | |
| 198 | + * @param progressListener {@linkplain ImageLoadingProgressListener | |
| 199 | + * Listener} for image loading progress. Listener fires events on UI thread if this method | |
| 200 | + * is called on UI thread. Caching on disk should be enabled in | |
| 201 | + * {@linkplain DisplayImageOptions options} to make | |
| 202 | + * this listener work. | |
| 203 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 204 | + * @throws IllegalArgumentException if passed <b>imageAware</b> is null | |
| 205 | + */ | |
| 206 | + public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, | |
| 207 | + ImageLoadingListener listener, ImageLoadingProgressListener progressListener) { | |
| 208 | + checkConfiguration(); | |
| 209 | + if (imageAware == null) { | |
| 210 | + throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS); | |
| 211 | + } | |
| 212 | + if (listener == null) { | |
| 213 | + listener = defaultListener; | |
| 214 | + } | |
| 215 | + if (options == null) { | |
| 216 | + options = configuration.defaultDisplayImageOptions; | |
| 217 | + } | |
| 218 | + | |
| 219 | + if (TextUtils.isEmpty(uri)) { | |
| 220 | + engine.cancelDisplayTaskFor(imageAware); | |
| 221 | + listener.onLoadingStarted(uri, imageAware.getWrappedView()); | |
| 222 | + if (options.shouldShowImageForEmptyUri()) { | |
| 223 | + imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources)); | |
| 224 | + } else { | |
| 225 | + imageAware.setImageDrawable(null); | |
| 226 | + } | |
| 227 | + listener.onLoadingComplete(uri, imageAware.getWrappedView(), null); | |
| 228 | + return; | |
| 229 | + } | |
| 230 | + | |
| 231 | + ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize()); | |
| 232 | + String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize); | |
| 233 | + engine.prepareDisplayTaskFor(imageAware, memoryCacheKey); | |
| 234 | + | |
| 235 | + listener.onLoadingStarted(uri, imageAware.getWrappedView()); | |
| 236 | + | |
| 237 | + Bitmap bmp = configuration.memoryCache.get(memoryCacheKey); | |
| 238 | + if (bmp != null && !bmp.isRecycled()) { | |
| 239 | + L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey); | |
| 240 | + | |
| 241 | + if (options.shouldPostProcess()) { | |
| 242 | + ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, | |
| 243 | + options, listener, progressListener, engine.getLockForUri(uri)); | |
| 244 | + ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo, | |
| 245 | + defineHandler(options)); | |
| 246 | + if (options.isSyncLoading()) { | |
| 247 | + displayTask.run(); | |
| 248 | + } else { | |
| 249 | + engine.submit(displayTask); | |
| 250 | + } | |
| 251 | + } else { | |
| 252 | + options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE); | |
| 253 | + listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp); | |
| 254 | + } | |
| 255 | + } else { | |
| 256 | + if (options.shouldShowImageOnLoading()) { | |
| 257 | + imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources)); | |
| 258 | + } else if (options.isResetViewBeforeLoading()) { | |
| 259 | + imageAware.setImageDrawable(null); | |
| 260 | + } | |
| 261 | + | |
| 262 | + ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, | |
| 263 | + options, listener, progressListener, engine.getLockForUri(uri)); | |
| 264 | + LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo, | |
| 265 | + defineHandler(options)); | |
| 266 | + if (options.isSyncLoading()) { | |
| 267 | + displayTask.run(); | |
| 268 | + } else { | |
| 269 | + engine.submit(displayTask); | |
| 270 | + } | |
| 271 | + } | |
| 272 | + } | |
| 273 | + | |
| 274 | + /** | |
| 275 | + * Adds display image task to execution pool. Image will be set to ImageView when it's turn. <br/> | |
| 276 | + * Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration | |
| 277 | + * configuration} will be used.<br /> | |
| 278 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 279 | + * | |
| 280 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 281 | + * @param imageView {@link ImageView} which should display image | |
| 282 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 283 | + * @throws IllegalArgumentException if passed <b>imageView</b> is null | |
| 284 | + */ | |
| 285 | + public void displayImage(String uri, ImageView imageView) { | |
| 286 | + displayImage(uri, new ImageViewAware(imageView), null, null, null); | |
| 287 | + } | |
| 288 | + | |
| 289 | + /** | |
| 290 | + * Adds display image task to execution pool. Image will be set to ImageView when it's turn.<br /> | |
| 291 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 292 | + * | |
| 293 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 294 | + * @param imageView {@link ImageView} which should display image | |
| 295 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 296 | + * decoding and displaying. If <b>null</b> - default display image options | |
| 297 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) | |
| 298 | + * from configuration} will be used. | |
| 299 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 300 | + * @throws IllegalArgumentException if passed <b>imageView</b> is null | |
| 301 | + */ | |
| 302 | + public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) { | |
| 303 | + displayImage(uri, new ImageViewAware(imageView), options, null, null); | |
| 304 | + } | |
| 305 | + | |
| 306 | + /** | |
| 307 | + * Adds display image task to execution pool. Image will be set to ImageView when it's turn.<br /> | |
| 308 | + * Default {@linkplain DisplayImageOptions display image options} from {@linkplain ImageLoaderConfiguration | |
| 309 | + * configuration} will be used.<br /> | |
| 310 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 311 | + * | |
| 312 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 313 | + * @param imageView {@link ImageView} which should display image | |
| 314 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on | |
| 315 | + * UI thread if this method is called on UI thread. | |
| 316 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 317 | + * @throws IllegalArgumentException if passed <b>imageView</b> is null | |
| 318 | + */ | |
| 319 | + public void displayImage(String uri, ImageView imageView, ImageLoadingListener listener) { | |
| 320 | + displayImage(uri, new ImageViewAware(imageView), null, listener, null); | |
| 321 | + } | |
| 322 | + | |
| 323 | + /** | |
| 324 | + * Adds display image task to execution pool. Image will be set to ImageView when it's turn.<br /> | |
| 325 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 326 | + * | |
| 327 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 328 | + * @param imageView {@link ImageView} which should display image | |
| 329 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 330 | + * decoding and displaying. If <b>null</b> - default display image options | |
| 331 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) | |
| 332 | + * from configuration} will be used. | |
| 333 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on | |
| 334 | + * UI thread if this method is called on UI thread. | |
| 335 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 336 | + * @throws IllegalArgumentException if passed <b>imageView</b> is null | |
| 337 | + */ | |
| 338 | + public void displayImage(String uri, ImageView imageView, DisplayImageOptions options, | |
| 339 | + ImageLoadingListener listener) { | |
| 340 | + displayImage(uri, imageView, options, listener, null); | |
| 341 | + } | |
| 342 | + | |
| 343 | + /** | |
| 344 | + * Adds display image task to execution pool. Image will be set to ImageView when it's turn.<br /> | |
| 345 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 346 | + * | |
| 347 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 348 | + * @param imageView {@link ImageView} which should display image | |
| 349 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 350 | + * decoding and displaying. If <b>null</b> - default display image options | |
| 351 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) | |
| 352 | + * from configuration} will be used. | |
| 353 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires | |
| 354 | + * events on UI thread if this method is called on UI thread. | |
| 355 | + * @param progressListener {@linkplain ImageLoadingProgressListener | |
| 356 | + * Listener} for image loading progress. Listener fires events on UI thread if this method | |
| 357 | + * is called on UI thread. Caching on disk should be enabled in | |
| 358 | + * {@linkplain DisplayImageOptions options} to make | |
| 359 | + * this listener work. | |
| 360 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 361 | + * @throws IllegalArgumentException if passed <b>imageView</b> is null | |
| 362 | + */ | |
| 363 | + public void displayImage(String uri, ImageView imageView, DisplayImageOptions options, | |
| 364 | + ImageLoadingListener listener, ImageLoadingProgressListener progressListener) { | |
| 365 | + displayImage(uri, new ImageViewAware(imageView), options, listener, progressListener); | |
| 366 | + } | |
| 367 | + | |
| 368 | + /** | |
| 369 | + * Adds load image task to execution pool. Image will be returned with | |
| 370 | + * {@link ImageLoadingListener#onLoadingComplete(String, View, Bitmap)} callback}. | |
| 371 | + * <br /> | |
| 372 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 373 | + * | |
| 374 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 375 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on UI | |
| 376 | + * thread if this method is called on UI thread. | |
| 377 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 378 | + */ | |
| 379 | + public void loadImage(String uri, ImageLoadingListener listener) { | |
| 380 | + loadImage(uri, null, null, listener, null); | |
| 381 | + } | |
| 382 | + | |
| 383 | + /** | |
| 384 | + * Adds load image task to execution pool. Image will be returned with | |
| 385 | + * {@link ImageLoadingListener#onLoadingComplete(String, View, Bitmap)} callback}. | |
| 386 | + * <br /> | |
| 387 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 388 | + * | |
| 389 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 390 | + * @param targetImageSize Minimal size for {@link Bitmap} which will be returned in | |
| 391 | + * {@linkplain ImageLoadingListener#onLoadingComplete(String, View, | |
| 392 | + * Bitmap)} callback}. Downloaded image will be decoded | |
| 393 | + * and scaled to {@link Bitmap} of the size which is <b>equal or larger</b> (usually a bit | |
| 394 | + * larger) than incoming targetImageSize. | |
| 395 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires | |
| 396 | + * events on UI thread if this method is called on UI thread. | |
| 397 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 398 | + */ | |
| 399 | + public void loadImage(String uri, ImageSize targetImageSize, ImageLoadingListener listener) { | |
| 400 | + loadImage(uri, targetImageSize, null, listener, null); | |
| 401 | + } | |
| 402 | + | |
| 403 | + /** | |
| 404 | + * Adds load image task to execution pool. Image will be returned with | |
| 405 | + * {@link ImageLoadingListener#onLoadingComplete(String, View, Bitmap)} callback}. | |
| 406 | + * <br /> | |
| 407 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 408 | + * | |
| 409 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 410 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 411 | + * decoding and displaying. If <b>null</b> - default display image options | |
| 412 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from | |
| 413 | + * configuration} will be used.<br /> | |
| 414 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires events on UI | |
| 415 | + * thread if this method is called on UI thread. | |
| 416 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 417 | + */ | |
| 418 | + public void loadImage(String uri, DisplayImageOptions options, ImageLoadingListener listener) { | |
| 419 | + loadImage(uri, null, options, listener, null); | |
| 420 | + } | |
| 421 | + | |
| 422 | + /** | |
| 423 | + * Adds load image task to execution pool. Image will be returned with | |
| 424 | + * {@link ImageLoadingListener#onLoadingComplete(String, View, Bitmap)} callback}. | |
| 425 | + * <br /> | |
| 426 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 427 | + * | |
| 428 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 429 | + * @param targetImageSize Minimal size for {@link Bitmap} which will be returned in | |
| 430 | + * {@linkplain ImageLoadingListener#onLoadingComplete(String, View, | |
| 431 | + * Bitmap)} callback}. Downloaded image will be decoded | |
| 432 | + * and scaled to {@link Bitmap} of the size which is <b>equal or larger</b> (usually a bit | |
| 433 | + * larger) than incoming targetImageSize. | |
| 434 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 435 | + * decoding and displaying. If <b>null</b> - default display image options | |
| 436 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) | |
| 437 | + * from configuration} will be used.<br /> | |
| 438 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires | |
| 439 | + * events on UI thread if this method is called on UI thread. | |
| 440 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 441 | + */ | |
| 442 | + public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options, | |
| 443 | + ImageLoadingListener listener) { | |
| 444 | + loadImage(uri, targetImageSize, options, listener, null); | |
| 445 | + } | |
| 446 | + | |
| 447 | + /** | |
| 448 | + * Adds load image task to execution pool. Image will be returned with | |
| 449 | + * {@link ImageLoadingListener#onLoadingComplete(String, View, Bitmap)} callback}. | |
| 450 | + * <br /> | |
| 451 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 452 | + * | |
| 453 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 454 | + * @param targetImageSize Minimal size for {@link Bitmap} which will be returned in | |
| 455 | + * {@linkplain ImageLoadingListener#onLoadingComplete(String, View, | |
| 456 | + * Bitmap)} callback}. Downloaded image will be decoded | |
| 457 | + * and scaled to {@link Bitmap} of the size which is <b>equal or larger</b> (usually a bit | |
| 458 | + * larger) than incoming targetImageSize. | |
| 459 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 460 | + * decoding and displaying. If <b>null</b> - default display image options | |
| 461 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) | |
| 462 | + * from configuration} will be used.<br /> | |
| 463 | + * @param listener {@linkplain ImageLoadingListener Listener} for image loading process. Listener fires | |
| 464 | + * events on UI thread if this method is called on UI thread. | |
| 465 | + * @param progressListener {@linkplain ImageLoadingProgressListener | |
| 466 | + * Listener} for image loading progress. Listener fires events on UI thread if this method | |
| 467 | + * is called on UI thread. Caching on disk should be enabled in | |
| 468 | + * {@linkplain DisplayImageOptions options} to make | |
| 469 | + * this listener work. | |
| 470 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 471 | + */ | |
| 472 | + public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options, | |
| 473 | + ImageLoadingListener listener, ImageLoadingProgressListener progressListener) { | |
| 474 | + checkConfiguration(); | |
| 475 | + if (targetImageSize == null) { | |
| 476 | + targetImageSize = configuration.getMaxImageSize(); | |
| 477 | + } | |
| 478 | + if (options == null) { | |
| 479 | + options = configuration.defaultDisplayImageOptions; | |
| 480 | + } | |
| 481 | + | |
| 482 | + NonViewAware imageAware = new NonViewAware(uri, targetImageSize, ViewScaleType.CROP); | |
| 483 | + displayImage(uri, imageAware, options, listener, progressListener); | |
| 484 | + } | |
| 485 | + | |
| 486 | + /** | |
| 487 | + * Loads and decodes image synchronously.<br /> | |
| 488 | + * Default display image options | |
| 489 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from | |
| 490 | + * configuration} will be used.<br /> | |
| 491 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 492 | + * | |
| 493 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 494 | + * @return Result image Bitmap. Can be <b>null</b> if image loading/decoding was failed or cancelled. | |
| 495 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 496 | + */ | |
| 497 | + public Bitmap loadImageSync(String uri) { | |
| 498 | + return loadImageSync(uri, null, null); | |
| 499 | + } | |
| 500 | + | |
| 501 | + /** | |
| 502 | + * Loads and decodes image synchronously.<br /> | |
| 503 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 504 | + * | |
| 505 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 506 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 507 | + * decoding and scaling. If <b>null</b> - default display image options | |
| 508 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from | |
| 509 | + * configuration} will be used. | |
| 510 | + * @return Result image Bitmap. Can be <b>null</b> if image loading/decoding was failed or cancelled. | |
| 511 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 512 | + */ | |
| 513 | + public Bitmap loadImageSync(String uri, DisplayImageOptions options) { | |
| 514 | + return loadImageSync(uri, null, options); | |
| 515 | + } | |
| 516 | + | |
| 517 | + /** | |
| 518 | + * Loads and decodes image synchronously.<br /> | |
| 519 | + * Default display image options | |
| 520 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) from | |
| 521 | + * configuration} will be used.<br /> | |
| 522 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 523 | + * | |
| 524 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 525 | + * @param targetImageSize Minimal size for {@link Bitmap} which will be returned. Downloaded image will be decoded | |
| 526 | + * and scaled to {@link Bitmap} of the size which is <b>equal or larger</b> (usually a bit | |
| 527 | + * larger) than incoming targetImageSize. | |
| 528 | + * @return Result image Bitmap. Can be <b>null</b> if image loading/decoding was failed or cancelled. | |
| 529 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 530 | + */ | |
| 531 | + public Bitmap loadImageSync(String uri, ImageSize targetImageSize) { | |
| 532 | + return loadImageSync(uri, targetImageSize, null); | |
| 533 | + } | |
| 534 | + | |
| 535 | + /** | |
| 536 | + * Loads and decodes image synchronously.<br /> | |
| 537 | + * <b>NOTE:</b> {@link #init(ImageLoaderConfiguration)} method must be called before this method call | |
| 538 | + * | |
| 539 | + * @param uri Image URI (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png") | |
| 540 | + * @param targetImageSize Minimal size for {@link Bitmap} which will be returned. Downloaded image will be decoded | |
| 541 | + * and scaled to {@link Bitmap} of the size which is <b>equal or larger</b> (usually a bit | |
| 542 | + * larger) than incoming targetImageSize. | |
| 543 | + * @param options {@linkplain DisplayImageOptions Options} for image | |
| 544 | + * decoding and scaling. If <b>null</b> - default display image options | |
| 545 | + * {@linkplain ImageLoaderConfiguration.Builder#defaultDisplayImageOptions(DisplayImageOptions) | |
| 546 | + * from configuration} will be used. | |
| 547 | + * @return Result image Bitmap. Can be <b>null</b> if image loading/decoding was failed or cancelled. | |
| 548 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 549 | + */ | |
| 550 | + public Bitmap loadImageSync(String uri, ImageSize targetImageSize, DisplayImageOptions options) { | |
| 551 | + if (options == null) { | |
| 552 | + options = configuration.defaultDisplayImageOptions; | |
| 553 | + } | |
| 554 | + options = new DisplayImageOptions.Builder().cloneFrom(options).syncLoading(true).build(); | |
| 555 | + | |
| 556 | + SyncImageLoadingListener listener = new SyncImageLoadingListener(); | |
| 557 | + loadImage(uri, targetImageSize, options, listener); | |
| 558 | + return listener.getLoadedBitmap(); | |
| 559 | + } | |
| 560 | + | |
| 561 | + /** | |
| 562 | + * Checks if ImageLoader's configuration was initialized | |
| 563 | + * | |
| 564 | + * @throws IllegalStateException if configuration wasn't initialized | |
| 565 | + */ | |
| 566 | + private void checkConfiguration() { | |
| 567 | + if (configuration == null) { | |
| 568 | + throw new IllegalStateException(ERROR_NOT_INIT); | |
| 569 | + } | |
| 570 | + } | |
| 571 | + | |
| 572 | + /** Sets a default loading listener for all display and loading tasks. */ | |
| 573 | + public void setDefaultLoadingListener(ImageLoadingListener listener) { | |
| 574 | + defaultListener = listener == null ? new SimpleImageLoadingListener() : listener; | |
| 575 | + } | |
| 576 | + | |
| 577 | + /** | |
| 578 | + * Returns memory cache | |
| 579 | + * | |
| 580 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 581 | + */ | |
| 582 | + public MemoryCache getMemoryCache() { | |
| 583 | + checkConfiguration(); | |
| 584 | + return configuration.memoryCache; | |
| 585 | + } | |
| 586 | + | |
| 587 | + /** | |
| 588 | + * Clears memory cache | |
| 589 | + * | |
| 590 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 591 | + */ | |
| 592 | + public void clearMemoryCache() { | |
| 593 | + checkConfiguration(); | |
| 594 | + configuration.memoryCache.clear(); | |
| 595 | + } | |
| 596 | + | |
| 597 | + /** | |
| 598 | + * Returns disk cache | |
| 599 | + * | |
| 600 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 601 | + * @deprecated Use {@link #getDiskCache()} instead | |
| 602 | + */ | |
| 603 | + @Deprecated | |
| 604 | + public DiskCache getDiscCache() { | |
| 605 | + return getDiskCache(); | |
| 606 | + } | |
| 607 | + | |
| 608 | + /** | |
| 609 | + * Returns disk cache | |
| 610 | + * | |
| 611 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 612 | + */ | |
| 613 | + public DiskCache getDiskCache() { | |
| 614 | + checkConfiguration(); | |
| 615 | + return configuration.diskCache; | |
| 616 | + } | |
| 617 | + | |
| 618 | + /** | |
| 619 | + * Clears disk cache. | |
| 620 | + * | |
| 621 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 622 | + * @deprecated Use {@link #clearDiskCache()} instead | |
| 623 | + */ | |
| 624 | + @Deprecated | |
| 625 | + public void clearDiscCache() { | |
| 626 | + clearDiskCache(); | |
| 627 | + } | |
| 628 | + | |
| 629 | + /** | |
| 630 | + * Clears disk cache. | |
| 631 | + * | |
| 632 | + * @throws IllegalStateException if {@link #init(ImageLoaderConfiguration)} method wasn't called before | |
| 633 | + */ | |
| 634 | + public void clearDiskCache() { | |
| 635 | + checkConfiguration(); | |
| 636 | + configuration.diskCache.clear(); | |
| 637 | + } | |
| 638 | + | |
| 639 | + /** | |
| 640 | + * Returns URI of image which is loading at this moment into passed | |
| 641 | + * {@link ImageAware ImageAware} | |
| 642 | + */ | |
| 643 | + public String getLoadingUriForView(ImageAware imageAware) { | |
| 644 | + return engine.getLoadingUriForView(imageAware); | |
| 645 | + } | |
| 646 | + | |
| 647 | + /** | |
| 648 | + * Returns URI of image which is loading at this moment into passed | |
| 649 | + * {@link ImageView ImageView} | |
| 650 | + */ | |
| 651 | + public String getLoadingUriForView(ImageView imageView) { | |
| 652 | + return engine.getLoadingUriForView(new ImageViewAware(imageView)); | |
| 653 | + } | |
| 654 | + | |
| 655 | + /** | |
| 656 | + * Cancel the task of loading and displaying image for passed | |
| 657 | + * {@link ImageAware ImageAware}. | |
| 658 | + * | |
| 659 | + * @param imageAware {@link ImageAware ImageAware} for | |
| 660 | + * which display task will be cancelled | |
| 661 | + */ | |
| 662 | + public void cancelDisplayTask(ImageAware imageAware) { | |
| 663 | + engine.cancelDisplayTaskFor(imageAware); | |
| 664 | + } | |
| 665 | + | |
| 666 | + /** | |
| 667 | + * Cancel the task of loading and displaying image for passed | |
| 668 | + * {@link ImageView ImageView}. | |
| 669 | + * | |
| 670 | + * @param imageView {@link ImageView ImageView} for which display task will be cancelled | |
| 671 | + */ | |
| 672 | + public void cancelDisplayTask(ImageView imageView) { | |
| 673 | + engine.cancelDisplayTaskFor(new ImageViewAware(imageView)); | |
| 674 | + } | |
| 675 | + | |
| 676 | + /** | |
| 677 | + * Denies or allows ImageLoader to download images from the network.<br /> | |
| 678 | + * <br /> | |
| 679 | + * If downloads are denied and if image isn't cached then | |
| 680 | + * {@link ImageLoadingListener#onLoadingFailed(String, View, FailReason)} callback will be fired with | |
| 681 | + * {@link FailReason.FailType#NETWORK_DENIED} | |
| 682 | + * | |
| 683 | + * @param denyNetworkDownloads pass <b>true</b> - to deny engine to download images from the network; <b>false</b> - | |
| 684 | + * to allow engine to download images from network. | |
| 685 | + */ | |
| 686 | + public void denyNetworkDownloads(boolean denyNetworkDownloads) { | |
| 687 | + engine.denyNetworkDownloads(denyNetworkDownloads); | |
| 688 | + } | |
| 689 | + | |
| 690 | + /** | |
| 691 | + * Sets option whether ImageLoader will use {@link FlushedInputStream} for network downloads to handle <a | |
| 692 | + * href="http://code.google.com/p/android/issues/detail?id=6066">this known problem</a> or not. | |
| 693 | + * | |
| 694 | + * @param handleSlowNetwork pass <b>true</b> - to use {@link FlushedInputStream} for network downloads; <b>false</b> | |
| 695 | + * - otherwise. | |
| 696 | + */ | |
| 697 | + public void handleSlowNetwork(boolean handleSlowNetwork) { | |
| 698 | + engine.handleSlowNetwork(handleSlowNetwork); | |
| 699 | + } | |
| 700 | + | |
| 701 | + /** | |
| 702 | + * Pause ImageLoader. All new "load&display" tasks won't be executed until ImageLoader is {@link #resume() resumed}. | |
| 703 | + * <br /> | |
| 704 | + * Already running tasks are not paused. | |
| 705 | + */ | |
| 706 | + public void pause() { | |
| 707 | + engine.pause(); | |
| 708 | + } | |
| 709 | + | |
| 710 | + /** Resumes waiting "load&display" tasks */ | |
| 711 | + public void resume() { | |
| 712 | + engine.resume(); | |
| 713 | + } | |
| 714 | + | |
| 715 | + /** | |
| 716 | + * Cancels all running and scheduled display image tasks.<br /> | |
| 717 | + * <b>NOTE:</b> This method doesn't shutdown | |
| 718 | + * {@linkplain ImageLoaderConfiguration.Builder#taskExecutor(java.util.concurrent.Executor) | |
| 719 | + * custom task executors} if you set them.<br /> | |
| 720 | + * ImageLoader still can be used after calling this method. | |
| 721 | + */ | |
| 722 | + public void stop() { | |
| 723 | + engine.stop(); | |
| 724 | + } | |
| 725 | + | |
| 726 | + /** | |
| 727 | + * {@linkplain #stop() Stops ImageLoader} and clears current configuration. <br /> | |
| 728 | + * You can {@linkplain #init(ImageLoaderConfiguration) init} ImageLoader with new configuration after calling this | |
| 729 | + * method. | |
| 730 | + */ | |
| 731 | + public void destroy() { | |
| 732 | + if (configuration != null) L.d(LOG_DESTROY); | |
| 733 | + stop(); | |
| 734 | + configuration.diskCache.close(); | |
| 735 | + engine = null; | |
| 736 | + configuration = null; | |
| 737 | + } | |
| 738 | + | |
| 739 | + private static Handler defineHandler(DisplayImageOptions options) { | |
| 740 | + Handler handler = options.getHandler(); | |
| 741 | + if (options.isSyncLoading()) { | |
| 742 | + handler = null; | |
| 743 | + } else if (handler == null && Looper.myLooper() == Looper.getMainLooper()) { | |
| 744 | + handler = new Handler(); | |
| 745 | + } | |
| 746 | + return handler; | |
| 747 | + } | |
| 748 | + | |
| 749 | + /** | |
| 750 | + * Listener which is designed for synchronous image loading. | |
| 751 | + * | |
| 752 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 753 | + * @since 1.9.0 | |
| 754 | + */ | |
| 755 | + private static class SyncImageLoadingListener extends SimpleImageLoadingListener { | |
| 756 | + | |
| 757 | + private Bitmap loadedImage; | |
| 758 | + | |
| 759 | + @Override | |
| 760 | + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { | |
| 761 | + this.loadedImage = loadedImage; | |
| 762 | + } | |
| 763 | + | |
| 764 | + public Bitmap getLoadedBitmap() { | |
| 765 | + return loadedImage; | |
| 766 | + } | |
| 767 | + } | |
| 768 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core; | |
| 17 | + | |
| 18 | +import android.content.Context; | |
| 19 | +import android.content.res.Resources; | |
| 20 | +import android.util.DisplayMetrics; | |
| 21 | +import com.nostra13.universalimageloader.cache.disc.DiskCache; | |
| 22 | +import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; | |
| 23 | +import com.nostra13.universalimageloader.cache.memory.MemoryCache; | |
| 24 | +import com.nostra13.universalimageloader.cache.memory.impl.FuzzyKeyMemoryCache; | |
| 25 | +import com.nostra13.universalimageloader.core.assist.FlushedInputStream; | |
| 26 | +import com.nostra13.universalimageloader.core.assist.ImageSize; | |
| 27 | +import com.nostra13.universalimageloader.core.assist.QueueProcessingType; | |
| 28 | +import com.nostra13.universalimageloader.core.decode.ImageDecoder; | |
| 29 | +import com.nostra13.universalimageloader.core.download.ImageDownloader; | |
| 30 | +import com.nostra13.universalimageloader.core.process.BitmapProcessor; | |
| 31 | +import com.nostra13.universalimageloader.utils.L; | |
| 32 | +import com.nostra13.universalimageloader.utils.MemoryCacheUtils; | |
| 33 | + | |
| 34 | +import java.io.IOException; | |
| 35 | +import java.io.InputStream; | |
| 36 | +import java.util.concurrent.Executor; | |
| 37 | + | |
| 38 | +/** | |
| 39 | + * Presents configuration for {@link ImageLoader} | |
| 40 | + * | |
| 41 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 42 | + * @see ImageLoader | |
| 43 | + * @see MemoryCache | |
| 44 | + * @see DiskCache | |
| 45 | + * @see DisplayImageOptions | |
| 46 | + * @see ImageDownloader | |
| 47 | + * @see FileNameGenerator | |
| 48 | + * @since 1.0.0 | |
| 49 | + */ | |
| 50 | +public final class ImageLoaderConfiguration { | |
| 51 | + | |
| 52 | + final Resources resources; | |
| 53 | + | |
| 54 | + final int maxImageWidthForMemoryCache; | |
| 55 | + final int maxImageHeightForMemoryCache; | |
| 56 | + final int maxImageWidthForDiskCache; | |
| 57 | + final int maxImageHeightForDiskCache; | |
| 58 | + final BitmapProcessor processorForDiskCache; | |
| 59 | + | |
| 60 | + final Executor taskExecutor; | |
| 61 | + final Executor taskExecutorForCachedImages; | |
| 62 | + final boolean customExecutor; | |
| 63 | + final boolean customExecutorForCachedImages; | |
| 64 | + | |
| 65 | + final int threadPoolSize; | |
| 66 | + final int threadPriority; | |
| 67 | + final QueueProcessingType tasksProcessingType; | |
| 68 | + | |
| 69 | + final MemoryCache memoryCache; | |
| 70 | + final DiskCache diskCache; | |
| 71 | + final ImageDownloader downloader; | |
| 72 | + final ImageDecoder decoder; | |
| 73 | + final DisplayImageOptions defaultDisplayImageOptions; | |
| 74 | + | |
| 75 | + final ImageDownloader networkDeniedDownloader; | |
| 76 | + final ImageDownloader slowNetworkDownloader; | |
| 77 | + | |
| 78 | + private ImageLoaderConfiguration(final Builder builder) { | |
| 79 | + resources = builder.context.getResources(); | |
| 80 | + maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache; | |
| 81 | + maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache; | |
| 82 | + maxImageWidthForDiskCache = builder.maxImageWidthForDiskCache; | |
| 83 | + maxImageHeightForDiskCache = builder.maxImageHeightForDiskCache; | |
| 84 | + processorForDiskCache = builder.processorForDiskCache; | |
| 85 | + taskExecutor = builder.taskExecutor; | |
| 86 | + taskExecutorForCachedImages = builder.taskExecutorForCachedImages; | |
| 87 | + threadPoolSize = builder.threadPoolSize; | |
| 88 | + threadPriority = builder.threadPriority; | |
| 89 | + tasksProcessingType = builder.tasksProcessingType; | |
| 90 | + diskCache = builder.diskCache; | |
| 91 | + memoryCache = builder.memoryCache; | |
| 92 | + defaultDisplayImageOptions = builder.defaultDisplayImageOptions; | |
| 93 | + downloader = builder.downloader; | |
| 94 | + decoder = builder.decoder; | |
| 95 | + | |
| 96 | + customExecutor = builder.customExecutor; | |
| 97 | + customExecutorForCachedImages = builder.customExecutorForCachedImages; | |
| 98 | + | |
| 99 | + networkDeniedDownloader = new NetworkDeniedImageDownloader(downloader); | |
| 100 | + slowNetworkDownloader = new SlowNetworkImageDownloader(downloader); | |
| 101 | + | |
| 102 | + L.writeDebugLogs(builder.writeLogs); | |
| 103 | + } | |
| 104 | + | |
| 105 | + /** | |
| 106 | + * Creates default configuration for {@link ImageLoader} <br /> | |
| 107 | + * <b>Default values:</b> | |
| 108 | + * <ul> | |
| 109 | + * <li>maxImageWidthForMemoryCache = device's screen width</li> | |
| 110 | + * <li>maxImageHeightForMemoryCache = device's screen height</li> | |
| 111 | + * <li>maxImageWidthForDikcCache = unlimited</li> | |
| 112 | + * <li>maxImageHeightForDiskCache = unlimited</li> | |
| 113 | + * <li>threadPoolSize = {@link Builder#DEFAULT_THREAD_POOL_SIZE this}</li> | |
| 114 | + * <li>threadPriority = {@link Builder#DEFAULT_THREAD_PRIORITY this}</li> | |
| 115 | + * <li>allow to cache different sizes of image in memory</li> | |
| 116 | + * <li>memoryCache = {@link DefaultConfigurationFactory#createMemoryCache(Context, int)}</li> | |
| 117 | + * <li>diskCache = {@link com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache}</li> | |
| 118 | + * <li>imageDownloader = {@link DefaultConfigurationFactory#createImageDownloader(Context)}</li> | |
| 119 | + * <li>imageDecoder = {@link DefaultConfigurationFactory#createImageDecoder(boolean)}</li> | |
| 120 | + * <li>diskCacheFileNameGenerator = {@link DefaultConfigurationFactory#createFileNameGenerator()}</li> | |
| 121 | + * <li>defaultDisplayImageOptions = {@link DisplayImageOptions#createSimple() Simple options}</li> | |
| 122 | + * <li>tasksProcessingOrder = {@link QueueProcessingType#FIFO}</li> | |
| 123 | + * <li>detailed logging disabled</li> | |
| 124 | + * </ul> | |
| 125 | + */ | |
| 126 | + public static ImageLoaderConfiguration createDefault(Context context) { | |
| 127 | + return new Builder(context).build(); | |
| 128 | + } | |
| 129 | + | |
| 130 | + ImageSize getMaxImageSize() { | |
| 131 | + DisplayMetrics displayMetrics = resources.getDisplayMetrics(); | |
| 132 | + | |
| 133 | + int width = maxImageWidthForMemoryCache; | |
| 134 | + if (width <= 0) { | |
| 135 | + width = displayMetrics.widthPixels; | |
| 136 | + } | |
| 137 | + int height = maxImageHeightForMemoryCache; | |
| 138 | + if (height <= 0) { | |
| 139 | + height = displayMetrics.heightPixels; | |
| 140 | + } | |
| 141 | + return new ImageSize(width, height); | |
| 142 | + } | |
| 143 | + | |
| 144 | + /** | |
| 145 | + * Builder for {@link ImageLoaderConfiguration} | |
| 146 | + * | |
| 147 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 148 | + */ | |
| 149 | + public static class Builder { | |
| 150 | + | |
| 151 | + private static final String WARNING_OVERLAP_DISK_CACHE_PARAMS = "diskCache(), diskCacheSize() and diskCacheFileCount calls overlap each other"; | |
| 152 | + private static final String WARNING_OVERLAP_DISK_CACHE_NAME_GENERATOR = "diskCache() and diskCacheFileNameGenerator() calls overlap each other"; | |
| 153 | + private static final String WARNING_OVERLAP_MEMORY_CACHE = "memoryCache() and memoryCacheSize() calls overlap each other"; | |
| 154 | + private static final String WARNING_OVERLAP_EXECUTOR = "threadPoolSize(), threadPriority() and tasksProcessingOrder() calls " | |
| 155 | + + "can overlap taskExecutor() and taskExecutorForCachedImages() calls."; | |
| 156 | + | |
| 157 | + /** {@value} */ | |
| 158 | + public static final int DEFAULT_THREAD_POOL_SIZE = 3; | |
| 159 | + /** {@value} */ | |
| 160 | + public static final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY - 2; | |
| 161 | + /** {@value} */ | |
| 162 | + public static final QueueProcessingType DEFAULT_TASK_PROCESSING_TYPE = QueueProcessingType.FIFO; | |
| 163 | + | |
| 164 | + private Context context; | |
| 165 | + | |
| 166 | + private int maxImageWidthForMemoryCache = 0; | |
| 167 | + private int maxImageHeightForMemoryCache = 0; | |
| 168 | + private int maxImageWidthForDiskCache = 0; | |
| 169 | + private int maxImageHeightForDiskCache = 0; | |
| 170 | + private BitmapProcessor processorForDiskCache = null; | |
| 171 | + | |
| 172 | + private Executor taskExecutor = null; | |
| 173 | + private Executor taskExecutorForCachedImages = null; | |
| 174 | + private boolean customExecutor = false; | |
| 175 | + private boolean customExecutorForCachedImages = false; | |
| 176 | + | |
| 177 | + private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE; | |
| 178 | + private int threadPriority = DEFAULT_THREAD_PRIORITY; | |
| 179 | + private boolean denyCacheImageMultipleSizesInMemory = false; | |
| 180 | + private QueueProcessingType tasksProcessingType = DEFAULT_TASK_PROCESSING_TYPE; | |
| 181 | + | |
| 182 | + private int memoryCacheSize = 0; | |
| 183 | + private long diskCacheSize = 0; | |
| 184 | + private int diskCacheFileCount = 0; | |
| 185 | + | |
| 186 | + private MemoryCache memoryCache = null; | |
| 187 | + private DiskCache diskCache = null; | |
| 188 | + private FileNameGenerator diskCacheFileNameGenerator = null; | |
| 189 | + private ImageDownloader downloader = null; | |
| 190 | + private ImageDecoder decoder; | |
| 191 | + private DisplayImageOptions defaultDisplayImageOptions = null; | |
| 192 | + | |
| 193 | + private boolean writeLogs = false; | |
| 194 | + | |
| 195 | + public Builder(Context context) { | |
| 196 | + this.context = context.getApplicationContext(); | |
| 197 | + } | |
| 198 | + | |
| 199 | + /** | |
| 200 | + * Sets options for memory cache | |
| 201 | + * | |
| 202 | + * @param maxImageWidthForMemoryCache Maximum image width which will be used for memory saving during decoding | |
| 203 | + * an image to {@link android.graphics.Bitmap Bitmap}. <b>Default value - device's screen width</b> | |
| 204 | + * @param maxImageHeightForMemoryCache Maximum image height which will be used for memory saving during decoding | |
| 205 | + * an image to {@link android.graphics.Bitmap Bitmap}. <b>Default value</b> - device's screen height | |
| 206 | + */ | |
| 207 | + public Builder memoryCacheExtraOptions(int maxImageWidthForMemoryCache, int maxImageHeightForMemoryCache) { | |
| 208 | + this.maxImageWidthForMemoryCache = maxImageWidthForMemoryCache; | |
| 209 | + this.maxImageHeightForMemoryCache = maxImageHeightForMemoryCache; | |
| 210 | + return this; | |
| 211 | + } | |
| 212 | + | |
| 213 | + /** | |
| 214 | + * @deprecated Use | |
| 215 | + * {@link #diskCacheExtraOptions(int, int, BitmapProcessor)} | |
| 216 | + * instead | |
| 217 | + */ | |
| 218 | + @Deprecated | |
| 219 | + public Builder discCacheExtraOptions(int maxImageWidthForDiskCache, int maxImageHeightForDiskCache, | |
| 220 | + BitmapProcessor processorForDiskCache) { | |
| 221 | + return diskCacheExtraOptions(maxImageWidthForDiskCache, maxImageHeightForDiskCache, processorForDiskCache); | |
| 222 | + } | |
| 223 | + | |
| 224 | + /** | |
| 225 | + * Sets options for resizing/compressing of downloaded images before saving to disk cache.<br /> | |
| 226 | + * <b>NOTE: Use this option only when you have appropriate needs. It can make ImageLoader slower.</b> | |
| 227 | + * | |
| 228 | + * @param maxImageWidthForDiskCache Maximum width of downloaded images for saving at disk cache | |
| 229 | + * @param maxImageHeightForDiskCache Maximum height of downloaded images for saving at disk cache | |
| 230 | + * @param processorForDiskCache null-ok; {@linkplain BitmapProcessor Bitmap processor} which process images before saving them in disc cache | |
| 231 | + */ | |
| 232 | + public Builder diskCacheExtraOptions(int maxImageWidthForDiskCache, int maxImageHeightForDiskCache, | |
| 233 | + BitmapProcessor processorForDiskCache) { | |
| 234 | + this.maxImageWidthForDiskCache = maxImageWidthForDiskCache; | |
| 235 | + this.maxImageHeightForDiskCache = maxImageHeightForDiskCache; | |
| 236 | + this.processorForDiskCache = processorForDiskCache; | |
| 237 | + return this; | |
| 238 | + } | |
| 239 | + | |
| 240 | + /** | |
| 241 | + * Sets custom {@linkplain Executor executor} for tasks of loading and displaying images.<br /> | |
| 242 | + * <br /> | |
| 243 | + * <b>NOTE:</b> If you set custom executor then following configuration options will not be considered for this | |
| 244 | + * executor: | |
| 245 | + * <ul> | |
| 246 | + * <li>{@link #threadPoolSize(int)}</li> | |
| 247 | + * <li>{@link #threadPriority(int)}</li> | |
| 248 | + * <li>{@link #tasksProcessingOrder(QueueProcessingType)}</li> | |
| 249 | + * </ul> | |
| 250 | + * | |
| 251 | + * @see #taskExecutorForCachedImages(Executor) | |
| 252 | + */ | |
| 253 | + public Builder taskExecutor(Executor executor) { | |
| 254 | + if (threadPoolSize != DEFAULT_THREAD_POOL_SIZE || threadPriority != DEFAULT_THREAD_PRIORITY || tasksProcessingType != DEFAULT_TASK_PROCESSING_TYPE) { | |
| 255 | + L.w(WARNING_OVERLAP_EXECUTOR); | |
| 256 | + } | |
| 257 | + | |
| 258 | + this.taskExecutor = executor; | |
| 259 | + return this; | |
| 260 | + } | |
| 261 | + | |
| 262 | + /** | |
| 263 | + * Sets custom {@linkplain Executor executor} for tasks of displaying <b>cached on disk</b> images (these tasks | |
| 264 | + * are executed quickly so UIL prefer to use separate executor for them).<br /> | |
| 265 | + * <br /> | |
| 266 | + * If you set the same executor for {@linkplain #taskExecutor(Executor) general tasks} and | |
| 267 | + * tasks about cached images (this method) then these tasks will be in the | |
| 268 | + * same thread pool. So short-lived tasks can wait a long time for their turn.<br /> | |
| 269 | + * <br /> | |
| 270 | + * <b>NOTE:</b> If you set custom executor then following configuration options will not be considered for this | |
| 271 | + * executor: | |
| 272 | + * <ul> | |
| 273 | + * <li>{@link #threadPoolSize(int)}</li> | |
| 274 | + * <li>{@link #threadPriority(int)}</li> | |
| 275 | + * <li>{@link #tasksProcessingOrder(QueueProcessingType)}</li> | |
| 276 | + * </ul> | |
| 277 | + * | |
| 278 | + * @see #taskExecutor(Executor) | |
| 279 | + */ | |
| 280 | + public Builder taskExecutorForCachedImages(Executor executorForCachedImages) { | |
| 281 | + if (threadPoolSize != DEFAULT_THREAD_POOL_SIZE || threadPriority != DEFAULT_THREAD_PRIORITY || tasksProcessingType != DEFAULT_TASK_PROCESSING_TYPE) { | |
| 282 | + L.w(WARNING_OVERLAP_EXECUTOR); | |
| 283 | + } | |
| 284 | + | |
| 285 | + this.taskExecutorForCachedImages = executorForCachedImages; | |
| 286 | + return this; | |
| 287 | + } | |
| 288 | + | |
| 289 | + /** | |
| 290 | + * Sets thread pool size for image display tasks.<br /> | |
| 291 | + * Default value - {@link #DEFAULT_THREAD_POOL_SIZE this} | |
| 292 | + */ | |
| 293 | + public Builder threadPoolSize(int threadPoolSize) { | |
| 294 | + if (taskExecutor != null || taskExecutorForCachedImages != null) { | |
| 295 | + L.w(WARNING_OVERLAP_EXECUTOR); | |
| 296 | + } | |
| 297 | + | |
| 298 | + this.threadPoolSize = threadPoolSize; | |
| 299 | + return this; | |
| 300 | + } | |
| 301 | + | |
| 302 | + /** | |
| 303 | + * Sets the priority for image loading threads. Should be <b>NOT</b> greater than {@link Thread#MAX_PRIORITY} or | |
| 304 | + * less than {@link Thread#MIN_PRIORITY}<br /> | |
| 305 | + * Default value - {@link #DEFAULT_THREAD_PRIORITY this} | |
| 306 | + */ | |
| 307 | + public Builder threadPriority(int threadPriority) { | |
| 308 | + if (taskExecutor != null || taskExecutorForCachedImages != null) { | |
| 309 | + L.w(WARNING_OVERLAP_EXECUTOR); | |
| 310 | + } | |
| 311 | + | |
| 312 | + if (threadPriority < Thread.MIN_PRIORITY) { | |
| 313 | + this.threadPriority = Thread.MIN_PRIORITY; | |
| 314 | + } else { | |
| 315 | + if (threadPriority > Thread.MAX_PRIORITY) { | |
| 316 | + this.threadPriority = Thread.MAX_PRIORITY; | |
| 317 | + } else { | |
| 318 | + this.threadPriority = threadPriority; | |
| 319 | + } | |
| 320 | + } | |
| 321 | + return this; | |
| 322 | + } | |
| 323 | + | |
| 324 | + /** | |
| 325 | + * When you display an image in a small {@link android.widget.ImageView ImageView} and later you try to display | |
| 326 | + * this image (from identical URI) in a larger {@link android.widget.ImageView ImageView} so decoded image of | |
| 327 | + * bigger size will be cached in memory as a previous decoded image of smaller size.<br /> | |
| 328 | + * So <b>the default behavior is to allow to cache multiple sizes of one image in memory</b>. You can | |
| 329 | + * <b>deny</b> it by calling <b>this</b> method: so when some image will be cached in memory then previous | |
| 330 | + * cached size of this image (if it exists) will be removed from memory cache before. | |
| 331 | + */ | |
| 332 | + public Builder denyCacheImageMultipleSizesInMemory() { | |
| 333 | + this.denyCacheImageMultipleSizesInMemory = true; | |
| 334 | + return this; | |
| 335 | + } | |
| 336 | + | |
| 337 | + /** | |
| 338 | + * Sets type of queue processing for tasks for loading and displaying images.<br /> | |
| 339 | + * Default value - {@link QueueProcessingType#FIFO} | |
| 340 | + */ | |
| 341 | + public Builder tasksProcessingOrder(QueueProcessingType tasksProcessingType) { | |
| 342 | + if (taskExecutor != null || taskExecutorForCachedImages != null) { | |
| 343 | + L.w(WARNING_OVERLAP_EXECUTOR); | |
| 344 | + } | |
| 345 | + | |
| 346 | + this.tasksProcessingType = tasksProcessingType; | |
| 347 | + return this; | |
| 348 | + } | |
| 349 | + | |
| 350 | + /** | |
| 351 | + * Sets maximum memory cache size for {@link android.graphics.Bitmap bitmaps} (in bytes).<br /> | |
| 352 | + * Default value - 1/8 of available app memory.<br /> | |
| 353 | + * <b>NOTE:</b> If you use this method then | |
| 354 | + * {@link com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache LruMemoryCache} will be used as | |
| 355 | + * memory cache. You can use {@link #memoryCache(MemoryCache)} method to set your own implementation of | |
| 356 | + * {@link MemoryCache}. | |
| 357 | + */ | |
| 358 | + public Builder memoryCacheSize(int memoryCacheSize) { | |
| 359 | + if (memoryCacheSize <= 0) throw new IllegalArgumentException("memoryCacheSize must be a positive number"); | |
| 360 | + | |
| 361 | + if (memoryCache != null) { | |
| 362 | + L.w(WARNING_OVERLAP_MEMORY_CACHE); | |
| 363 | + } | |
| 364 | + | |
| 365 | + this.memoryCacheSize = memoryCacheSize; | |
| 366 | + return this; | |
| 367 | + } | |
| 368 | + | |
| 369 | + /** | |
| 370 | + * Sets maximum memory cache size (in percent of available app memory) for {@link android.graphics.Bitmap | |
| 371 | + * bitmaps}.<br /> | |
| 372 | + * Default value - 1/8 of available app memory.<br /> | |
| 373 | + * <b>NOTE:</b> If you use this method then | |
| 374 | + * {@link com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache LruMemoryCache} will be used as | |
| 375 | + * memory cache. You can use {@link #memoryCache(MemoryCache)} method to set your own implementation of | |
| 376 | + * {@link MemoryCache}. | |
| 377 | + */ | |
| 378 | + public Builder memoryCacheSizePercentage(int availableMemoryPercent) { | |
| 379 | + if (availableMemoryPercent <= 0 || availableMemoryPercent >= 100) { | |
| 380 | + throw new IllegalArgumentException("availableMemoryPercent must be in range (0 < % < 100)"); | |
| 381 | + } | |
| 382 | + | |
| 383 | + if (memoryCache != null) { | |
| 384 | + L.w(WARNING_OVERLAP_MEMORY_CACHE); | |
| 385 | + } | |
| 386 | + | |
| 387 | + long availableMemory = Runtime.getRuntime().maxMemory(); | |
| 388 | + memoryCacheSize = (int) (availableMemory * (availableMemoryPercent / 100f)); | |
| 389 | + return this; | |
| 390 | + } | |
| 391 | + | |
| 392 | + /** | |
| 393 | + * Sets memory cache for {@link android.graphics.Bitmap bitmaps}.<br /> | |
| 394 | + * Default value - {@link com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache LruMemoryCache} | |
| 395 | + * with limited memory cache size (size = 1/8 of available app memory)<br /> | |
| 396 | + * <br /> | |
| 397 | + * <b>NOTE:</b> If you set custom memory cache then following configuration option will not be considered: | |
| 398 | + * <ul> | |
| 399 | + * <li>{@link #memoryCacheSize(int)}</li> | |
| 400 | + * </ul> | |
| 401 | + */ | |
| 402 | + public Builder memoryCache(MemoryCache memoryCache) { | |
| 403 | + if (memoryCacheSize != 0) { | |
| 404 | + L.w(WARNING_OVERLAP_MEMORY_CACHE); | |
| 405 | + } | |
| 406 | + | |
| 407 | + this.memoryCache = memoryCache; | |
| 408 | + return this; | |
| 409 | + } | |
| 410 | + | |
| 411 | + /** @deprecated Use {@link #diskCacheSize(int)} instead */ | |
| 412 | + @Deprecated | |
| 413 | + public Builder discCacheSize(int maxCacheSize) { | |
| 414 | + return diskCacheSize(maxCacheSize); | |
| 415 | + } | |
| 416 | + | |
| 417 | + /** | |
| 418 | + * Sets maximum disk cache size for images (in bytes).<br /> | |
| 419 | + * By default: disk cache is unlimited.<br /> | |
| 420 | + * <b>NOTE:</b> If you use this method then | |
| 421 | + * {@link com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache LruDiskCache} | |
| 422 | + * will be used as disk cache. You can use {@link #diskCache(DiskCache)} method for introduction your own | |
| 423 | + * implementation of {@link DiskCache} | |
| 424 | + */ | |
| 425 | + public Builder diskCacheSize(int maxCacheSize) { | |
| 426 | + if (maxCacheSize <= 0) throw new IllegalArgumentException("maxCacheSize must be a positive number"); | |
| 427 | + | |
| 428 | + if (diskCache != null) { | |
| 429 | + L.w(WARNING_OVERLAP_DISK_CACHE_PARAMS); | |
| 430 | + } | |
| 431 | + | |
| 432 | + this.diskCacheSize = maxCacheSize; | |
| 433 | + return this; | |
| 434 | + } | |
| 435 | + | |
| 436 | + /** @deprecated Use {@link #diskCacheFileCount(int)} instead */ | |
| 437 | + @Deprecated | |
| 438 | + public Builder discCacheFileCount(int maxFileCount) { | |
| 439 | + return diskCacheFileCount(maxFileCount); | |
| 440 | + } | |
| 441 | + | |
| 442 | + /** | |
| 443 | + * Sets maximum file count in disk cache directory.<br /> | |
| 444 | + * By default: disk cache is unlimited.<br /> | |
| 445 | + * <b>NOTE:</b> If you use this method then | |
| 446 | + * {@link com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache LruDiskCache} | |
| 447 | + * will be used as disk cache. You can use {@link #diskCache(DiskCache)} method for introduction your own | |
| 448 | + * implementation of {@link DiskCache} | |
| 449 | + */ | |
| 450 | + public Builder diskCacheFileCount(int maxFileCount) { | |
| 451 | + if (maxFileCount <= 0) throw new IllegalArgumentException("maxFileCount must be a positive number"); | |
| 452 | + | |
| 453 | + if (diskCache != null) { | |
| 454 | + L.w(WARNING_OVERLAP_DISK_CACHE_PARAMS); | |
| 455 | + } | |
| 456 | + | |
| 457 | + this.diskCacheFileCount = maxFileCount; | |
| 458 | + return this; | |
| 459 | + } | |
| 460 | + | |
| 461 | + /** @deprecated Use {@link #diskCacheFileNameGenerator(FileNameGenerator)} */ | |
| 462 | + @Deprecated | |
| 463 | + public Builder discCacheFileNameGenerator(FileNameGenerator fileNameGenerator) { | |
| 464 | + return diskCacheFileNameGenerator(fileNameGenerator); | |
| 465 | + } | |
| 466 | + | |
| 467 | + /** | |
| 468 | + * Sets name generator for files cached in disk cache.<br /> | |
| 469 | + * Default value - | |
| 470 | + * {@link DefaultConfigurationFactory#createFileNameGenerator() | |
| 471 | + * DefaultConfigurationFactory.createFileNameGenerator()} | |
| 472 | + */ | |
| 473 | + public Builder diskCacheFileNameGenerator(FileNameGenerator fileNameGenerator) { | |
| 474 | + if (diskCache != null) { | |
| 475 | + L.w(WARNING_OVERLAP_DISK_CACHE_NAME_GENERATOR); | |
| 476 | + } | |
| 477 | + | |
| 478 | + this.diskCacheFileNameGenerator = fileNameGenerator; | |
| 479 | + return this; | |
| 480 | + } | |
| 481 | + | |
| 482 | + /** @deprecated Use {@link #diskCache(DiskCache)} */ | |
| 483 | + @Deprecated | |
| 484 | + public Builder discCache(DiskCache diskCache) { | |
| 485 | + return diskCache(diskCache); | |
| 486 | + } | |
| 487 | + | |
| 488 | + /** | |
| 489 | + * Sets disk cache for images.<br /> | |
| 490 | + * Default value - {@link com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache | |
| 491 | + * UnlimitedDiskCache}. Cache directory is defined by | |
| 492 | + * {@link com.nostra13.universalimageloader.utils.StorageUtils#getCacheDirectory(Context) | |
| 493 | + * StorageUtils.getCacheDirectory(Context)}.<br /> | |
| 494 | + * <br /> | |
| 495 | + * <b>NOTE:</b> If you set custom disk cache then following configuration option will not be considered: | |
| 496 | + * <ul> | |
| 497 | + * <li>{@link #diskCacheSize(int)}</li> | |
| 498 | + * <li>{@link #diskCacheFileCount(int)}</li> | |
| 499 | + * <li>{@link #diskCacheFileNameGenerator(FileNameGenerator)}</li> | |
| 500 | + * </ul> | |
| 501 | + */ | |
| 502 | + public Builder diskCache(DiskCache diskCache) { | |
| 503 | + if (diskCacheSize > 0 || diskCacheFileCount > 0) { | |
| 504 | + L.w(WARNING_OVERLAP_DISK_CACHE_PARAMS); | |
| 505 | + } | |
| 506 | + if (diskCacheFileNameGenerator != null) { | |
| 507 | + L.w(WARNING_OVERLAP_DISK_CACHE_NAME_GENERATOR); | |
| 508 | + } | |
| 509 | + | |
| 510 | + this.diskCache = diskCache; | |
| 511 | + return this; | |
| 512 | + } | |
| 513 | + | |
| 514 | + /** | |
| 515 | + * Sets utility which will be responsible for downloading of image.<br /> | |
| 516 | + * Default value - | |
| 517 | + * {@link DefaultConfigurationFactory#createImageDownloader(Context) | |
| 518 | + * DefaultConfigurationFactory.createImageDownloader()} | |
| 519 | + */ | |
| 520 | + public Builder imageDownloader(ImageDownloader imageDownloader) { | |
| 521 | + this.downloader = imageDownloader; | |
| 522 | + return this; | |
| 523 | + } | |
| 524 | + | |
| 525 | + /** | |
| 526 | + * Sets utility which will be responsible for decoding of image stream.<br /> | |
| 527 | + * Default value - | |
| 528 | + * {@link DefaultConfigurationFactory#createImageDecoder(boolean) | |
| 529 | + * DefaultConfigurationFactory.createImageDecoder()} | |
| 530 | + */ | |
| 531 | + public Builder imageDecoder(ImageDecoder imageDecoder) { | |
| 532 | + this.decoder = imageDecoder; | |
| 533 | + return this; | |
| 534 | + } | |
| 535 | + | |
| 536 | + /** | |
| 537 | + * Sets default {@linkplain DisplayImageOptions display image options} for image displaying. These options will | |
| 538 | + * be used for every {@linkplain ImageLoader#displayImage(String, android.widget.ImageView) image display call} | |
| 539 | + * without passing custom {@linkplain DisplayImageOptions options}<br /> | |
| 540 | + * Default value - {@link DisplayImageOptions#createSimple() Simple options} | |
| 541 | + */ | |
| 542 | + public Builder defaultDisplayImageOptions(DisplayImageOptions defaultDisplayImageOptions) { | |
| 543 | + this.defaultDisplayImageOptions = defaultDisplayImageOptions; | |
| 544 | + return this; | |
| 545 | + } | |
| 546 | + | |
| 547 | + /** | |
| 548 | + * Enables detail logging of {@link ImageLoader} work. To prevent detail logs don't call this method. | |
| 549 | + * Consider {@link L#disableLogging()} to disable | |
| 550 | + * ImageLoader logging completely (even error logs) | |
| 551 | + */ | |
| 552 | + public Builder writeDebugLogs() { | |
| 553 | + this.writeLogs = true; | |
| 554 | + return this; | |
| 555 | + } | |
| 556 | + | |
| 557 | + /** Builds configured {@link ImageLoaderConfiguration} object */ | |
| 558 | + public ImageLoaderConfiguration build() { | |
| 559 | + initEmptyFieldsWithDefaultValues(); | |
| 560 | + return new ImageLoaderConfiguration(this); | |
| 561 | + } | |
| 562 | + | |
| 563 | + private void initEmptyFieldsWithDefaultValues() { | |
| 564 | + if (taskExecutor == null) { | |
| 565 | + taskExecutor = DefaultConfigurationFactory | |
| 566 | + .createExecutor(threadPoolSize, threadPriority, tasksProcessingType); | |
| 567 | + } else { | |
| 568 | + customExecutor = true; | |
| 569 | + } | |
| 570 | + if (taskExecutorForCachedImages == null) { | |
| 571 | + taskExecutorForCachedImages = DefaultConfigurationFactory | |
| 572 | + .createExecutor(threadPoolSize, threadPriority, tasksProcessingType); | |
| 573 | + } else { | |
| 574 | + customExecutorForCachedImages = true; | |
| 575 | + } | |
| 576 | + if (diskCache == null) { | |
| 577 | + if (diskCacheFileNameGenerator == null) { | |
| 578 | + diskCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator(); | |
| 579 | + } | |
| 580 | + diskCache = DefaultConfigurationFactory | |
| 581 | + .createDiskCache(context, diskCacheFileNameGenerator, diskCacheSize, diskCacheFileCount); | |
| 582 | + } | |
| 583 | + if (memoryCache == null) { | |
| 584 | + memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize); | |
| 585 | + } | |
| 586 | + if (denyCacheImageMultipleSizesInMemory) { | |
| 587 | + memoryCache = new FuzzyKeyMemoryCache(memoryCache, MemoryCacheUtils.createFuzzyKeyComparator()); | |
| 588 | + } | |
| 589 | + if (downloader == null) { | |
| 590 | + downloader = DefaultConfigurationFactory.createImageDownloader(context); | |
| 591 | + } | |
| 592 | + if (decoder == null) { | |
| 593 | + decoder = DefaultConfigurationFactory.createImageDecoder(writeLogs); | |
| 594 | + } | |
| 595 | + if (defaultDisplayImageOptions == null) { | |
| 596 | + defaultDisplayImageOptions = DisplayImageOptions.createSimple(); | |
| 597 | + } | |
| 598 | + } | |
| 599 | + } | |
| 600 | + | |
| 601 | + /** | |
| 602 | + * Decorator. Prevents downloads from network (throws {@link IllegalStateException exception}).<br /> | |
| 603 | + * In most cases this downloader shouldn't be used directly. | |
| 604 | + * | |
| 605 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 606 | + * @since 1.8.0 | |
| 607 | + */ | |
| 608 | + private static class NetworkDeniedImageDownloader implements ImageDownloader { | |
| 609 | + | |
| 610 | + private final ImageDownloader wrappedDownloader; | |
| 611 | + | |
| 612 | + public NetworkDeniedImageDownloader(ImageDownloader wrappedDownloader) { | |
| 613 | + this.wrappedDownloader = wrappedDownloader; | |
| 614 | + } | |
| 615 | + | |
| 616 | + @Override | |
| 617 | + public InputStream getStream(String imageUri, Object extra) throws IOException { | |
| 618 | + switch (Scheme.ofUri(imageUri)) { | |
| 619 | + case HTTP: | |
| 620 | + case HTTPS: | |
| 621 | + throw new IllegalStateException(); | |
| 622 | + default: | |
| 623 | + return wrappedDownloader.getStream(imageUri, extra); | |
| 624 | + } | |
| 625 | + } | |
| 626 | + } | |
| 627 | + | |
| 628 | + /** | |
| 629 | + * Decorator. Handles <a href="http://code.google.com/p/android/issues/detail?id=6066">this problem</a> on slow networks | |
| 630 | + * using {@link FlushedInputStream}. | |
| 631 | + * | |
| 632 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 633 | + * @since 1.8.1 | |
| 634 | + */ | |
| 635 | + private static class SlowNetworkImageDownloader implements ImageDownloader { | |
| 636 | + | |
| 637 | + private final ImageDownloader wrappedDownloader; | |
| 638 | + | |
| 639 | + public SlowNetworkImageDownloader(ImageDownloader wrappedDownloader) { | |
| 640 | + this.wrappedDownloader = wrappedDownloader; | |
| 641 | + } | |
| 642 | + | |
| 643 | + @Override | |
| 644 | + public InputStream getStream(String imageUri, Object extra) throws IOException { | |
| 645 | + InputStream imageStream = wrappedDownloader.getStream(imageUri, extra); | |
| 646 | + switch (Scheme.ofUri(imageUri)) { | |
| 647 | + case HTTP: | |
| 648 | + case HTTPS: | |
| 649 | + return new FlushedInputStream(imageStream); | |
| 650 | + default: | |
| 651 | + return imageStream; | |
| 652 | + } | |
| 653 | + } | |
| 654 | + } | |
| 655 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core; | |
| 17 | + | |
| 18 | +import android.view.View; | |
| 19 | +import com.nostra13.universalimageloader.core.assist.FailReason; | |
| 20 | +import com.nostra13.universalimageloader.core.assist.FlushedInputStream; | |
| 21 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 22 | +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; | |
| 23 | + | |
| 24 | +import java.io.File; | |
| 25 | +import java.util.Collections; | |
| 26 | +import java.util.HashMap; | |
| 27 | +import java.util.Map; | |
| 28 | +import java.util.WeakHashMap; | |
| 29 | +import java.util.concurrent.Executor; | |
| 30 | +import java.util.concurrent.ExecutorService; | |
| 31 | +import java.util.concurrent.atomic.AtomicBoolean; | |
| 32 | +import java.util.concurrent.locks.ReentrantLock; | |
| 33 | + | |
| 34 | +/** | |
| 35 | + * {@link ImageLoader} engine which responsible for {@linkplain LoadAndDisplayImageTask display task} execution. | |
| 36 | + * | |
| 37 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 38 | + * @since 1.7.1 | |
| 39 | + */ | |
| 40 | +class ImageLoaderEngine { | |
| 41 | + | |
| 42 | + final ImageLoaderConfiguration configuration; | |
| 43 | + | |
| 44 | + private Executor taskExecutor; | |
| 45 | + private Executor taskExecutorForCachedImages; | |
| 46 | + private Executor taskDistributor; | |
| 47 | + | |
| 48 | + private final Map<Integer, String> cacheKeysForImageAwares = Collections | |
| 49 | + .synchronizedMap(new HashMap<Integer, String>()); | |
| 50 | + private final Map<String, ReentrantLock> uriLocks = new WeakHashMap<String, ReentrantLock>(); | |
| 51 | + | |
| 52 | + private final AtomicBoolean paused = new AtomicBoolean(false); | |
| 53 | + private final AtomicBoolean networkDenied = new AtomicBoolean(false); | |
| 54 | + private final AtomicBoolean slowNetwork = new AtomicBoolean(false); | |
| 55 | + | |
| 56 | + private final Object pauseLock = new Object(); | |
| 57 | + | |
| 58 | + ImageLoaderEngine(ImageLoaderConfiguration configuration) { | |
| 59 | + this.configuration = configuration; | |
| 60 | + | |
| 61 | + taskExecutor = configuration.taskExecutor; | |
| 62 | + taskExecutorForCachedImages = configuration.taskExecutorForCachedImages; | |
| 63 | + | |
| 64 | + taskDistributor = DefaultConfigurationFactory.createTaskDistributor(); | |
| 65 | + } | |
| 66 | + | |
| 67 | + /** Submits task to execution pool */ | |
| 68 | + void submit(final LoadAndDisplayImageTask task) { | |
| 69 | + taskDistributor.execute(new Runnable() { | |
| 70 | + @Override | |
| 71 | + public void run() { | |
| 72 | + File image = configuration.diskCache.get(task.getLoadingUri()); | |
| 73 | + boolean isImageCachedOnDisk = image != null && image.exists(); | |
| 74 | + initExecutorsIfNeed(); | |
| 75 | + if (isImageCachedOnDisk) { | |
| 76 | + taskExecutorForCachedImages.execute(task); | |
| 77 | + } else { | |
| 78 | + taskExecutor.execute(task); | |
| 79 | + } | |
| 80 | + } | |
| 81 | + }); | |
| 82 | + } | |
| 83 | + | |
| 84 | + /** Submits task to execution pool */ | |
| 85 | + void submit(ProcessAndDisplayImageTask task) { | |
| 86 | + initExecutorsIfNeed(); | |
| 87 | + taskExecutorForCachedImages.execute(task); | |
| 88 | + } | |
| 89 | + | |
| 90 | + private void initExecutorsIfNeed() { | |
| 91 | + if (!configuration.customExecutor && ((ExecutorService) taskExecutor).isShutdown()) { | |
| 92 | + taskExecutor = createTaskExecutor(); | |
| 93 | + } | |
| 94 | + if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages) | |
| 95 | + .isShutdown()) { | |
| 96 | + taskExecutorForCachedImages = createTaskExecutor(); | |
| 97 | + } | |
| 98 | + } | |
| 99 | + | |
| 100 | + private Executor createTaskExecutor() { | |
| 101 | + return DefaultConfigurationFactory | |
| 102 | + .createExecutor(configuration.threadPoolSize, configuration.threadPriority, | |
| 103 | + configuration.tasksProcessingType); | |
| 104 | + } | |
| 105 | + | |
| 106 | + /** | |
| 107 | + * Returns URI of image which is loading at this moment into passed {@link ImageAware} | |
| 108 | + */ | |
| 109 | + String getLoadingUriForView(ImageAware imageAware) { | |
| 110 | + return cacheKeysForImageAwares.get(imageAware.getId()); | |
| 111 | + } | |
| 112 | + | |
| 113 | + /** | |
| 114 | + * Associates <b>memoryCacheKey</b> with <b>imageAware</b>. Then it helps to define image URI is loaded into View at | |
| 115 | + * exact moment. | |
| 116 | + */ | |
| 117 | + void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) { | |
| 118 | + cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey); | |
| 119 | + } | |
| 120 | + | |
| 121 | + /** | |
| 122 | + * Cancels the task of loading and displaying image for incoming <b>imageAware</b>. | |
| 123 | + * | |
| 124 | + * @param imageAware {@link ImageAware} for which display task | |
| 125 | + * will be cancelled | |
| 126 | + */ | |
| 127 | + void cancelDisplayTaskFor(ImageAware imageAware) { | |
| 128 | + cacheKeysForImageAwares.remove(imageAware.getId()); | |
| 129 | + } | |
| 130 | + | |
| 131 | + /** | |
| 132 | + * Denies or allows engine to download images from the network.<br /> <br /> If downloads are denied and if image | |
| 133 | + * isn't cached then {@link ImageLoadingListener#onLoadingFailed(String, View, FailReason)} callback will be fired | |
| 134 | + * with {@link FailReason.FailType#NETWORK_DENIED} | |
| 135 | + * | |
| 136 | + * @param denyNetworkDownloads pass <b>true</b> - to deny engine to download images from the network; <b>false</b> - | |
| 137 | + * to allow engine to download images from network. | |
| 138 | + */ | |
| 139 | + void denyNetworkDownloads(boolean denyNetworkDownloads) { | |
| 140 | + networkDenied.set(denyNetworkDownloads); | |
| 141 | + } | |
| 142 | + | |
| 143 | + /** | |
| 144 | + * Sets option whether ImageLoader will use {@link FlushedInputStream} for network downloads to handle <a | |
| 145 | + * href="http://code.google.com/p/android/issues/detail?id=6066">this known problem</a> or not. | |
| 146 | + * | |
| 147 | + * @param handleSlowNetwork pass <b>true</b> - to use {@link FlushedInputStream} for network downloads; <b>false</b> | |
| 148 | + * - otherwise. | |
| 149 | + */ | |
| 150 | + void handleSlowNetwork(boolean handleSlowNetwork) { | |
| 151 | + slowNetwork.set(handleSlowNetwork); | |
| 152 | + } | |
| 153 | + | |
| 154 | + /** | |
| 155 | + * Pauses engine. All new "load&display" tasks won't be executed until ImageLoader is {@link #resume() resumed}.<br | |
| 156 | + * /> Already running tasks are not paused. | |
| 157 | + */ | |
| 158 | + void pause() { | |
| 159 | + paused.set(true); | |
| 160 | + } | |
| 161 | + | |
| 162 | + /** Resumes engine work. Paused "load&display" tasks will continue its work. */ | |
| 163 | + void resume() { | |
| 164 | + paused.set(false); | |
| 165 | + synchronized (pauseLock) { | |
| 166 | + pauseLock.notifyAll(); | |
| 167 | + } | |
| 168 | + } | |
| 169 | + | |
| 170 | + /** | |
| 171 | + * Stops engine, cancels all running and scheduled display image tasks. Clears internal data. | |
| 172 | + * <br /> | |
| 173 | + * <b>NOTE:</b> This method doesn't shutdown | |
| 174 | + * {@linkplain ImageLoaderConfiguration.Builder#taskExecutor(Executor) | |
| 175 | + * custom task executors} if you set them. | |
| 176 | + */ | |
| 177 | + void stop() { | |
| 178 | + if (!configuration.customExecutor) { | |
| 179 | + ((ExecutorService) taskExecutor).shutdownNow(); | |
| 180 | + } | |
| 181 | + if (!configuration.customExecutorForCachedImages) { | |
| 182 | + ((ExecutorService) taskExecutorForCachedImages).shutdownNow(); | |
| 183 | + } | |
| 184 | + | |
| 185 | + cacheKeysForImageAwares.clear(); | |
| 186 | + uriLocks.clear(); | |
| 187 | + } | |
| 188 | + | |
| 189 | + void fireCallback(Runnable r) { | |
| 190 | + taskDistributor.execute(r); | |
| 191 | + } | |
| 192 | + | |
| 193 | + ReentrantLock getLockForUri(String uri) { | |
| 194 | + ReentrantLock lock = uriLocks.get(uri); | |
| 195 | + if (lock == null) { | |
| 196 | + lock = new ReentrantLock(); | |
| 197 | + uriLocks.put(uri, lock); | |
| 198 | + } | |
| 199 | + return lock; | |
| 200 | + } | |
| 201 | + | |
| 202 | + AtomicBoolean getPause() { | |
| 203 | + return paused; | |
| 204 | + } | |
| 205 | + | |
| 206 | + Object getPauseLock() { | |
| 207 | + return pauseLock; | |
| 208 | + } | |
| 209 | + | |
| 210 | + boolean isNetworkDenied() { | |
| 211 | + return networkDenied.get(); | |
| 212 | + } | |
| 213 | + | |
| 214 | + boolean isSlowNetwork() { | |
| 215 | + return slowNetwork.get(); | |
| 216 | + } | |
| 217 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core; | |
| 17 | + | |
| 18 | +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; | |
| 19 | +import com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener; | |
| 20 | +import com.nostra13.universalimageloader.core.assist.ImageSize; | |
| 21 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 22 | + | |
| 23 | +import java.util.concurrent.locks.ReentrantLock; | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * Information for load'n'display image task | |
| 27 | + * | |
| 28 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 29 | + * @see com.nostra13.universalimageloader.utils.MemoryCacheUtils | |
| 30 | + * @see DisplayImageOptions | |
| 31 | + * @see ImageLoadingListener | |
| 32 | + * @see ImageLoadingProgressListener | |
| 33 | + * @since 1.3.1 | |
| 34 | + */ | |
| 35 | +final class ImageLoadingInfo { | |
| 36 | + | |
| 37 | + final String uri; | |
| 38 | + final String memoryCacheKey; | |
| 39 | + final ImageAware imageAware; | |
| 40 | + final ImageSize targetSize; | |
| 41 | + final DisplayImageOptions options; | |
| 42 | + final ImageLoadingListener listener; | |
| 43 | + final ImageLoadingProgressListener progressListener; | |
| 44 | + final ReentrantLock loadFromUriLock; | |
| 45 | + | |
| 46 | + public ImageLoadingInfo(String uri, ImageAware imageAware, ImageSize targetSize, String memoryCacheKey, | |
| 47 | + DisplayImageOptions options, ImageLoadingListener listener, | |
| 48 | + ImageLoadingProgressListener progressListener, ReentrantLock loadFromUriLock) { | |
| 49 | + this.uri = uri; | |
| 50 | + this.imageAware = imageAware; | |
| 51 | + this.targetSize = targetSize; | |
| 52 | + this.options = options; | |
| 53 | + this.listener = listener; | |
| 54 | + this.progressListener = progressListener; | |
| 55 | + this.loadFromUriLock = loadFromUriLock; | |
| 56 | + this.memoryCacheKey = memoryCacheKey; | |
| 57 | + } | |
| 58 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.os.Handler; | |
| 20 | +import com.nostra13.universalimageloader.core.assist.FailReason; | |
| 21 | +import com.nostra13.universalimageloader.core.assist.FailReason.FailType; | |
| 22 | +import com.nostra13.universalimageloader.core.assist.ImageScaleType; | |
| 23 | +import com.nostra13.universalimageloader.core.assist.ImageSize; | |
| 24 | +import com.nostra13.universalimageloader.core.assist.LoadedFrom; | |
| 25 | +import com.nostra13.universalimageloader.core.assist.ViewScaleType; | |
| 26 | +import com.nostra13.universalimageloader.core.decode.ImageDecoder; | |
| 27 | +import com.nostra13.universalimageloader.core.decode.ImageDecodingInfo; | |
| 28 | +import com.nostra13.universalimageloader.core.download.ImageDownloader; | |
| 29 | +import com.nostra13.universalimageloader.core.download.ImageDownloader.Scheme; | |
| 30 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 31 | +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; | |
| 32 | +import com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener; | |
| 33 | +import com.nostra13.universalimageloader.utils.IoUtils; | |
| 34 | +import com.nostra13.universalimageloader.utils.L; | |
| 35 | + | |
| 36 | +import java.io.File; | |
| 37 | +import java.io.IOException; | |
| 38 | +import java.io.InputStream; | |
| 39 | +import java.util.concurrent.atomic.AtomicBoolean; | |
| 40 | +import java.util.concurrent.locks.ReentrantLock; | |
| 41 | + | |
| 42 | +/** | |
| 43 | + * Presents load'n'display image task. Used to load image from Internet or file system, decode it to {@link Bitmap}, and | |
| 44 | + * display it in {@link ImageAware} using {@link DisplayBitmapTask}. | |
| 45 | + * | |
| 46 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 47 | + * @see ImageLoaderConfiguration | |
| 48 | + * @see ImageLoadingInfo | |
| 49 | + * @since 1.3.1 | |
| 50 | + */ | |
| 51 | +final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener { | |
| 52 | + | |
| 53 | + private static final String LOG_WAITING_FOR_RESUME = "ImageLoader is paused. Waiting... [%s]"; | |
| 54 | + private static final String LOG_RESUME_AFTER_PAUSE = ".. Resume loading [%s]"; | |
| 55 | + private static final String LOG_DELAY_BEFORE_LOADING = "Delay %d ms before loading... [%s]"; | |
| 56 | + private static final String LOG_START_DISPLAY_IMAGE_TASK = "Start display image task [%s]"; | |
| 57 | + private static final String LOG_WAITING_FOR_IMAGE_LOADED = "Image already is loading. Waiting... [%s]"; | |
| 58 | + private static final String LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING = "...Get cached bitmap from memory after waiting. [%s]"; | |
| 59 | + private static final String LOG_LOAD_IMAGE_FROM_NETWORK = "Load image from network [%s]"; | |
| 60 | + private static final String LOG_LOAD_IMAGE_FROM_DISK_CACHE = "Load image from disk cache [%s]"; | |
| 61 | + private static final String LOG_RESIZE_CACHED_IMAGE_FILE = "Resize image in disk cache [%s]"; | |
| 62 | + private static final String LOG_PREPROCESS_IMAGE = "PreProcess image before caching in memory [%s]"; | |
| 63 | + private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]"; | |
| 64 | + private static final String LOG_CACHE_IMAGE_IN_MEMORY = "Cache image in memory [%s]"; | |
| 65 | + private static final String LOG_CACHE_IMAGE_ON_DISK = "Cache image on disk [%s]"; | |
| 66 | + private static final String LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK = "Process image before cache on disk [%s]"; | |
| 67 | + private static final String LOG_TASK_CANCELLED_IMAGEAWARE_REUSED = "ImageAware is reused for another image. Task is cancelled. [%s]"; | |
| 68 | + private static final String LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED = "ImageAware was collected by GC. Task is cancelled. [%s]"; | |
| 69 | + private static final String LOG_TASK_INTERRUPTED = "Task was interrupted [%s]"; | |
| 70 | + | |
| 71 | + private static final String ERROR_NO_IMAGE_STREAM = "No stream for image [%s]"; | |
| 72 | + private static final String ERROR_PRE_PROCESSOR_NULL = "Pre-processor returned null [%s]"; | |
| 73 | + private static final String ERROR_POST_PROCESSOR_NULL = "Post-processor returned null [%s]"; | |
| 74 | + private static final String ERROR_PROCESSOR_FOR_DISK_CACHE_NULL = "Bitmap processor for disk cache returned null [%s]"; | |
| 75 | + | |
| 76 | + private final ImageLoaderEngine engine; | |
| 77 | + private final ImageLoadingInfo imageLoadingInfo; | |
| 78 | + private final Handler handler; | |
| 79 | + | |
| 80 | + // Helper references | |
| 81 | + private final ImageLoaderConfiguration configuration; | |
| 82 | + private final ImageDownloader downloader; | |
| 83 | + private final ImageDownloader networkDeniedDownloader; | |
| 84 | + private final ImageDownloader slowNetworkDownloader; | |
| 85 | + private final ImageDecoder decoder; | |
| 86 | + final String uri; | |
| 87 | + private final String memoryCacheKey; | |
| 88 | + final ImageAware imageAware; | |
| 89 | + private final ImageSize targetSize; | |
| 90 | + final DisplayImageOptions options; | |
| 91 | + final ImageLoadingListener listener; | |
| 92 | + final ImageLoadingProgressListener progressListener; | |
| 93 | + private final boolean syncLoading; | |
| 94 | + | |
| 95 | + // State vars | |
| 96 | + private LoadedFrom loadedFrom = LoadedFrom.NETWORK; | |
| 97 | + | |
| 98 | + public LoadAndDisplayImageTask(ImageLoaderEngine engine, ImageLoadingInfo imageLoadingInfo, Handler handler) { | |
| 99 | + this.engine = engine; | |
| 100 | + this.imageLoadingInfo = imageLoadingInfo; | |
| 101 | + this.handler = handler; | |
| 102 | + | |
| 103 | + configuration = engine.configuration; | |
| 104 | + downloader = configuration.downloader; | |
| 105 | + networkDeniedDownloader = configuration.networkDeniedDownloader; | |
| 106 | + slowNetworkDownloader = configuration.slowNetworkDownloader; | |
| 107 | + decoder = configuration.decoder; | |
| 108 | + uri = imageLoadingInfo.uri; | |
| 109 | + memoryCacheKey = imageLoadingInfo.memoryCacheKey; | |
| 110 | + imageAware = imageLoadingInfo.imageAware; | |
| 111 | + targetSize = imageLoadingInfo.targetSize; | |
| 112 | + options = imageLoadingInfo.options; | |
| 113 | + listener = imageLoadingInfo.listener; | |
| 114 | + progressListener = imageLoadingInfo.progressListener; | |
| 115 | + syncLoading = options.isSyncLoading(); | |
| 116 | + } | |
| 117 | + | |
| 118 | + @Override | |
| 119 | + public void run() { | |
| 120 | + if (waitIfPaused()) return; | |
| 121 | + if (delayIfNeed()) return; | |
| 122 | + | |
| 123 | + ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock; | |
| 124 | + L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey); | |
| 125 | + if (loadFromUriLock.isLocked()) { | |
| 126 | + L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey); | |
| 127 | + } | |
| 128 | + | |
| 129 | + loadFromUriLock.lock(); | |
| 130 | + Bitmap bmp; | |
| 131 | + try { | |
| 132 | + checkTaskNotActual(); | |
| 133 | + | |
| 134 | + bmp = configuration.memoryCache.get(memoryCacheKey); | |
| 135 | + if (bmp == null || bmp.isRecycled()) { | |
| 136 | + bmp = tryLoadBitmap(); | |
| 137 | + if (bmp == null) return; // listener callback already was fired | |
| 138 | + | |
| 139 | + checkTaskNotActual(); | |
| 140 | + checkTaskInterrupted(); | |
| 141 | + | |
| 142 | + if (options.shouldPreProcess()) { | |
| 143 | + L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey); | |
| 144 | + bmp = options.getPreProcessor().process(bmp); | |
| 145 | + if (bmp == null) { | |
| 146 | + L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey); | |
| 147 | + } | |
| 148 | + } | |
| 149 | + | |
| 150 | + if (bmp != null && options.isCacheInMemory()) { | |
| 151 | + L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey); | |
| 152 | + configuration.memoryCache.put(memoryCacheKey, bmp); | |
| 153 | + } | |
| 154 | + } else { | |
| 155 | + loadedFrom = LoadedFrom.MEMORY_CACHE; | |
| 156 | + L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey); | |
| 157 | + } | |
| 158 | + | |
| 159 | + if (bmp != null && options.shouldPostProcess()) { | |
| 160 | + L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey); | |
| 161 | + bmp = options.getPostProcessor().process(bmp); | |
| 162 | + if (bmp == null) { | |
| 163 | + L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey); | |
| 164 | + } | |
| 165 | + } | |
| 166 | + checkTaskNotActual(); | |
| 167 | + checkTaskInterrupted(); | |
| 168 | + } catch (TaskCancelledException e) { | |
| 169 | + fireCancelEvent(); | |
| 170 | + return; | |
| 171 | + } finally { | |
| 172 | + loadFromUriLock.unlock(); | |
| 173 | + } | |
| 174 | + | |
| 175 | + DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom); | |
| 176 | + runTask(displayBitmapTask, syncLoading, handler, engine); | |
| 177 | + } | |
| 178 | + | |
| 179 | + /** @return <b>true</b> - if task should be interrupted; <b>false</b> - otherwise */ | |
| 180 | + private boolean waitIfPaused() { | |
| 181 | + AtomicBoolean pause = engine.getPause(); | |
| 182 | + if (pause.get()) { | |
| 183 | + synchronized (engine.getPauseLock()) { | |
| 184 | + if (pause.get()) { | |
| 185 | + L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey); | |
| 186 | + try { | |
| 187 | + engine.getPauseLock().wait(); | |
| 188 | + } catch (InterruptedException e) { | |
| 189 | + L.e(LOG_TASK_INTERRUPTED, memoryCacheKey); | |
| 190 | + return true; | |
| 191 | + } | |
| 192 | + L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey); | |
| 193 | + } | |
| 194 | + } | |
| 195 | + } | |
| 196 | + return isTaskNotActual(); | |
| 197 | + } | |
| 198 | + | |
| 199 | + /** @return <b>true</b> - if task should be interrupted; <b>false</b> - otherwise */ | |
| 200 | + private boolean delayIfNeed() { | |
| 201 | + if (options.shouldDelayBeforeLoading()) { | |
| 202 | + L.d(LOG_DELAY_BEFORE_LOADING, options.getDelayBeforeLoading(), memoryCacheKey); | |
| 203 | + try { | |
| 204 | + Thread.sleep(options.getDelayBeforeLoading()); | |
| 205 | + } catch (InterruptedException e) { | |
| 206 | + L.e(LOG_TASK_INTERRUPTED, memoryCacheKey); | |
| 207 | + return true; | |
| 208 | + } | |
| 209 | + return isTaskNotActual(); | |
| 210 | + } | |
| 211 | + return false; | |
| 212 | + } | |
| 213 | + | |
| 214 | + private Bitmap tryLoadBitmap() throws TaskCancelledException { | |
| 215 | + Bitmap bitmap = null; | |
| 216 | + try { | |
| 217 | + File imageFile = configuration.diskCache.get(uri); | |
| 218 | + if (imageFile != null && imageFile.exists() && imageFile.length() > 0) { | |
| 219 | + L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey); | |
| 220 | + loadedFrom = LoadedFrom.DISC_CACHE; | |
| 221 | + | |
| 222 | + checkTaskNotActual(); | |
| 223 | + bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath())); | |
| 224 | + } | |
| 225 | + if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { | |
| 226 | + L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey); | |
| 227 | + loadedFrom = LoadedFrom.NETWORK; | |
| 228 | + | |
| 229 | + String imageUriForDecoding = uri; | |
| 230 | + if (options.isCacheOnDisk() && tryCacheImageOnDisk()) { | |
| 231 | + imageFile = configuration.diskCache.get(uri); | |
| 232 | + if (imageFile != null) { | |
| 233 | + imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath()); | |
| 234 | + } | |
| 235 | + } | |
| 236 | + | |
| 237 | + checkTaskNotActual(); | |
| 238 | + bitmap = decodeImage(imageUriForDecoding); | |
| 239 | + | |
| 240 | + if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { | |
| 241 | + fireFailEvent(FailType.DECODING_ERROR, null); | |
| 242 | + } | |
| 243 | + } | |
| 244 | + } catch (IllegalStateException e) { | |
| 245 | + fireFailEvent(FailType.NETWORK_DENIED, null); | |
| 246 | + } catch (TaskCancelledException e) { | |
| 247 | + throw e; | |
| 248 | + } catch (IOException e) { | |
| 249 | + L.e(e); | |
| 250 | + fireFailEvent(FailType.IO_ERROR, e); | |
| 251 | + } catch (OutOfMemoryError e) { | |
| 252 | + L.e(e); | |
| 253 | + fireFailEvent(FailType.OUT_OF_MEMORY, e); | |
| 254 | + } catch (Throwable e) { | |
| 255 | + L.e(e); | |
| 256 | + fireFailEvent(FailType.UNKNOWN, e); | |
| 257 | + } | |
| 258 | + return bitmap; | |
| 259 | + } | |
| 260 | + | |
| 261 | + private Bitmap decodeImage(String imageUri) throws IOException { | |
| 262 | + ViewScaleType viewScaleType = imageAware.getScaleType(); | |
| 263 | + ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType, | |
| 264 | + getDownloader(), options); | |
| 265 | + return decoder.decode(decodingInfo); | |
| 266 | + } | |
| 267 | + | |
| 268 | + /** @return <b>true</b> - if image was downloaded successfully; <b>false</b> - otherwise */ | |
| 269 | + private boolean tryCacheImageOnDisk() throws TaskCancelledException { | |
| 270 | + L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey); | |
| 271 | + | |
| 272 | + boolean loaded; | |
| 273 | + try { | |
| 274 | + loaded = downloadImage(); | |
| 275 | + if (loaded) { | |
| 276 | + int width = configuration.maxImageWidthForDiskCache; | |
| 277 | + int height = configuration.maxImageHeightForDiskCache; | |
| 278 | + if (width > 0 || height > 0) { | |
| 279 | + L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey); | |
| 280 | + resizeAndSaveImage(width, height); // TODO : process boolean result | |
| 281 | + } | |
| 282 | + } | |
| 283 | + } catch (IOException e) { | |
| 284 | + L.e(e); | |
| 285 | + loaded = false; | |
| 286 | + } | |
| 287 | + return loaded; | |
| 288 | + } | |
| 289 | + | |
| 290 | + private boolean downloadImage() throws IOException { | |
| 291 | + InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader()); | |
| 292 | + if (is == null) { | |
| 293 | + L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey); | |
| 294 | + return false; | |
| 295 | + } else { | |
| 296 | + try { | |
| 297 | + return configuration.diskCache.save(uri, is, this); | |
| 298 | + } finally { | |
| 299 | + IoUtils.closeSilently(is); | |
| 300 | + } | |
| 301 | + } | |
| 302 | + } | |
| 303 | + | |
| 304 | + /** Decodes image file into Bitmap, resize it and save it back */ | |
| 305 | + private boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException { | |
| 306 | + // Decode image file, compress and re-save it | |
| 307 | + boolean saved = false; | |
| 308 | + File targetFile = configuration.diskCache.get(uri); | |
| 309 | + if (targetFile != null && targetFile.exists()) { | |
| 310 | + ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight); | |
| 311 | + DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options) | |
| 312 | + .imageScaleType(ImageScaleType.IN_SAMPLE_INT).build(); | |
| 313 | + ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, | |
| 314 | + Scheme.FILE.wrap(targetFile.getAbsolutePath()), uri, targetImageSize, ViewScaleType.FIT_INSIDE, | |
| 315 | + getDownloader(), specialOptions); | |
| 316 | + Bitmap bmp = decoder.decode(decodingInfo); | |
| 317 | + if (bmp != null && configuration.processorForDiskCache != null) { | |
| 318 | + L.d(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK, memoryCacheKey); | |
| 319 | + bmp = configuration.processorForDiskCache.process(bmp); | |
| 320 | + if (bmp == null) { | |
| 321 | + L.e(ERROR_PROCESSOR_FOR_DISK_CACHE_NULL, memoryCacheKey); | |
| 322 | + } | |
| 323 | + } | |
| 324 | + if (bmp != null) { | |
| 325 | + saved = configuration.diskCache.save(uri, bmp); | |
| 326 | + bmp.recycle(); | |
| 327 | + } | |
| 328 | + } | |
| 329 | + return saved; | |
| 330 | + } | |
| 331 | + | |
| 332 | + @Override | |
| 333 | + public boolean onBytesCopied(int current, int total) { | |
| 334 | + return syncLoading || fireProgressEvent(current, total); | |
| 335 | + } | |
| 336 | + | |
| 337 | + /** @return <b>true</b> - if loading should be continued; <b>false</b> - if loading should be interrupted */ | |
| 338 | + private boolean fireProgressEvent(final int current, final int total) { | |
| 339 | + if (isTaskInterrupted() || isTaskNotActual()) return false; | |
| 340 | + if (progressListener != null) { | |
| 341 | + Runnable r = new Runnable() { | |
| 342 | + @Override | |
| 343 | + public void run() { | |
| 344 | + progressListener.onProgressUpdate(uri, imageAware.getWrappedView(), current, total); | |
| 345 | + } | |
| 346 | + }; | |
| 347 | + runTask(r, false, handler, engine); | |
| 348 | + } | |
| 349 | + return true; | |
| 350 | + } | |
| 351 | + | |
| 352 | + private void fireFailEvent(final FailType failType, final Throwable failCause) { | |
| 353 | + if (syncLoading || isTaskInterrupted() || isTaskNotActual()) return; | |
| 354 | + Runnable r = new Runnable() { | |
| 355 | + @Override | |
| 356 | + public void run() { | |
| 357 | + if (options.shouldShowImageOnFail()) { | |
| 358 | + imageAware.setImageDrawable(options.getImageOnFail(configuration.resources)); | |
| 359 | + } | |
| 360 | + listener.onLoadingFailed(uri, imageAware.getWrappedView(), new FailReason(failType, failCause)); | |
| 361 | + } | |
| 362 | + }; | |
| 363 | + runTask(r, false, handler, engine); | |
| 364 | + } | |
| 365 | + | |
| 366 | + private void fireCancelEvent() { | |
| 367 | + if (syncLoading || isTaskInterrupted()) return; | |
| 368 | + Runnable r = new Runnable() { | |
| 369 | + @Override | |
| 370 | + public void run() { | |
| 371 | + listener.onLoadingCancelled(uri, imageAware.getWrappedView()); | |
| 372 | + } | |
| 373 | + }; | |
| 374 | + runTask(r, false, handler, engine); | |
| 375 | + } | |
| 376 | + | |
| 377 | + private ImageDownloader getDownloader() { | |
| 378 | + ImageDownloader d; | |
| 379 | + if (engine.isNetworkDenied()) { | |
| 380 | + d = networkDeniedDownloader; | |
| 381 | + } else if (engine.isSlowNetwork()) { | |
| 382 | + d = slowNetworkDownloader; | |
| 383 | + } else { | |
| 384 | + d = downloader; | |
| 385 | + } | |
| 386 | + return d; | |
| 387 | + } | |
| 388 | + | |
| 389 | + /** | |
| 390 | + * @throws TaskCancelledException if task is not actual (target ImageAware is collected by GC or the image URI of | |
| 391 | + * this task doesn't match to image URI which is actual for current ImageAware at | |
| 392 | + * this moment) | |
| 393 | + */ | |
| 394 | + private void checkTaskNotActual() throws TaskCancelledException { | |
| 395 | + checkViewCollected(); | |
| 396 | + checkViewReused(); | |
| 397 | + } | |
| 398 | + | |
| 399 | + /** | |
| 400 | + * @return <b>true</b> - if task is not actual (target ImageAware is collected by GC or the image URI of this task | |
| 401 | + * doesn't match to image URI which is actual for current ImageAware at this moment)); <b>false</b> - otherwise | |
| 402 | + */ | |
| 403 | + private boolean isTaskNotActual() { | |
| 404 | + return isViewCollected() || isViewReused(); | |
| 405 | + } | |
| 406 | + | |
| 407 | + /** @throws TaskCancelledException if target ImageAware is collected */ | |
| 408 | + private void checkViewCollected() throws TaskCancelledException { | |
| 409 | + if (isViewCollected()) { | |
| 410 | + throw new TaskCancelledException(); | |
| 411 | + } | |
| 412 | + } | |
| 413 | + | |
| 414 | + /** @return <b>true</b> - if target ImageAware is collected by GC; <b>false</b> - otherwise */ | |
| 415 | + private boolean isViewCollected() { | |
| 416 | + if (imageAware.isCollected()) { | |
| 417 | + L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey); | |
| 418 | + return true; | |
| 419 | + } | |
| 420 | + return false; | |
| 421 | + } | |
| 422 | + | |
| 423 | + /** @throws TaskCancelledException if target ImageAware is collected by GC */ | |
| 424 | + private void checkViewReused() throws TaskCancelledException { | |
| 425 | + if (isViewReused()) { | |
| 426 | + throw new TaskCancelledException(); | |
| 427 | + } | |
| 428 | + } | |
| 429 | + | |
| 430 | + /** @return <b>true</b> - if current ImageAware is reused for displaying another image; <b>false</b> - otherwise */ | |
| 431 | + private boolean isViewReused() { | |
| 432 | + String currentCacheKey = engine.getLoadingUriForView(imageAware); | |
| 433 | + // Check whether memory cache key (image URI) for current ImageAware is actual. | |
| 434 | + // If ImageAware is reused for another task then current task should be cancelled. | |
| 435 | + boolean imageAwareWasReused = !memoryCacheKey.equals(currentCacheKey); | |
| 436 | + if (imageAwareWasReused) { | |
| 437 | + L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey); | |
| 438 | + return true; | |
| 439 | + } | |
| 440 | + return false; | |
| 441 | + } | |
| 442 | + | |
| 443 | + /** @throws TaskCancelledException if current task was interrupted */ | |
| 444 | + private void checkTaskInterrupted() throws TaskCancelledException { | |
| 445 | + if (isTaskInterrupted()) { | |
| 446 | + throw new TaskCancelledException(); | |
| 447 | + } | |
| 448 | + } | |
| 449 | + | |
| 450 | + /** @return <b>true</b> - if current task was interrupted; <b>false</b> - otherwise */ | |
| 451 | + private boolean isTaskInterrupted() { | |
| 452 | + if (Thread.interrupted()) { | |
| 453 | + L.d(LOG_TASK_INTERRUPTED, memoryCacheKey); | |
| 454 | + return true; | |
| 455 | + } | |
| 456 | + return false; | |
| 457 | + } | |
| 458 | + | |
| 459 | + String getLoadingUri() { | |
| 460 | + return uri; | |
| 461 | + } | |
| 462 | + | |
| 463 | + static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) { | |
| 464 | + if (sync) { | |
| 465 | + r.run(); | |
| 466 | + } else if (handler == null) { | |
| 467 | + engine.fireCallback(r); | |
| 468 | + } else { | |
| 469 | + handler.post(r); | |
| 470 | + } | |
| 471 | + } | |
| 472 | + | |
| 473 | + /** | |
| 474 | + * Exceptions for case when task is cancelled (thread is interrupted, image view is reused for another task, view is | |
| 475 | + * collected by GC). | |
| 476 | + * | |
| 477 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 478 | + * @since 1.9.1 | |
| 479 | + */ | |
| 480 | + class TaskCancelledException extends Exception { | |
| 481 | + } | |
| 482 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.os.Handler; | |
| 20 | +import android.widget.ImageView; | |
| 21 | +import com.nostra13.universalimageloader.core.assist.LoadedFrom; | |
| 22 | +import com.nostra13.universalimageloader.core.process.BitmapProcessor; | |
| 23 | +import com.nostra13.universalimageloader.utils.L; | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * Presents process'n'display image task. Processes image {@linkplain Bitmap} and display it in {@link ImageView} using | |
| 27 | + * {@link DisplayBitmapTask}. | |
| 28 | + * | |
| 29 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 30 | + * @since 1.8.0 | |
| 31 | + */ | |
| 32 | +final class ProcessAndDisplayImageTask implements Runnable { | |
| 33 | + | |
| 34 | + private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]"; | |
| 35 | + | |
| 36 | + private final ImageLoaderEngine engine; | |
| 37 | + private final Bitmap bitmap; | |
| 38 | + private final ImageLoadingInfo imageLoadingInfo; | |
| 39 | + private final Handler handler; | |
| 40 | + | |
| 41 | + public ProcessAndDisplayImageTask(ImageLoaderEngine engine, Bitmap bitmap, ImageLoadingInfo imageLoadingInfo, | |
| 42 | + Handler handler) { | |
| 43 | + this.engine = engine; | |
| 44 | + this.bitmap = bitmap; | |
| 45 | + this.imageLoadingInfo = imageLoadingInfo; | |
| 46 | + this.handler = handler; | |
| 47 | + } | |
| 48 | + | |
| 49 | + @Override | |
| 50 | + public void run() { | |
| 51 | + L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey); | |
| 52 | + | |
| 53 | + BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor(); | |
| 54 | + Bitmap processedBitmap = processor.process(bitmap); | |
| 55 | + DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine, | |
| 56 | + LoadedFrom.MEMORY_CACHE); | |
| 57 | + LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine); | |
| 58 | + } | |
| 59 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/assist/ContentLengthInputStream.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2013-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.assist; | |
| 17 | + | |
| 18 | +import java.io.IOException; | |
| 19 | +import java.io.InputStream; | |
| 20 | + | |
| 21 | +/** | |
| 22 | + * Decorator for {@link InputStream InputStream}. Provides possibility to return defined stream length by | |
| 23 | + * {@link #available()} method. | |
| 24 | + * | |
| 25 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com), Mariotaku | |
| 26 | + * @since 1.9.1 | |
| 27 | + */ | |
| 28 | +public class ContentLengthInputStream extends InputStream { | |
| 29 | + | |
| 30 | + private final InputStream stream; | |
| 31 | + private final int length; | |
| 32 | + | |
| 33 | + public ContentLengthInputStream(InputStream stream, int length) { | |
| 34 | + this.stream = stream; | |
| 35 | + this.length = length; | |
| 36 | + } | |
| 37 | + | |
| 38 | + @Override | |
| 39 | + public int available() { | |
| 40 | + return length; | |
| 41 | + } | |
| 42 | + | |
| 43 | + @Override | |
| 44 | + public void close() throws IOException { | |
| 45 | + stream.close(); | |
| 46 | + } | |
| 47 | + | |
| 48 | + @Override | |
| 49 | + public void mark(int readLimit) { | |
| 50 | + stream.mark(readLimit); | |
| 51 | + } | |
| 52 | + | |
| 53 | + @Override | |
| 54 | + public int read() throws IOException { | |
| 55 | + return stream.read(); | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Override | |
| 59 | + public int read(byte[] buffer) throws IOException { | |
| 60 | + return stream.read(buffer); | |
| 61 | + } | |
| 62 | + | |
| 63 | + @Override | |
| 64 | + public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { | |
| 65 | + return stream.read(buffer, byteOffset, byteCount); | |
| 66 | + } | |
| 67 | + | |
| 68 | + @Override | |
| 69 | + public void reset() throws IOException { | |
| 70 | + stream.reset(); | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + public long skip(long byteCount) throws IOException { | |
| 75 | + return stream.skip(byteCount); | |
| 76 | + } | |
| 77 | + | |
| 78 | + @Override | |
| 79 | + public boolean markSupported() { | |
| 80 | + return stream.markSupported(); | |
| 81 | + } | |
| 82 | +} | |
| \ No newline at end of file | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.assist; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * Presents the reason why image loading and displaying was failed | |
| 20 | + * | |
| 21 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 22 | + * @since 1.0.0 | |
| 23 | + */ | |
| 24 | +public class FailReason { | |
| 25 | + | |
| 26 | + private final FailType type; | |
| 27 | + | |
| 28 | + private final Throwable cause; | |
| 29 | + | |
| 30 | + public FailReason(FailType type, Throwable cause) { | |
| 31 | + this.type = type; | |
| 32 | + this.cause = cause; | |
| 33 | + } | |
| 34 | + | |
| 35 | + /** @return {@linkplain FailType Fail type} */ | |
| 36 | + public FailType getType() { | |
| 37 | + return type; | |
| 38 | + } | |
| 39 | + | |
| 40 | + /** @return Thrown exception/error, can be <b>null</b> */ | |
| 41 | + public Throwable getCause() { | |
| 42 | + return cause; | |
| 43 | + } | |
| 44 | + | |
| 45 | + /** Presents type of fail while image loading */ | |
| 46 | + public static enum FailType { | |
| 47 | + /** Input/output error. Can be caused by network communication fail or error while caching image on file system. */ | |
| 48 | + IO_ERROR, | |
| 49 | + /** | |
| 50 | + * Error while | |
| 51 | + * {@linkplain android.graphics.BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options) | |
| 52 | + * decode image to Bitmap} | |
| 53 | + */ | |
| 54 | + DECODING_ERROR, | |
| 55 | + /** | |
| 56 | + * {@linkplain com.nostra13.universalimageloader.core.ImageLoader#denyNetworkDownloads(boolean) Network | |
| 57 | + * downloads are denied} and requested image wasn't cached in disk cache before. | |
| 58 | + */ | |
| 59 | + NETWORK_DENIED, | |
| 60 | + /** Not enough memory to create needed Bitmap for image */ | |
| 61 | + OUT_OF_MEMORY, | |
| 62 | + /** Unknown error was occurred while loading image */ | |
| 63 | + UNKNOWN | |
| 64 | + } | |
| 65 | +} | |
| \ No newline at end of file | ... | ... |
| 1 | +package com.nostra13.universalimageloader.core.assist; | |
| 2 | + | |
| 3 | +import java.io.FilterInputStream; | |
| 4 | +import java.io.IOException; | |
| 5 | +import java.io.InputStream; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * Many streams obtained over slow connection show <a href="http://code.google.com/p/android/issues/detail?id=6066">this | |
| 9 | + * problem</a>. | |
| 10 | + */ | |
| 11 | +public class FlushedInputStream extends FilterInputStream { | |
| 12 | + | |
| 13 | + public FlushedInputStream(InputStream inputStream) { | |
| 14 | + super(inputStream); | |
| 15 | + } | |
| 16 | + | |
| 17 | + @Override | |
| 18 | + public long skip(long n) throws IOException { | |
| 19 | + long totalBytesSkipped = 0L; | |
| 20 | + while (totalBytesSkipped < n) { | |
| 21 | + long bytesSkipped = in.skip(n - totalBytesSkipped); | |
| 22 | + if (bytesSkipped == 0L) { | |
| 23 | + int by_te = read(); | |
| 24 | + if (by_te < 0) { | |
| 25 | + break; // we reached EOF | |
| 26 | + } else { | |
| 27 | + bytesSkipped = 1; // we read one byte | |
| 28 | + } | |
| 29 | + } | |
| 30 | + totalBytesSkipped += bytesSkipped; | |
| 31 | + } | |
| 32 | + return totalBytesSkipped; | |
| 33 | + } | |
| 34 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.assist; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * Type of image scaling during decoding. | |
| 20 | + * | |
| 21 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 22 | + * @since 1.5.0 | |
| 23 | + */ | |
| 24 | +public enum ImageScaleType { | |
| 25 | + /** Image won't be scaled */ | |
| 26 | + NONE, | |
| 27 | + /** | |
| 28 | + * Image will be scaled down only if image size is greater than | |
| 29 | + * {@linkplain javax.microedition.khronos.opengles.GL10#GL_MAX_TEXTURE_SIZE maximum acceptable texture size}. | |
| 30 | + * Usually it's 2048x2048.<br /> | |
| 31 | + * If Bitmap is expected to display than it must not exceed this size (otherwise you'll get the exception | |
| 32 | + * "OpenGLRenderer: Bitmap too large to be uploaded into a texture".<br /> | |
| 33 | + * Image will be subsampled in an integer number of times (1, 2, 3, ...) to maximum texture size of device. | |
| 34 | + */ | |
| 35 | + NONE_SAFE, | |
| 36 | + /** | |
| 37 | + * Image will be reduces 2-fold until next reduce step make image smaller target size.<br /> | |
| 38 | + * It's <b>fast</b> type and it's preferable for usage in lists/grids/galleries (and other | |
| 39 | + * {@linkplain android.widget.AdapterView adapter-views}) .<br /> | |
| 40 | + * Relates to {@link android.graphics.BitmapFactory.Options#inSampleSize}<br /> | |
| 41 | + * Note: If original image size is smaller than target size then original image <b>won't</b> be scaled. | |
| 42 | + */ | |
| 43 | + IN_SAMPLE_POWER_OF_2, | |
| 44 | + /** | |
| 45 | + * Image will be subsampled in an integer number of times (1, 2, 3, ...). Use it if memory economy is quite | |
| 46 | + * important.<br /> | |
| 47 | + * Relates to {@link android.graphics.BitmapFactory.Options#inSampleSize}<br /> | |
| 48 | + * Note: If original image size is smaller than target size then original image <b>won't</b> be scaled. | |
| 49 | + */ | |
| 50 | + IN_SAMPLE_INT, | |
| 51 | + /** | |
| 52 | + * Image will scaled-down exactly to target size (scaled width or height or both will be equal to target size; | |
| 53 | + * depends on {@linkplain android.widget.ImageView.ScaleType ImageView's scale type}). Use it if memory economy is | |
| 54 | + * critically important.<br /> | |
| 55 | + * <b>Note:</b> If original image size is smaller than target size then original image <b>won't</b> be scaled.<br /> | |
| 56 | + * <br /> | |
| 57 | + * <b>NOTE:</b> For creating result Bitmap (of exact size) additional Bitmap will be created with | |
| 58 | + * {@link android.graphics.Bitmap#createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean) | |
| 59 | + * Bitmap.createBitmap(...)}.<br /> | |
| 60 | + * <b>Cons:</b> Saves memory by keeping smaller Bitmap in memory cache (comparing with IN_SAMPLE... scale types)<br /> | |
| 61 | + * <b>Pros:</b> Requires more memory in one time for creation of result Bitmap. | |
| 62 | + */ | |
| 63 | + EXACTLY, | |
| 64 | + /** | |
| 65 | + * Image will scaled exactly to target size (scaled width or height or both will be equal to target size; depends on | |
| 66 | + * {@linkplain android.widget.ImageView.ScaleType ImageView's scale type}). Use it if memory economy is critically | |
| 67 | + * important.<br /> | |
| 68 | + * <b>Note:</b> If original image size is smaller than target size then original image <b>will be stretched</b> to | |
| 69 | + * target size.<br /> | |
| 70 | + * <br /> | |
| 71 | + * <b>NOTE:</b> For creating result Bitmap (of exact size) additional Bitmap will be created with | |
| 72 | + * {@link android.graphics.Bitmap#createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean) | |
| 73 | + * Bitmap.createBitmap(...)}.<br /> | |
| 74 | + * <b>Cons:</b> Saves memory by keeping smaller Bitmap in memory cache (comparing with IN_SAMPLE... scale types)<br /> | |
| 75 | + * <b>Pros:</b> Requires more memory in one time for creation of result Bitmap. | |
| 76 | + */ | |
| 77 | + EXACTLY_STRETCHED | |
| 78 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.assist; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * Present width and height values | |
| 20 | + * | |
| 21 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 22 | + * @since 1.0.0 | |
| 23 | + */ | |
| 24 | +public class ImageSize { | |
| 25 | + | |
| 26 | + private static final int TO_STRING_MAX_LENGHT = 9; // "9999x9999".length() | |
| 27 | + private static final String SEPARATOR = "x"; | |
| 28 | + | |
| 29 | + private final int width; | |
| 30 | + private final int height; | |
| 31 | + | |
| 32 | + public ImageSize(int width, int height) { | |
| 33 | + this.width = width; | |
| 34 | + this.height = height; | |
| 35 | + } | |
| 36 | + | |
| 37 | + public ImageSize(int width, int height, int rotation) { | |
| 38 | + if (rotation % 180 == 0) { | |
| 39 | + this.width = width; | |
| 40 | + this.height = height; | |
| 41 | + } else { | |
| 42 | + this.width = height; | |
| 43 | + this.height = width; | |
| 44 | + } | |
| 45 | + } | |
| 46 | + | |
| 47 | + public int getWidth() { | |
| 48 | + return width; | |
| 49 | + } | |
| 50 | + | |
| 51 | + public int getHeight() { | |
| 52 | + return height; | |
| 53 | + } | |
| 54 | + | |
| 55 | + /** Scales down dimensions in <b>sampleSize</b> times. Returns new object. */ | |
| 56 | + public ImageSize scaleDown(int sampleSize) { | |
| 57 | + return new ImageSize(width / sampleSize, height / sampleSize); | |
| 58 | + } | |
| 59 | + | |
| 60 | + /** Scales dimensions according to incoming scale. Returns new object. */ | |
| 61 | + public ImageSize scale(float scale) { | |
| 62 | + return new ImageSize((int) (width * scale), (int) (height * scale)); | |
| 63 | + } | |
| 64 | + | |
| 65 | + @Override | |
| 66 | + public String toString() { | |
| 67 | + return new StringBuilder(TO_STRING_MAX_LENGHT).append(width).append(SEPARATOR).append(height).toString(); | |
| 68 | + } | |
| 69 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.assist; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * Queue processing type which will be used for display task processing | |
| 20 | + * | |
| 21 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 22 | + * @since 1.6.3 | |
| 23 | + */ | |
| 24 | +public enum QueueProcessingType { | |
| 25 | + FIFO, LIFO | |
| 26 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.assist; | |
| 17 | + | |
| 18 | +import android.widget.ImageView; | |
| 19 | +import android.widget.ImageView.ScaleType; | |
| 20 | + | |
| 21 | +/** | |
| 22 | + * Simplify {@linkplain ScaleType ImageView's scale type} to 2 types: {@link #FIT_INSIDE} and {@link #CROP} | |
| 23 | + * | |
| 24 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 25 | + * @since 1.6.1 | |
| 26 | + */ | |
| 27 | +public enum ViewScaleType { | |
| 28 | + /** | |
| 29 | + * Scale the image uniformly (maintain the image's aspect ratio) so that at least one dimension (width or height) of | |
| 30 | + * the image will be equal to or less the corresponding dimension of the view. | |
| 31 | + */ | |
| 32 | + FIT_INSIDE, | |
| 33 | + /** | |
| 34 | + * Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the | |
| 35 | + * image will be equal to or larger than the corresponding dimension of the view. | |
| 36 | + */ | |
| 37 | + CROP; | |
| 38 | + | |
| 39 | + /** | |
| 40 | + * Defines scale type of ImageView. | |
| 41 | + * | |
| 42 | + * @param imageView {@link ImageView} | |
| 43 | + * @return {@link #FIT_INSIDE} for | |
| 44 | + * <ul> | |
| 45 | + * <li>{@link ScaleType#FIT_CENTER}</li> | |
| 46 | + * <li>{@link ScaleType#FIT_XY}</li> | |
| 47 | + * <li>{@link ScaleType#FIT_START}</li> | |
| 48 | + * <li>{@link ScaleType#FIT_END}</li> | |
| 49 | + * <li>{@link ScaleType#CENTER_INSIDE}</li> | |
| 50 | + * </ul> | |
| 51 | + * {@link #CROP} for | |
| 52 | + * <ul> | |
| 53 | + * <li>{@link ScaleType#CENTER}</li> | |
| 54 | + * <li>{@link ScaleType#CENTER_CROP}</li> | |
| 55 | + * <li>{@link ScaleType#MATRIX}</li> | |
| 56 | + * </ul> | |
| 57 | + */ | |
| 58 | + public static ViewScaleType fromImageView(ImageView imageView) { | |
| 59 | + switch (imageView.getScaleType()) { | |
| 60 | + case FIT_CENTER: | |
| 61 | + case FIT_XY: | |
| 62 | + case FIT_START: | |
| 63 | + case FIT_END: | |
| 64 | + case CENTER_INSIDE: | |
| 65 | + return FIT_INSIDE; | |
| 66 | + case MATRIX: | |
| 67 | + case CENTER: | |
| 68 | + case CENTER_CROP: | |
| 69 | + default: | |
| 70 | + return CROP; | |
| 71 | + } | |
| 72 | + } | |
| 73 | +} | ... | ... |
| 1 | +/* | |
| 2 | + * Written by Doug Lea with assistance from members of JCP JSR-166 | |
| 3 | + * Expert Group and released to the public domain, as explained at | |
| 4 | + * http://creativecommons.org/licenses/publicdomain | |
| 5 | + */ | |
| 6 | + | |
| 7 | +package com.nostra13.universalimageloader.core.assist.deque; | |
| 8 | +import java.util.Iterator; | |
| 9 | +import java.util.NoSuchElementException; | |
| 10 | +import java.util.concurrent.BlockingQueue; | |
| 11 | +import java.util.concurrent.TimeUnit; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * A {@link Deque} that additionally supports blocking operations that wait | |
| 15 | + * for the deque to become non-empty when retrieving an element, and wait for | |
| 16 | + * space to become available in the deque when storing an element. | |
| 17 | + * | |
| 18 | + * <p><tt>BlockingDeque</tt> methods come in four forms, with different ways | |
| 19 | + * of handling operations that cannot be satisfied immediately, but may be | |
| 20 | + * satisfied at some point in the future: | |
| 21 | + * one throws an exception, the second returns a special value (either | |
| 22 | + * <tt>null</tt> or <tt>false</tt>, depending on the operation), the third | |
| 23 | + * blocks the current thread indefinitely until the operation can succeed, | |
| 24 | + * and the fourth blocks for only a given maximum time limit before giving | |
| 25 | + * up. These methods are summarized in the following table: | |
| 26 | + * | |
| 27 | + * <p> | |
| 28 | + * <table BORDER CELLPADDING=3 CELLSPACING=1> | |
| 29 | + * <tr> | |
| 30 | + * <td ALIGN=CENTER COLSPAN = 5> <b>First Element (Head)</b></td> | |
| 31 | + * </tr> | |
| 32 | + * <tr> | |
| 33 | + * <td></td> | |
| 34 | + * <td ALIGN=CENTER><em>Throws exception</em></td> | |
| 35 | + * <td ALIGN=CENTER><em>Special value</em></td> | |
| 36 | + * <td ALIGN=CENTER><em>Blocks</em></td> | |
| 37 | + * <td ALIGN=CENTER><em>Times out</em></td> | |
| 38 | + * </tr> | |
| 39 | + * <tr> | |
| 40 | + * <td><b>Insert</b></td> | |
| 41 | + * <td>{@link #addFirst addFirst(e)}</td> | |
| 42 | + * <td>{@link #offerFirst offerFirst(e)}</td> | |
| 43 | + * <td>{@link #putFirst putFirst(e)}</td> | |
| 44 | + * <td>{@link #offerFirst offerFirst(e, time, unit)}</td> | |
| 45 | + * </tr> | |
| 46 | + * <tr> | |
| 47 | + * <td><b>Remove</b></td> | |
| 48 | + * <td>{@link #removeFirst removeFirst()}</td> | |
| 49 | + * <td>{@link #pollFirst pollFirst()}</td> | |
| 50 | + * <td>{@link #takeFirst takeFirst()}</td> | |
| 51 | + * <td>{@link #pollFirst(long, TimeUnit) pollFirst(time, unit)}</td> | |
| 52 | + * </tr> | |
| 53 | + * <tr> | |
| 54 | + * <td><b>Examine</b></td> | |
| 55 | + * <td>{@link #getFirst getFirst()}</td> | |
| 56 | + * <td>{@link #peekFirst peekFirst()}</td> | |
| 57 | + * <td><em>not applicable</em></td> | |
| 58 | + * <td><em>not applicable</em></td> | |
| 59 | + * </tr> | |
| 60 | + * <tr> | |
| 61 | + * <td ALIGN=CENTER COLSPAN = 5> <b>Last Element (Tail)</b></td> | |
| 62 | + * </tr> | |
| 63 | + * <tr> | |
| 64 | + * <td></td> | |
| 65 | + * <td ALIGN=CENTER><em>Throws exception</em></td> | |
| 66 | + * <td ALIGN=CENTER><em>Special value</em></td> | |
| 67 | + * <td ALIGN=CENTER><em>Blocks</em></td> | |
| 68 | + * <td ALIGN=CENTER><em>Times out</em></td> | |
| 69 | + * </tr> | |
| 70 | + * <tr> | |
| 71 | + * <td><b>Insert</b></td> | |
| 72 | + * <td>{@link #addLast addLast(e)}</td> | |
| 73 | + * <td>{@link #offerLast offerLast(e)}</td> | |
| 74 | + * <td>{@link #putLast putLast(e)}</td> | |
| 75 | + * <td>{@link #offerLast offerLast(e, time, unit)}</td> | |
| 76 | + * </tr> | |
| 77 | + * <tr> | |
| 78 | + * <td><b>Remove</b></td> | |
| 79 | + * <td>{@link #removeLast() removeLast()}</td> | |
| 80 | + * <td>{@link #pollLast() pollLast()}</td> | |
| 81 | + * <td>{@link #takeLast takeLast()}</td> | |
| 82 | + * <td>{@link #pollLast(long, TimeUnit) pollLast(time, unit)}</td> | |
| 83 | + * </tr> | |
| 84 | + * <tr> | |
| 85 | + * <td><b>Examine</b></td> | |
| 86 | + * <td>{@link #getLast getLast()}</td> | |
| 87 | + * <td>{@link #peekLast peekLast()}</td> | |
| 88 | + * <td><em>not applicable</em></td> | |
| 89 | + * <td><em>not applicable</em></td> | |
| 90 | + * </tr> | |
| 91 | + * </table> | |
| 92 | + * | |
| 93 | + * <p>Like any {@link BlockingQueue}, a <tt>BlockingDeque</tt> is thread safe, | |
| 94 | + * does not permit null elements, and may (or may not) be | |
| 95 | + * capacity-constrained. | |
| 96 | + * | |
| 97 | + * <p>A <tt>BlockingDeque</tt> implementation may be used directly as a FIFO | |
| 98 | + * <tt>BlockingQueue</tt>. The methods inherited from the | |
| 99 | + * <tt>BlockingQueue</tt> interface are precisely equivalent to | |
| 100 | + * <tt>BlockingDeque</tt> methods as indicated in the following table: | |
| 101 | + * | |
| 102 | + * <p> | |
| 103 | + * <table BORDER CELLPADDING=3 CELLSPACING=1> | |
| 104 | + * <tr> | |
| 105 | + * <td ALIGN=CENTER> <b><tt>BlockingQueue</tt> Method</b></td> | |
| 106 | + * <td ALIGN=CENTER> <b>Equivalent <tt>BlockingDeque</tt> Method</b></td> | |
| 107 | + * </tr> | |
| 108 | + * <tr> | |
| 109 | + * <td ALIGN=CENTER COLSPAN = 2> <b>Insert</b></td> | |
| 110 | + * </tr> | |
| 111 | + * <tr> | |
| 112 | + * <td>{@link #add add(e)}</td> | |
| 113 | + * <td>{@link #addLast addLast(e)}</td> | |
| 114 | + * </tr> | |
| 115 | + * <tr> | |
| 116 | + * <td>{@link #offer offer(e)}</td> | |
| 117 | + * <td>{@link #offerLast offerLast(e)}</td> | |
| 118 | + * </tr> | |
| 119 | + * <tr> | |
| 120 | + * <td>{@link #put put(e)}</td> | |
| 121 | + * <td>{@link #putLast putLast(e)}</td> | |
| 122 | + * </tr> | |
| 123 | + * <tr> | |
| 124 | + * <td>{@link #offer offer(e, time, unit)}</td> | |
| 125 | + * <td>{@link #offerLast offerLast(e, time, unit)}</td> | |
| 126 | + * </tr> | |
| 127 | + * <tr> | |
| 128 | + * <td ALIGN=CENTER COLSPAN = 2> <b>Remove</b></td> | |
| 129 | + * </tr> | |
| 130 | + * <tr> | |
| 131 | + * <td>{@link #remove() remove()}</td> | |
| 132 | + * <td>{@link #removeFirst() removeFirst()}</td> | |
| 133 | + * </tr> | |
| 134 | + * <tr> | |
| 135 | + * <td>{@link #poll() poll()}</td> | |
| 136 | + * <td>{@link #pollFirst() pollFirst()}</td> | |
| 137 | + * </tr> | |
| 138 | + * <tr> | |
| 139 | + * <td>{@link #take() take()}</td> | |
| 140 | + * <td>{@link #takeFirst() takeFirst()}</td> | |
| 141 | + * </tr> | |
| 142 | + * <tr> | |
| 143 | + * <td>{@link #poll(long, TimeUnit) poll(time, unit)}</td> | |
| 144 | + * <td>{@link #pollFirst(long, TimeUnit) pollFirst(time, unit)}</td> | |
| 145 | + * </tr> | |
| 146 | + * <tr> | |
| 147 | + * <td ALIGN=CENTER COLSPAN = 2> <b>Examine</b></td> | |
| 148 | + * </tr> | |
| 149 | + * <tr> | |
| 150 | + * <td>{@link #element() element()}</td> | |
| 151 | + * <td>{@link #getFirst() getFirst()}</td> | |
| 152 | + * </tr> | |
| 153 | + * <tr> | |
| 154 | + * <td>{@link #peek() peek()}</td> | |
| 155 | + * <td>{@link #peekFirst() peekFirst()}</td> | |
| 156 | + * </tr> | |
| 157 | + * </table> | |
| 158 | + * | |
| 159 | + * <p>Memory consistency effects: As with other concurrent | |
| 160 | + * collections, actions in a thread prior to placing an object into a | |
| 161 | + * {@code BlockingDeque} | |
| 162 | + * <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a> | |
| 163 | + * actions subsequent to the access or removal of that element from | |
| 164 | + * the {@code BlockingDeque} in another thread. | |
| 165 | + * | |
| 166 | + * <p>This interface is a member of the | |
| 167 | + * <a href="{@docRoot}/../technotes/guides/collections/index.html"> | |
| 168 | + * Java Collections Framework</a>. | |
| 169 | + * | |
| 170 | + * @since 1.6 | |
| 171 | + * @author Doug Lea | |
| 172 | + * @param <E> the type of elements held in this collection | |
| 173 | + */ | |
| 174 | +public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> { | |
| 175 | + /* | |
| 176 | + * We have "diamond" multiple interface inheritance here, and that | |
| 177 | + * introduces ambiguities. Methods might end up with different | |
| 178 | + * specs depending on the branch chosen by javadoc. Thus a lot of | |
| 179 | + * methods specs here are copied from superinterfaces. | |
| 180 | + */ | |
| 181 | + | |
| 182 | + /** | |
| 183 | + * Inserts the specified element at the front of this deque if it is | |
| 184 | + * possible to do so immediately without violating capacity restrictions, | |
| 185 | + * throwing an <tt>IllegalStateException</tt> if no space is currently | |
| 186 | + * available. When using a capacity-restricted deque, it is generally | |
| 187 | + * preferable to use {@link #offerFirst offerFirst}. | |
| 188 | + * | |
| 189 | + * @param e the element to add | |
| 190 | + * @throws IllegalStateException {@inheritDoc} | |
| 191 | + * @throws ClassCastException {@inheritDoc} | |
| 192 | + * @throws NullPointerException if the specified element is null | |
| 193 | + * @throws IllegalArgumentException {@inheritDoc} | |
| 194 | + */ | |
| 195 | + void addFirst(E e); | |
| 196 | + | |
| 197 | + /** | |
| 198 | + * Inserts the specified element at the end of this deque if it is | |
| 199 | + * possible to do so immediately without violating capacity restrictions, | |
| 200 | + * throwing an <tt>IllegalStateException</tt> if no space is currently | |
| 201 | + * available. When using a capacity-restricted deque, it is generally | |
| 202 | + * preferable to use {@link #offerLast offerLast}. | |
| 203 | + * | |
| 204 | + * @param e the element to add | |
| 205 | + * @throws IllegalStateException {@inheritDoc} | |
| 206 | + * @throws ClassCastException {@inheritDoc} | |
| 207 | + * @throws NullPointerException if the specified element is null | |
| 208 | + * @throws IllegalArgumentException {@inheritDoc} | |
| 209 | + */ | |
| 210 | + void addLast(E e); | |
| 211 | + | |
| 212 | + /** | |
| 213 | + * Inserts the specified element at the front of this deque if it is | |
| 214 | + * possible to do so immediately without violating capacity restrictions, | |
| 215 | + * returning <tt>true</tt> upon success and <tt>false</tt> if no space is | |
| 216 | + * currently available. | |
| 217 | + * When using a capacity-restricted deque, this method is generally | |
| 218 | + * preferable to the {@link #addFirst addFirst} method, which can | |
| 219 | + * fail to insert an element only by throwing an exception. | |
| 220 | + * | |
| 221 | + * @param e the element to add | |
| 222 | + * @throws ClassCastException {@inheritDoc} | |
| 223 | + * @throws NullPointerException if the specified element is null | |
| 224 | + * @throws IllegalArgumentException {@inheritDoc} | |
| 225 | + */ | |
| 226 | + boolean offerFirst(E e); | |
| 227 | + | |
| 228 | + /** | |
| 229 | + * Inserts the specified element at the end of this deque if it is | |
| 230 | + * possible to do so immediately without violating capacity restrictions, | |
| 231 | + * returning <tt>true</tt> upon success and <tt>false</tt> if no space is | |
| 232 | + * currently available. | |
| 233 | + * When using a capacity-restricted deque, this method is generally | |
| 234 | + * preferable to the {@link #addLast addLast} method, which can | |
| 235 | + * fail to insert an element only by throwing an exception. | |
| 236 | + * | |
| 237 | + * @param e the element to add | |
| 238 | + * @throws ClassCastException {@inheritDoc} | |
| 239 | + * @throws NullPointerException if the specified element is null | |
| 240 | + * @throws IllegalArgumentException {@inheritDoc} | |
| 241 | + */ | |
| 242 | + boolean offerLast(E e); | |
| 243 | + | |
| 244 | + /** | |
| 245 | + * Inserts the specified element at the front of this deque, | |
| 246 | + * waiting if necessary for space to become available. | |
| 247 | + * | |
| 248 | + * @param e the element to add | |
| 249 | + * @throws InterruptedException if interrupted while waiting | |
| 250 | + * @throws ClassCastException if the class of the specified element | |
| 251 | + * prevents it from being added to this deque | |
| 252 | + * @throws NullPointerException if the specified element is null | |
| 253 | + * @throws IllegalArgumentException if some property of the specified | |
| 254 | + * element prevents it from being added to this deque | |
| 255 | + */ | |
| 256 | + void putFirst(E e) throws InterruptedException; | |
| 257 | + | |
| 258 | + /** | |
| 259 | + * Inserts the specified element at the end of this deque, | |
| 260 | + * waiting if necessary for space to become available. | |
| 261 | + * | |
| 262 | + * @param e the element to add | |
| 263 | + * @throws InterruptedException if interrupted while waiting | |
| 264 | + * @throws ClassCastException if the class of the specified element | |
| 265 | + * prevents it from being added to this deque | |
| 266 | + * @throws NullPointerException if the specified element is null | |
| 267 | + * @throws IllegalArgumentException if some property of the specified | |
| 268 | + * element prevents it from being added to this deque | |
| 269 | + */ | |
| 270 | + void putLast(E e) throws InterruptedException; | |
| 271 | + | |
| 272 | + /** | |
| 273 | + * Inserts the specified element at the front of this deque, | |
| 274 | + * waiting up to the specified wait time if necessary for space to | |
| 275 | + * become available. | |
| 276 | + * | |
| 277 | + * @param e the element to add | |
| 278 | + * @param timeout how long to wait before giving up, in units of | |
| 279 | + * <tt>unit</tt> | |
| 280 | + * @param unit a <tt>TimeUnit</tt> determining how to interpret the | |
| 281 | + * <tt>timeout</tt> parameter | |
| 282 | + * @return <tt>true</tt> if successful, or <tt>false</tt> if | |
| 283 | + * the specified waiting time elapses before space is available | |
| 284 | + * @throws InterruptedException if interrupted while waiting | |
| 285 | + * @throws ClassCastException if the class of the specified element | |
| 286 | + * prevents it from being added to this deque | |
| 287 | + * @throws NullPointerException if the specified element is null | |
| 288 | + * @throws IllegalArgumentException if some property of the specified | |
| 289 | + * element prevents it from being added to this deque | |
| 290 | + */ | |
| 291 | + boolean offerFirst(E e, long timeout, TimeUnit unit) | |
| 292 | + throws InterruptedException; | |
| 293 | + | |
| 294 | + /** | |
| 295 | + * Inserts the specified element at the end of this deque, | |
| 296 | + * waiting up to the specified wait time if necessary for space to | |
| 297 | + * become available. | |
| 298 | + * | |
| 299 | + * @param e the element to add | |
| 300 | + * @param timeout how long to wait before giving up, in units of | |
| 301 | + * <tt>unit</tt> | |
| 302 | + * @param unit a <tt>TimeUnit</tt> determining how to interpret the | |
| 303 | + * <tt>timeout</tt> parameter | |
| 304 | + * @return <tt>true</tt> if successful, or <tt>false</tt> if | |
| 305 | + * the specified waiting time elapses before space is available | |
| 306 | + * @throws InterruptedException if interrupted while waiting | |
| 307 | + * @throws ClassCastException if the class of the specified element | |
| 308 | + * prevents it from being added to this deque | |
| 309 | + * @throws NullPointerException if the specified element is null | |
| 310 | + * @throws IllegalArgumentException if some property of the specified | |
| 311 | + * element prevents it from being added to this deque | |
| 312 | + */ | |
| 313 | + boolean offerLast(E e, long timeout, TimeUnit unit) | |
| 314 | + throws InterruptedException; | |
| 315 | + | |
| 316 | + /** | |
| 317 | + * Retrieves and removes the first element of this deque, waiting | |
| 318 | + * if necessary until an element becomes available. | |
| 319 | + * | |
| 320 | + * @return the head of this deque | |
| 321 | + * @throws InterruptedException if interrupted while waiting | |
| 322 | + */ | |
| 323 | + E takeFirst() throws InterruptedException; | |
| 324 | + | |
| 325 | + /** | |
| 326 | + * Retrieves and removes the last element of this deque, waiting | |
| 327 | + * if necessary until an element becomes available. | |
| 328 | + * | |
| 329 | + * @return the tail of this deque | |
| 330 | + * @throws InterruptedException if interrupted while waiting | |
| 331 | + */ | |
| 332 | + E takeLast() throws InterruptedException; | |
| 333 | + | |
| 334 | + /** | |
| 335 | + * Retrieves and removes the first element of this deque, waiting | |
| 336 | + * up to the specified wait time if necessary for an element to | |
| 337 | + * become available. | |
| 338 | + * | |
| 339 | + * @param timeout how long to wait before giving up, in units of | |
| 340 | + * <tt>unit</tt> | |
| 341 | + * @param unit a <tt>TimeUnit</tt> determining how to interpret the | |
| 342 | + * <tt>timeout</tt> parameter | |
| 343 | + * @return the head of this deque, or <tt>null</tt> if the specified | |
| 344 | + * waiting time elapses before an element is available | |
| 345 | + * @throws InterruptedException if interrupted while waiting | |
| 346 | + */ | |
| 347 | + E pollFirst(long timeout, TimeUnit unit) | |
| 348 | + throws InterruptedException; | |
| 349 | + | |
| 350 | + /** | |
| 351 | + * Retrieves and removes the last element of this deque, waiting | |
| 352 | + * up to the specified wait time if necessary for an element to | |
| 353 | + * become available. | |
| 354 | + * | |
| 355 | + * @param timeout how long to wait before giving up, in units of | |
| 356 | + * <tt>unit</tt> | |
| 357 | + * @param unit a <tt>TimeUnit</tt> determining how to interpret the | |
| 358 | + * <tt>timeout</tt> parameter | |
| 359 | + * @return the tail of this deque, or <tt>null</tt> if the specified | |
| 360 | + * waiting time elapses before an element is available | |
| 361 | + * @throws InterruptedException if interrupted while waiting | |
| 362 | + */ | |
| 363 | + E pollLast(long timeout, TimeUnit unit) | |
| 364 | + throws InterruptedException; | |
| 365 | + | |
| 366 | + /** | |
| 367 | + * Removes the first occurrence of the specified element from this deque. | |
| 368 | + * If the deque does not contain the element, it is unchanged. | |
| 369 | + * More formally, removes the first element <tt>e</tt> such that | |
| 370 | + * <tt>o.equals(e)</tt> (if such an element exists). | |
| 371 | + * Returns <tt>true</tt> if this deque contained the specified element | |
| 372 | + * (or equivalently, if this deque changed as a result of the call). | |
| 373 | + * | |
| 374 | + * @param o element to be removed from this deque, if present | |
| 375 | + * @return <tt>true</tt> if an element was removed as a result of this call | |
| 376 | + * @throws ClassCastException if the class of the specified element | |
| 377 | + * is incompatible with this deque (optional) | |
| 378 | + * @throws NullPointerException if the specified element is null (optional) | |
| 379 | + */ | |
| 380 | + boolean removeFirstOccurrence(Object o); | |
| 381 | + | |
| 382 | + /** | |
| 383 | + * Removes the last occurrence of the specified element from this deque. | |
| 384 | + * If the deque does not contain the element, it is unchanged. | |
| 385 | + * More formally, removes the last element <tt>e</tt> such that | |
| 386 | + * <tt>o.equals(e)</tt> (if such an element exists). | |
| 387 | + * Returns <tt>true</tt> if this deque contained the specified element | |
| 388 | + * (or equivalently, if this deque changed as a result of the call). | |
| 389 | + * | |
| 390 | + * @param o element to be removed from this deque, if present | |
| 391 | + * @return <tt>true</tt> if an element was removed as a result of this call | |
| 392 | + * @throws ClassCastException if the class of the specified element | |
| 393 | + * is incompatible with this deque (optional) | |
| 394 | + * @throws NullPointerException if the specified element is null (optional) | |
| 395 | + */ | |
| 396 | + boolean removeLastOccurrence(Object o); | |
| 397 | + | |
| 398 | + // *** BlockingQueue methods *** | |
| 399 | + | |
| 400 | + /** | |
| 401 | + * Inserts the specified element into the queue represented by this deque | |
| 402 | + * (in other words, at the tail of this deque) if it is possible to do so | |
| 403 | + * immediately without violating capacity restrictions, returning | |
| 404 | + * <tt>true</tt> upon success and throwing an | |
| 405 | + * <tt>IllegalStateException</tt> if no space is currently available. | |
| 406 | + * When using a capacity-restricted deque, it is generally preferable to | |
| 407 | + * use {@link #offer offer}. | |
| 408 | + * | |
| 409 | + * <p>This method is equivalent to {@link #addLast addLast}. | |
| 410 | + * | |
| 411 | + * @param e the element to add | |
| 412 | + * @throws IllegalStateException {@inheritDoc} | |
| 413 | + * @throws ClassCastException if the class of the specified element | |
| 414 | + * prevents it from being added to this deque | |
| 415 | + * @throws NullPointerException if the specified element is null | |
| 416 | + * @throws IllegalArgumentException if some property of the specified | |
| 417 | + * element prevents it from being added to this deque | |
| 418 | + */ | |
| 419 | + boolean add(E e); | |
| 420 | + | |
| 421 | + /** | |
| 422 | + * Inserts the specified element into the queue represented by this deque | |
| 423 | + * (in other words, at the tail of this deque) if it is possible to do so | |
| 424 | + * immediately without violating capacity restrictions, returning | |
| 425 | + * <tt>true</tt> upon success and <tt>false</tt> if no space is currently | |
| 426 | + * available. When using a capacity-restricted deque, this method is | |
| 427 | + * generally preferable to the {@link #add} method, which can fail to | |
| 428 | + * insert an element only by throwing an exception. | |
| 429 | + * | |
| 430 | + * <p>This method is equivalent to {@link #offerLast offerLast}. | |
| 431 | + * | |
| 432 | + * @param e the element to add | |
| 433 | + * @throws ClassCastException if the class of the specified element | |
| 434 | + * prevents it from being added to this deque | |
| 435 | + * @throws NullPointerException if the specified element is null | |
| 436 | + * @throws IllegalArgumentException if some property of the specified | |
| 437 | + * element prevents it from being added to this deque | |
| 438 | + */ | |
| 439 | + boolean offer(E e); | |
| 440 | + | |
| 441 | + /** | |
| 442 | + * Inserts the specified element into the queue represented by this deque | |
| 443 | + * (in other words, at the tail of this deque), waiting if necessary for | |
| 444 | + * space to become available. | |
| 445 | + * | |
| 446 | + * <p>This method is equivalent to {@link #putLast putLast}. | |
| 447 | + * | |
| 448 | + * @param e the element to add | |
| 449 | + * @throws InterruptedException {@inheritDoc} | |
| 450 | + * @throws ClassCastException if the class of the specified element | |
| 451 | + * prevents it from being added to this deque | |
| 452 | + * @throws NullPointerException if the specified element is null | |
| 453 | + * @throws IllegalArgumentException if some property of the specified | |
| 454 | + * element prevents it from being added to this deque | |
| 455 | + */ | |
| 456 | + void put(E e) throws InterruptedException; | |
| 457 | + | |
| 458 | + /** | |
| 459 | + * Inserts the specified element into the queue represented by this deque | |
| 460 | + * (in other words, at the tail of this deque), waiting up to the | |
| 461 | + * specified wait time if necessary for space to become available. | |
| 462 | + * | |
| 463 | + * <p>This method is equivalent to | |
| 464 | + * {@link #offerLast offerLast}. | |
| 465 | + * | |
| 466 | + * @param e the element to add | |
| 467 | + * @return <tt>true</tt> if the element was added to this deque, else | |
| 468 | + * <tt>false</tt> | |
| 469 | + * @throws InterruptedException {@inheritDoc} | |
| 470 | + * @throws ClassCastException if the class of the specified element | |
| 471 | + * prevents it from being added to this deque | |
| 472 | + * @throws NullPointerException if the specified element is null | |
| 473 | + * @throws IllegalArgumentException if some property of the specified | |
| 474 | + * element prevents it from being added to this deque | |
| 475 | + */ | |
| 476 | + boolean offer(E e, long timeout, TimeUnit unit) | |
| 477 | + throws InterruptedException; | |
| 478 | + | |
| 479 | + /** | |
| 480 | + * Retrieves and removes the head of the queue represented by this deque | |
| 481 | + * (in other words, the first element of this deque). | |
| 482 | + * This method differs from {@link #poll poll} only in that it | |
| 483 | + * throws an exception if this deque is empty. | |
| 484 | + * | |
| 485 | + * <p>This method is equivalent to {@link #removeFirst() removeFirst}. | |
| 486 | + * | |
| 487 | + * @return the head of the queue represented by this deque | |
| 488 | + * @throws NoSuchElementException if this deque is empty | |
| 489 | + */ | |
| 490 | + E remove(); | |
| 491 | + | |
| 492 | + /** | |
| 493 | + * Retrieves and removes the head of the queue represented by this deque | |
| 494 | + * (in other words, the first element of this deque), or returns | |
| 495 | + * <tt>null</tt> if this deque is empty. | |
| 496 | + * | |
| 497 | + * <p>This method is equivalent to {@link #pollFirst()}. | |
| 498 | + * | |
| 499 | + * @return the head of this deque, or <tt>null</tt> if this deque is empty | |
| 500 | + */ | |
| 501 | + E poll(); | |
| 502 | + | |
| 503 | + /** | |
| 504 | + * Retrieves and removes the head of the queue represented by this deque | |
| 505 | + * (in other words, the first element of this deque), waiting if | |
| 506 | + * necessary until an element becomes available. | |
| 507 | + * | |
| 508 | + * <p>This method is equivalent to {@link #takeFirst() takeFirst}. | |
| 509 | + * | |
| 510 | + * @return the head of this deque | |
| 511 | + * @throws InterruptedException if interrupted while waiting | |
| 512 | + */ | |
| 513 | + E take() throws InterruptedException; | |
| 514 | + | |
| 515 | + /** | |
| 516 | + * Retrieves and removes the head of the queue represented by this deque | |
| 517 | + * (in other words, the first element of this deque), waiting up to the | |
| 518 | + * specified wait time if necessary for an element to become available. | |
| 519 | + * | |
| 520 | + * <p>This method is equivalent to | |
| 521 | + * {@link #pollFirst(long,TimeUnit) pollFirst}. | |
| 522 | + * | |
| 523 | + * @return the head of this deque, or <tt>null</tt> if the | |
| 524 | + * specified waiting time elapses before an element is available | |
| 525 | + * @throws InterruptedException if interrupted while waiting | |
| 526 | + */ | |
| 527 | + E poll(long timeout, TimeUnit unit) | |
| 528 | + throws InterruptedException; | |
| 529 | + | |
| 530 | + /** | |
| 531 | + * Retrieves, but does not remove, the head of the queue represented by | |
| 532 | + * this deque (in other words, the first element of this deque). | |
| 533 | + * This method differs from {@link #peek peek} only in that it throws an | |
| 534 | + * exception if this deque is empty. | |
| 535 | + * | |
| 536 | + * <p>This method is equivalent to {@link #getFirst() getFirst}. | |
| 537 | + * | |
| 538 | + * @return the head of this deque | |
| 539 | + * @throws NoSuchElementException if this deque is empty | |
| 540 | + */ | |
| 541 | + E element(); | |
| 542 | + | |
| 543 | + /** | |
| 544 | + * Retrieves, but does not remove, the head of the queue represented by | |
| 545 | + * this deque (in other words, the first element of this deque), or | |
| 546 | + * returns <tt>null</tt> if this deque is empty. | |
| 547 | + * | |
| 548 | + * <p>This method is equivalent to {@link #peekFirst() peekFirst}. | |
| 549 | + * | |
| 550 | + * @return the head of this deque, or <tt>null</tt> if this deque is empty | |
| 551 | + */ | |
| 552 | + E peek(); | |
| 553 | + | |
| 554 | + /** | |
| 555 | + * Removes the first occurrence of the specified element from this deque. | |
| 556 | + * If the deque does not contain the element, it is unchanged. | |
| 557 | + * More formally, removes the first element <tt>e</tt> such that | |
| 558 | + * <tt>o.equals(e)</tt> (if such an element exists). | |
| 559 | + * Returns <tt>true</tt> if this deque contained the specified element | |
| 560 | + * (or equivalently, if this deque changed as a result of the call). | |
| 561 | + * | |
| 562 | + * <p>This method is equivalent to | |
| 563 | + * {@link #removeFirstOccurrence removeFirstOccurrence}. | |
| 564 | + * | |
| 565 | + * @param o element to be removed from this deque, if present | |
| 566 | + * @return <tt>true</tt> if this deque changed as a result of the call | |
| 567 | + * @throws ClassCastException if the class of the specified element | |
| 568 | + * is incompatible with this deque (optional) | |
| 569 | + * @throws NullPointerException if the specified element is null (optional) | |
| 570 | + */ | |
| 571 | + boolean remove(Object o); | |
| 572 | + | |
| 573 | + /** | |
| 574 | + * Returns <tt>true</tt> if this deque contains the specified element. | |
| 575 | + * More formally, returns <tt>true</tt> if and only if this deque contains | |
| 576 | + * at least one element <tt>e</tt> such that <tt>o.equals(e)</tt>. | |
| 577 | + * | |
| 578 | + * @param o object to be checked for containment in this deque | |
| 579 | + * @return <tt>true</tt> if this deque contains the specified element | |
| 580 | + * @throws ClassCastException if the class of the specified element | |
| 581 | + * is incompatible with this deque (optional) | |
| 582 | + * @throws NullPointerException if the specified element is null (optional) | |
| 583 | + */ | |
| 584 | + public boolean contains(Object o); | |
| 585 | + | |
| 586 | + /** | |
| 587 | + * Returns the number of elements in this deque. | |
| 588 | + * | |
| 589 | + * @return the number of elements in this deque | |
| 590 | + */ | |
| 591 | + public int size(); | |
| 592 | + | |
| 593 | + /** | |
| 594 | + * Returns an iterator over the elements in this deque in proper sequence. | |
| 595 | + * The elements will be returned in order from first (head) to last (tail). | |
| 596 | + * | |
| 597 | + * @return an iterator over the elements in this deque in proper sequence | |
| 598 | + */ | |
| 599 | + Iterator<E> iterator(); | |
| 600 | + | |
| 601 | + // *** Stack methods *** | |
| 602 | + | |
| 603 | + /** | |
| 604 | + * Pushes an element onto the stack represented by this deque. In other | |
| 605 | + * words, inserts the element at the front of this deque unless it would | |
| 606 | + * violate capacity restrictions. | |
| 607 | + * | |
| 608 | + * <p>This method is equivalent to {@link #addFirst addFirst}. | |
| 609 | + * | |
| 610 | + * @throws IllegalStateException {@inheritDoc} | |
| 611 | + * @throws ClassCastException {@inheritDoc} | |
| 612 | + * @throws NullPointerException if the specified element is null | |
| 613 | + * @throws IllegalArgumentException {@inheritDoc} | |
| 614 | + */ | |
| 615 | + void push(E e); | |
| 616 | +} | ... | ... |
| 1 | +/* | |
| 2 | + * Written by Doug Lea and Josh Bloch with assistance from members of | |
| 3 | + * JCP JSR-166 Expert Group and released to the public domain, as explained | |
| 4 | + * at http://creativecommons.org/licenses/publicdomain | |
| 5 | + */ | |
| 6 | + | |
| 7 | +package com.nostra13.universalimageloader.core.assist.deque; | |
| 8 | + | |
| 9 | +import java.util.Collection; | |
| 10 | +import java.util.Iterator; | |
| 11 | +import java.util.List; | |
| 12 | +import java.util.NoSuchElementException; | |
| 13 | +import java.util.Queue; | |
| 14 | +import java.util.Stack; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * A linear collection that supports element insertion and removal at | |
| 18 | + * both ends. The name <i>deque</i> is short for "double ended queue" | |
| 19 | + * and is usually pronounced "deck". Most <tt>Deque</tt> | |
| 20 | + * implementations place no fixed limits on the number of elements | |
| 21 | + * they may contain, but this interface supports capacity-restricted | |
| 22 | + * deques as well as those with no fixed size limit. | |
| 23 | + * | |
| 24 | + * <p>This interface defines methods to access the elements at both | |
| 25 | + * ends of the deque. Methods are provided to insert, remove, and | |
| 26 | + * examine the element. Each of these methods exists in two forms: | |
| 27 | + * one throws an exception if the operation fails, the other returns a | |
| 28 | + * special value (either <tt>null</tt> or <tt>false</tt>, depending on | |
| 29 | + * the operation). The latter form of the insert operation is | |
| 30 | + * designed specifically for use with capacity-restricted | |
| 31 | + * <tt>Deque</tt> implementations; in most implementations, insert | |
| 32 | + * operations cannot fail. | |
| 33 | + * | |
| 34 | + * <p>The twelve methods described above are summarized in the | |
| 35 | + * following table: | |
| 36 | + * | |
| 37 | + * <p> | |
| 38 | + * <table BORDER CELLPADDING=3 CELLSPACING=1> | |
| 39 | + * <tr> | |
| 40 | + * <td></td> | |
| 41 | + * <td ALIGN=CENTER COLSPAN = 2> <b>First Element (Head)</b></td> | |
| 42 | + * <td ALIGN=CENTER COLSPAN = 2> <b>Last Element (Tail)</b></td> | |
| 43 | + * </tr> | |
| 44 | + * <tr> | |
| 45 | + * <td></td> | |
| 46 | + * <td ALIGN=CENTER><em>Throws exception</em></td> | |
| 47 | + * <td ALIGN=CENTER><em>Special value</em></td> | |
| 48 | + * <td ALIGN=CENTER><em>Throws exception</em></td> | |
| 49 | + * <td ALIGN=CENTER><em>Special value</em></td> | |
| 50 | + * </tr> | |
| 51 | + * <tr> | |
| 52 | + * <td><b>Insert</b></td> | |
| 53 | + * <td>{@link #addFirst addFirst(e)}</td> | |
| 54 | + * <td>{@link #offerFirst offerFirst(e)}</td> | |
| 55 | + * <td>{@link #addLast addLast(e)}</td> | |
| 56 | + * <td>{@link #offerLast offerLast(e)}</td> | |
| 57 | + * </tr> | |
| 58 | + * <tr> | |
| 59 | + * <td><b>Remove</b></td> | |
| 60 | + * <td>{@link #removeFirst removeFirst()}</td> | |
| 61 | + * <td>{@link #pollFirst pollFirst()}</td> | |
| 62 | + * <td>{@link #removeLast removeLast()}</td> | |
| 63 | + * <td>{@link #pollLast pollLast()}</td> | |
| 64 | + * </tr> | |
| 65 | + * <tr> | |
| 66 | + * <td><b>Examine</b></td> | |
| 67 | + * <td>{@link #getFirst getFirst()}</td> | |
| 68 | + * <td>{@link #peekFirst peekFirst()}</td> | |
| 69 | + * <td>{@link #getLast getLast()}</td> | |
| 70 | + * <td>{@link #peekLast peekLast()}</td> | |
| 71 | + * </tr> | |
| 72 | + * </table> | |
| 73 | + * | |
| 74 | + * <p>This interface extends the {@link Queue} interface. When a deque is | |
| 75 | + * used as a queue, FIFO (First-In-First-Out) behavior results. Elements are | |
| 76 | + * added at the end of the deque and removed from the beginning. The methods | |
| 77 | + * inherited from the <tt>Queue</tt> interface are precisely equivalent to | |
| 78 | + * <tt>Deque</tt> methods as indicated in the following table: | |
| 79 | + * | |
| 80 | + * <p> | |
| 81 | + * <table BORDER CELLPADDING=3 CELLSPACING=1> | |
| 82 | + * <tr> | |
| 83 | + * <td ALIGN=CENTER> <b><tt>Queue</tt> Method</b></td> | |
| 84 | + * <td ALIGN=CENTER> <b>Equivalent <tt>Deque</tt> Method</b></td> | |
| 85 | + * </tr> | |
| 86 | + * <tr> | |
| 87 | + * <td>{@link Queue#add add(e)}</td> | |
| 88 | + * <td>{@link #addLast addLast(e)}</td> | |
| 89 | + * </tr> | |
| 90 | + * <tr> | |
| 91 | + * <td>{@link Queue#offer offer(e)}</td> | |
| 92 | + * <td>{@link #offerLast offerLast(e)}</td> | |
| 93 | + * </tr> | |
| 94 | + * <tr> | |
| 95 | + * <td>{@link Queue#remove remove()}</td> | |
| 96 | + * <td>{@link #removeFirst removeFirst()}</td> | |
| 97 | + * </tr> | |
| 98 | + * <tr> | |
| 99 | + * <td>{@link Queue#poll poll()}</td> | |
| 100 | + * <td>{@link #pollFirst pollFirst()}</td> | |
| 101 | + * </tr> | |
| 102 | + * <tr> | |
| 103 | + * <td>{@link Queue#element element()}</td> | |
| 104 | + * <td>{@link #getFirst getFirst()}</td> | |
| 105 | + * </tr> | |
| 106 | + * <tr> | |
| 107 | + * <td>{@link Queue#peek peek()}</td> | |
| 108 | + * <td>{@link #peek peekFirst()}</td> | |
| 109 | + * </tr> | |
| 110 | + * </table> | |
| 111 | + * | |
| 112 | + * <p>Deques can also be used as LIFO (Last-In-First-Out) stacks. This | |
| 113 | + * interface should be used in preference to the legacy {@link Stack} class. | |
| 114 | + * When a deque is used as a stack, elements are pushed and popped from the | |
| 115 | + * beginning of the deque. Stack methods are precisely equivalent to | |
| 116 | + * <tt>Deque</tt> methods as indicated in the table below: | |
| 117 | + * | |
| 118 | + * <p> | |
| 119 | + * <table BORDER CELLPADDING=3 CELLSPACING=1> | |
| 120 | + * <tr> | |
| 121 | + * <td ALIGN=CENTER> <b>Stack Method</b></td> | |
| 122 | + * <td ALIGN=CENTER> <b>Equivalent <tt>Deque</tt> Method</b></td> | |
| 123 | + * </tr> | |
| 124 | + * <tr> | |
| 125 | + * <td>{@link #push push(e)}</td> | |
| 126 | + * <td>{@link #addFirst addFirst(e)}</td> | |
| 127 | + * </tr> | |
| 128 | + * <tr> | |
| 129 | + * <td>{@link #pop pop()}</td> | |
| 130 | + * <td>{@link #removeFirst removeFirst()}</td> | |
| 131 | + * </tr> | |
| 132 | + * <tr> | |
| 133 | + * <td>{@link #peek peek()}</td> | |
| 134 | + * <td>{@link #peekFirst peekFirst()}</td> | |
| 135 | + * </tr> | |
| 136 | + * </table> | |
| 137 | + * | |
| 138 | + * <p>Note that the {@link #peek peek} method works equally well when | |
| 139 | + * a deque is used as a queue or a stack; in either case, elements are | |
| 140 | + * drawn from the beginning of the deque. | |
| 141 | + * | |
| 142 | + * <p>This interface provides two methods to remove interior | |
| 143 | + * elements, {@link #removeFirstOccurrence removeFirstOccurrence} and | |
| 144 | + * {@link #removeLastOccurrence removeLastOccurrence}. | |
| 145 | + * | |
| 146 | + * <p>Unlike the {@link List} interface, this interface does not | |
| 147 | + * provide support for indexed access to elements. | |
| 148 | + * | |
| 149 | + * <p>While <tt>Deque</tt> implementations are not strictly required | |
| 150 | + * to prohibit the insertion of null elements, they are strongly | |
| 151 | + * encouraged to do so. Users of any <tt>Deque</tt> implementations | |
| 152 | + * that do allow null elements are strongly encouraged <i>not</i> to | |
| 153 | + * take advantage of the ability to insert nulls. This is so because | |
| 154 | + * <tt>null</tt> is used as a special return value by various methods | |
| 155 | + * to indicated that the deque is empty. | |
| 156 | + * | |
| 157 | + * <p><tt>Deque</tt> implementations generally do not define | |
| 158 | + * element-based versions of the <tt>equals</tt> and <tt>hashCode</tt> | |
| 159 | + * methods, but instead inherit the identity-based versions from class | |
| 160 | + * <tt>Object</tt>. | |
| 161 | + * | |
| 162 | + * @author Doug Lea | |
| 163 | + * @author Josh Bloch | |
| 164 | + * @since 1.6 | |
| 165 | + * @param <E> the type of elements held in this collection | |
| 166 | + */ | |
| 167 | + | |
| 168 | +public interface Deque<E> extends Queue<E> { | |
| 169 | + /** | |
| 170 | + * Inserts the specified element at the front of this deque if it is | |
| 171 | + * possible to do so immediately without violating capacity restrictions. | |
| 172 | + * When using a capacity-restricted deque, it is generally preferable to | |
| 173 | + * use method {@link #offerFirst}. | |
| 174 | + * | |
| 175 | + * @param e the element to add | |
| 176 | + * @throws IllegalStateException if the element cannot be added at this | |
| 177 | + * time due to capacity restrictions | |
| 178 | + * @throws ClassCastException if the class of the specified element | |
| 179 | + * prevents it from being added to this deque | |
| 180 | + * @throws NullPointerException if the specified element is null and this | |
| 181 | + * deque does not permit null elements | |
| 182 | + * @throws IllegalArgumentException if some property of the specified | |
| 183 | + * element prevents it from being added to this deque | |
| 184 | + */ | |
| 185 | + void addFirst(E e); | |
| 186 | + | |
| 187 | + /** | |
| 188 | + * Inserts the specified element at the end of this deque if it is | |
| 189 | + * possible to do so immediately without violating capacity restrictions. | |
| 190 | + * When using a capacity-restricted deque, it is generally preferable to | |
| 191 | + * use method {@link #offerLast}. | |
| 192 | + * | |
| 193 | + * <p>This method is equivalent to {@link #add}. | |
| 194 | + * | |
| 195 | + * @param e the element to add | |
| 196 | + * @throws IllegalStateException if the element cannot be added at this | |
| 197 | + * time due to capacity restrictions | |
| 198 | + * @throws ClassCastException if the class of the specified element | |
| 199 | + * prevents it from being added to this deque | |
| 200 | + * @throws NullPointerException if the specified element is null and this | |
| 201 | + * deque does not permit null elements | |
| 202 | + * @throws IllegalArgumentException if some property of the specified | |
| 203 | + * element prevents it from being added to this deque | |
| 204 | + */ | |
| 205 | + void addLast(E e); | |
| 206 | + | |
| 207 | + /** | |
| 208 | + * Inserts the specified element at the front of this deque unless it would | |
| 209 | + * violate capacity restrictions. When using a capacity-restricted deque, | |
| 210 | + * this method is generally preferable to the {@link #addFirst} method, | |
| 211 | + * which can fail to insert an element only by throwing an exception. | |
| 212 | + * | |
| 213 | + * @param e the element to add | |
| 214 | + * @return <tt>true</tt> if the element was added to this deque, else | |
| 215 | + * <tt>false</tt> | |
| 216 | + * @throws ClassCastException if the class of the specified element | |
| 217 | + * prevents it from being added to this deque | |
| 218 | + * @throws NullPointerException if the specified element is null and this | |
| 219 | + * deque does not permit null elements | |
| 220 | + * @throws IllegalArgumentException if some property of the specified | |
| 221 | + * element prevents it from being added to this deque | |
| 222 | + */ | |
| 223 | + boolean offerFirst(E e); | |
| 224 | + | |
| 225 | + /** | |
| 226 | + * Inserts the specified element at the end of this deque unless it would | |
| 227 | + * violate capacity restrictions. When using a capacity-restricted deque, | |
| 228 | + * this method is generally preferable to the {@link #addLast} method, | |
| 229 | + * which can fail to insert an element only by throwing an exception. | |
| 230 | + * | |
| 231 | + * @param e the element to add | |
| 232 | + * @return <tt>true</tt> if the element was added to this deque, else | |
| 233 | + * <tt>false</tt> | |
| 234 | + * @throws ClassCastException if the class of the specified element | |
| 235 | + * prevents it from being added to this deque | |
| 236 | + * @throws NullPointerException if the specified element is null and this | |
| 237 | + * deque does not permit null elements | |
| 238 | + * @throws IllegalArgumentException if some property of the specified | |
| 239 | + * element prevents it from being added to this deque | |
| 240 | + */ | |
| 241 | + boolean offerLast(E e); | |
| 242 | + | |
| 243 | + /** | |
| 244 | + * Retrieves and removes the first element of this deque. This method | |
| 245 | + * differs from {@link #pollFirst pollFirst} only in that it throws an | |
| 246 | + * exception if this deque is empty. | |
| 247 | + * | |
| 248 | + * @return the head of this deque | |
| 249 | + * @throws NoSuchElementException if this deque is empty | |
| 250 | + */ | |
| 251 | + E removeFirst(); | |
| 252 | + | |
| 253 | + /** | |
| 254 | + * Retrieves and removes the last element of this deque. This method | |
| 255 | + * differs from {@link #pollLast pollLast} only in that it throws an | |
| 256 | + * exception if this deque is empty. | |
| 257 | + * | |
| 258 | + * @return the tail of this deque | |
| 259 | + * @throws NoSuchElementException if this deque is empty | |
| 260 | + */ | |
| 261 | + E removeLast(); | |
| 262 | + | |
| 263 | + /** | |
| 264 | + * Retrieves and removes the first element of this deque, | |
| 265 | + * or returns <tt>null</tt> if this deque is empty. | |
| 266 | + * | |
| 267 | + * @return the head of this deque, or <tt>null</tt> if this deque is empty | |
| 268 | + */ | |
| 269 | + E pollFirst(); | |
| 270 | + | |
| 271 | + /** | |
| 272 | + * Retrieves and removes the last element of this deque, | |
| 273 | + * or returns <tt>null</tt> if this deque is empty. | |
| 274 | + * | |
| 275 | + * @return the tail of this deque, or <tt>null</tt> if this deque is empty | |
| 276 | + */ | |
| 277 | + E pollLast(); | |
| 278 | + | |
| 279 | + /** | |
| 280 | + * Retrieves, but does not remove, the first element of this deque. | |
| 281 | + * | |
| 282 | + * This method differs from {@link #peekFirst peekFirst} only in that it | |
| 283 | + * throws an exception if this deque is empty. | |
| 284 | + * | |
| 285 | + * @return the head of this deque | |
| 286 | + * @throws NoSuchElementException if this deque is empty | |
| 287 | + */ | |
| 288 | + E getFirst(); | |
| 289 | + | |
| 290 | + /** | |
| 291 | + * Retrieves, but does not remove, the last element of this deque. | |
| 292 | + * This method differs from {@link #peekLast peekLast} only in that it | |
| 293 | + * throws an exception if this deque is empty. | |
| 294 | + * | |
| 295 | + * @return the tail of this deque | |
| 296 | + * @throws NoSuchElementException if this deque is empty | |
| 297 | + */ | |
| 298 | + E getLast(); | |
| 299 | + | |
| 300 | + /** | |
| 301 | + * Retrieves, but does not remove, the first element of this deque, | |
| 302 | + * or returns <tt>null</tt> if this deque is empty. | |
| 303 | + * | |
| 304 | + * @return the head of this deque, or <tt>null</tt> if this deque is empty | |
| 305 | + */ | |
| 306 | + E peekFirst(); | |
| 307 | + | |
| 308 | + /** | |
| 309 | + * Retrieves, but does not remove, the last element of this deque, | |
| 310 | + * or returns <tt>null</tt> if this deque is empty. | |
| 311 | + * | |
| 312 | + * @return the tail of this deque, or <tt>null</tt> if this deque is empty | |
| 313 | + */ | |
| 314 | + E peekLast(); | |
| 315 | + | |
| 316 | + /** | |
| 317 | + * Removes the first occurrence of the specified element from this deque. | |
| 318 | + * If the deque does not contain the element, it is unchanged. | |
| 319 | + * More formally, removes the first element <tt>e</tt> such that | |
| 320 | + * <tt>(o==null ? e==null : o.equals(e))</tt> | |
| 321 | + * (if such an element exists). | |
| 322 | + * Returns <tt>true</tt> if this deque contained the specified element | |
| 323 | + * (or equivalently, if this deque changed as a result of the call). | |
| 324 | + * | |
| 325 | + * @param o element to be removed from this deque, if present | |
| 326 | + * @return <tt>true</tt> if an element was removed as a result of this call | |
| 327 | + * @throws ClassCastException if the class of the specified element | |
| 328 | + * is incompatible with this deque (optional) | |
| 329 | + * @throws NullPointerException if the specified element is null and this | |
| 330 | + * deque does not permit null elements (optional) | |
| 331 | + */ | |
| 332 | + boolean removeFirstOccurrence(Object o); | |
| 333 | + | |
| 334 | + /** | |
| 335 | + * Removes the last occurrence of the specified element from this deque. | |
| 336 | + * If the deque does not contain the element, it is unchanged. | |
| 337 | + * More formally, removes the last element <tt>e</tt> such that | |
| 338 | + * <tt>(o==null ? e==null : o.equals(e))</tt> | |
| 339 | + * (if such an element exists). | |
| 340 | + * Returns <tt>true</tt> if this deque contained the specified element | |
| 341 | + * (or equivalently, if this deque changed as a result of the call). | |
| 342 | + * | |
| 343 | + * @param o element to be removed from this deque, if present | |
| 344 | + * @return <tt>true</tt> if an element was removed as a result of this call | |
| 345 | + * @throws ClassCastException if the class of the specified element | |
| 346 | + * is incompatible with this deque (optional) | |
| 347 | + * @throws NullPointerException if the specified element is null and this | |
| 348 | + * deque does not permit null elements (optional) | |
| 349 | + */ | |
| 350 | + boolean removeLastOccurrence(Object o); | |
| 351 | + | |
| 352 | + // *** Queue methods *** | |
| 353 | + | |
| 354 | + /** | |
| 355 | + * Inserts the specified element into the queue represented by this deque | |
| 356 | + * (in other words, at the tail of this deque) if it is possible to do so | |
| 357 | + * immediately without violating capacity restrictions, returning | |
| 358 | + * <tt>true</tt> upon success and throwing an | |
| 359 | + * <tt>IllegalStateException</tt> if no space is currently available. | |
| 360 | + * When using a capacity-restricted deque, it is generally preferable to | |
| 361 | + * use {@link #offer offer}. | |
| 362 | + * | |
| 363 | + * <p>This method is equivalent to {@link #addLast}. | |
| 364 | + * | |
| 365 | + * @param e the element to add | |
| 366 | + * @return <tt>true</tt> (as specified by {@link Collection#add}) | |
| 367 | + * @throws IllegalStateException if the element cannot be added at this | |
| 368 | + * time due to capacity restrictions | |
| 369 | + * @throws ClassCastException if the class of the specified element | |
| 370 | + * prevents it from being added to this deque | |
| 371 | + * @throws NullPointerException if the specified element is null and this | |
| 372 | + * deque does not permit null elements | |
| 373 | + * @throws IllegalArgumentException if some property of the specified | |
| 374 | + * element prevents it from being added to this deque | |
| 375 | + */ | |
| 376 | + boolean add(E e); | |
| 377 | + | |
| 378 | + /** | |
| 379 | + * Inserts the specified element into the queue represented by this deque | |
| 380 | + * (in other words, at the tail of this deque) if it is possible to do so | |
| 381 | + * immediately without violating capacity restrictions, returning | |
| 382 | + * <tt>true</tt> upon success and <tt>false</tt> if no space is currently | |
| 383 | + * available. When using a capacity-restricted deque, this method is | |
| 384 | + * generally preferable to the {@link #add} method, which can fail to | |
| 385 | + * insert an element only by throwing an exception. | |
| 386 | + * | |
| 387 | + * <p>This method is equivalent to {@link #offerLast}. | |
| 388 | + * | |
| 389 | + * @param e the element to add | |
| 390 | + * @return <tt>true</tt> if the element was added to this deque, else | |
| 391 | + * <tt>false</tt> | |
| 392 | + * @throws ClassCastException if the class of the specified element | |
| 393 | + * prevents it from being added to this deque | |
| 394 | + * @throws NullPointerException if the specified element is null and this | |
| 395 | + * deque does not permit null elements | |
| 396 | + * @throws IllegalArgumentException if some property of the specified | |
| 397 | + * element prevents it from being added to this deque | |
| 398 | + */ | |
| 399 | + boolean offer(E e); | |
| 400 | + | |
| 401 | + /** | |
| 402 | + * Retrieves and removes the head of the queue represented by this deque | |
| 403 | + * (in other words, the first element of this deque). | |
| 404 | + * This method differs from {@link #poll poll} only in that it throws an | |
| 405 | + * exception if this deque is empty. | |
| 406 | + * | |
| 407 | + * <p>This method is equivalent to {@link #removeFirst()}. | |
| 408 | + * | |
| 409 | + * @return the head of the queue represented by this deque | |
| 410 | + * @throws NoSuchElementException if this deque is empty | |
| 411 | + */ | |
| 412 | + E remove(); | |
| 413 | + | |
| 414 | + /** | |
| 415 | + * Retrieves and removes the head of the queue represented by this deque | |
| 416 | + * (in other words, the first element of this deque), or returns | |
| 417 | + * <tt>null</tt> if this deque is empty. | |
| 418 | + * | |
| 419 | + * <p>This method is equivalent to {@link #pollFirst()}. | |
| 420 | + * | |
| 421 | + * @return the first element of this deque, or <tt>null</tt> if | |
| 422 | + * this deque is empty | |
| 423 | + */ | |
| 424 | + E poll(); | |
| 425 | + | |
| 426 | + /** | |
| 427 | + * Retrieves, but does not remove, the head of the queue represented by | |
| 428 | + * this deque (in other words, the first element of this deque). | |
| 429 | + * This method differs from {@link #peek peek} only in that it throws an | |
| 430 | + * exception if this deque is empty. | |
| 431 | + * | |
| 432 | + * <p>This method is equivalent to {@link #getFirst()}. | |
| 433 | + * | |
| 434 | + * @return the head of the queue represented by this deque | |
| 435 | + * @throws NoSuchElementException if this deque is empty | |
| 436 | + */ | |
| 437 | + E element(); | |
| 438 | + | |
| 439 | + /** | |
| 440 | + * Retrieves, but does not remove, the head of the queue represented by | |
| 441 | + * this deque (in other words, the first element of this deque), or | |
| 442 | + * returns <tt>null</tt> if this deque is empty. | |
| 443 | + * | |
| 444 | + * <p>This method is equivalent to {@link #peekFirst()}. | |
| 445 | + * | |
| 446 | + * @return the head of the queue represented by this deque, or | |
| 447 | + * <tt>null</tt> if this deque is empty | |
| 448 | + */ | |
| 449 | + E peek(); | |
| 450 | + | |
| 451 | + | |
| 452 | + // *** Stack methods *** | |
| 453 | + | |
| 454 | + /** | |
| 455 | + * Pushes an element onto the stack represented by this deque (in other | |
| 456 | + * words, at the head of this deque) if it is possible to do so | |
| 457 | + * immediately without violating capacity restrictions, returning | |
| 458 | + * <tt>true</tt> upon success and throwing an | |
| 459 | + * <tt>IllegalStateException</tt> if no space is currently available. | |
| 460 | + * | |
| 461 | + * <p>This method is equivalent to {@link #addFirst}. | |
| 462 | + * | |
| 463 | + * @param e the element to push | |
| 464 | + * @throws IllegalStateException if the element cannot be added at this | |
| 465 | + * time due to capacity restrictions | |
| 466 | + * @throws ClassCastException if the class of the specified element | |
| 467 | + * prevents it from being added to this deque | |
| 468 | + * @throws NullPointerException if the specified element is null and this | |
| 469 | + * deque does not permit null elements | |
| 470 | + * @throws IllegalArgumentException if some property of the specified | |
| 471 | + * element prevents it from being added to this deque | |
| 472 | + */ | |
| 473 | + void push(E e); | |
| 474 | + | |
| 475 | + /** | |
| 476 | + * Pops an element from the stack represented by this deque. In other | |
| 477 | + * words, removes and returns the first element of this deque. | |
| 478 | + * | |
| 479 | + * <p>This method is equivalent to {@link #removeFirst()}. | |
| 480 | + * | |
| 481 | + * @return the element at the front of this deque (which is the top | |
| 482 | + * of the stack represented by this deque) | |
| 483 | + * @throws NoSuchElementException if this deque is empty | |
| 484 | + */ | |
| 485 | + E pop(); | |
| 486 | + | |
| 487 | + | |
| 488 | + // *** Collection methods *** | |
| 489 | + | |
| 490 | + /** | |
| 491 | + * Removes the first occurrence of the specified element from this deque. | |
| 492 | + * If the deque does not contain the element, it is unchanged. | |
| 493 | + * More formally, removes the first element <tt>e</tt> such that | |
| 494 | + * <tt>(o==null ? e==null : o.equals(e))</tt> | |
| 495 | + * (if such an element exists). | |
| 496 | + * Returns <tt>true</tt> if this deque contained the specified element | |
| 497 | + * (or equivalently, if this deque changed as a result of the call). | |
| 498 | + * | |
| 499 | + * <p>This method is equivalent to {@link #removeFirstOccurrence}. | |
| 500 | + * | |
| 501 | + * @param o element to be removed from this deque, if present | |
| 502 | + * @return <tt>true</tt> if an element was removed as a result of this call | |
| 503 | + * @throws ClassCastException if the class of the specified element | |
| 504 | + * is incompatible with this deque (optional) | |
| 505 | + * @throws NullPointerException if the specified element is null and this | |
| 506 | + * deque does not permit null elements (optional) | |
| 507 | + */ | |
| 508 | + boolean remove(Object o); | |
| 509 | + | |
| 510 | + /** | |
| 511 | + * Returns <tt>true</tt> if this deque contains the specified element. | |
| 512 | + * More formally, returns <tt>true</tt> if and only if this deque contains | |
| 513 | + * at least one element <tt>e</tt> such that | |
| 514 | + * <tt>(o==null ? e==null : o.equals(e))</tt>. | |
| 515 | + * | |
| 516 | + * @param o element whose presence in this deque is to be tested | |
| 517 | + * @return <tt>true</tt> if this deque contains the specified element | |
| 518 | + * @throws ClassCastException if the type of the specified element | |
| 519 | + * is incompatible with this deque (optional) | |
| 520 | + * @throws NullPointerException if the specified element is null and this | |
| 521 | + * deque does not permit null elements (optional) | |
| 522 | + */ | |
| 523 | + boolean contains(Object o); | |
| 524 | + | |
| 525 | + /** | |
| 526 | + * Returns the number of elements in this deque. | |
| 527 | + * | |
| 528 | + * @return the number of elements in this deque | |
| 529 | + */ | |
| 530 | + public int size(); | |
| 531 | + | |
| 532 | + /** | |
| 533 | + * Returns an iterator over the elements in this deque in proper sequence. | |
| 534 | + * The elements will be returned in order from first (head) to last (tail). | |
| 535 | + * | |
| 536 | + * @return an iterator over the elements in this deque in proper sequence | |
| 537 | + */ | |
| 538 | + Iterator<E> iterator(); | |
| 539 | + | |
| 540 | + /** | |
| 541 | + * Returns an iterator over the elements in this deque in reverse | |
| 542 | + * sequential order. The elements will be returned in order from | |
| 543 | + * last (tail) to first (head). | |
| 544 | + * | |
| 545 | + * @return an iterator over the elements in this deque in reverse | |
| 546 | + * sequence | |
| 547 | + */ | |
| 548 | + Iterator<E> descendingIterator(); | |
| 549 | + | |
| 550 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/assist/deque/LIFOLinkedBlockingDeque.java
0 → 100644
| 1 | +package com.nostra13.universalimageloader.core.assist.deque; | |
| 2 | + | |
| 3 | +import java.util.NoSuchElementException; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * {@link LinkedBlockingDeque} using LIFO algorithm | |
| 7 | + * | |
| 8 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 9 | + * @since 1.6.3 | |
| 10 | + */ | |
| 11 | +public class LIFOLinkedBlockingDeque<T> extends LinkedBlockingDeque<T> { | |
| 12 | + | |
| 13 | + private static final long serialVersionUID = -4114786347960826192L; | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * Inserts the specified element at the front of this deque if it is possible to do so immediately without violating | |
| 17 | + * capacity restrictions, returning <tt>true</tt> upon success and <tt>false</tt> if no space is currently | |
| 18 | + * available. When using a capacity-restricted deque, this method is generally preferable to the {@link #addFirst | |
| 19 | + * addFirst} method, which can fail to insert an element only by throwing an exception. | |
| 20 | + * | |
| 21 | + * @param e | |
| 22 | + * the element to add | |
| 23 | + * @throws ClassCastException | |
| 24 | + * {@inheritDoc} | |
| 25 | + * @throws NullPointerException | |
| 26 | + * if the specified element is null | |
| 27 | + * @throws IllegalArgumentException | |
| 28 | + * {@inheritDoc} | |
| 29 | + */ | |
| 30 | + @Override | |
| 31 | + public boolean offer(T e) { | |
| 32 | + return super.offerFirst(e); | |
| 33 | + } | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * Retrieves and removes the first element of this deque. This method differs from {@link #pollFirst pollFirst} only | |
| 37 | + * in that it throws an exception if this deque is empty. | |
| 38 | + * | |
| 39 | + * @return the head of this deque | |
| 40 | + * @throws NoSuchElementException | |
| 41 | + * if this deque is empty | |
| 42 | + */ | |
| 43 | + @Override | |
| 44 | + public T remove() { | |
| 45 | + return super.removeFirst(); | |
| 46 | + } | |
| 47 | +} | |
| \ No newline at end of file | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/assist/deque/LinkedBlockingDeque.java
0 → 100644
| 1 | +/* | |
| 2 | + * Written by Doug Lea with assistance from members of JCP JSR-166 | |
| 3 | + * Expert Group and released to the public domain, as explained at | |
| 4 | + * http://creativecommons.org/licenses/publicdomain | |
| 5 | + */ | |
| 6 | + | |
| 7 | +package com.nostra13.universalimageloader.core.assist.deque; | |
| 8 | + | |
| 9 | +import java.util.AbstractQueue; | |
| 10 | +import java.util.Collection; | |
| 11 | +import java.util.Iterator; | |
| 12 | +import java.util.NoSuchElementException; | |
| 13 | +import java.util.concurrent.TimeUnit; | |
| 14 | +import java.util.concurrent.locks.Condition; | |
| 15 | +import java.util.concurrent.locks.ReentrantLock; | |
| 16 | + | |
| 17 | +/** | |
| 18 | + * An optionally-bounded {@linkplain BlockingDeque blocking deque} based on | |
| 19 | + * linked nodes. | |
| 20 | + * | |
| 21 | + * <p> The optional capacity bound constructor argument serves as a | |
| 22 | + * way to prevent excessive expansion. The capacity, if unspecified, | |
| 23 | + * is equal to {@link Integer#MAX_VALUE}. Linked nodes are | |
| 24 | + * dynamically created upon each insertion unless this would bring the | |
| 25 | + * deque above capacity. | |
| 26 | + * | |
| 27 | + * <p>Most operations run in constant time (ignoring time spent | |
| 28 | + * blocking). Exceptions include {@link #remove(Object) remove}, | |
| 29 | + * {@link #removeFirstOccurrence removeFirstOccurrence}, {@link | |
| 30 | + * #removeLastOccurrence removeLastOccurrence}, {@link #contains | |
| 31 | + * contains}, {@link #iterator iterator.remove()}, and the bulk | |
| 32 | + * operations, all of which run in linear time. | |
| 33 | + * | |
| 34 | + * <p>This class and its iterator implement all of the | |
| 35 | + * <em>optional</em> methods of the {@link Collection} and {@link | |
| 36 | + * Iterator} interfaces. | |
| 37 | + * | |
| 38 | + * <p>This class is a member of the | |
| 39 | + * <a href="{@docRoot}/../technotes/guides/collections/index.html"> | |
| 40 | + * Java Collections Framework</a>. | |
| 41 | + * | |
| 42 | + * @since 1.6 | |
| 43 | + * @author Doug Lea | |
| 44 | + * @param <E> the type of elements held in this collection | |
| 45 | + */ | |
| 46 | +public class LinkedBlockingDeque<E> | |
| 47 | + extends AbstractQueue<E> | |
| 48 | + implements BlockingDeque<E>, java.io.Serializable { | |
| 49 | + | |
| 50 | + /* | |
| 51 | + * Implemented as a simple doubly-linked list protected by a | |
| 52 | + * single lock and using conditions to manage blocking. | |
| 53 | + * | |
| 54 | + * To implement weakly consistent iterators, it appears we need to | |
| 55 | + * keep all Nodes GC-reachable from a predecessor dequeued Node. | |
| 56 | + * That would cause two problems: | |
| 57 | + * - allow a rogue Iterator to cause unbounded memory retention | |
| 58 | + * - cause cross-generational linking of old Nodes to new Nodes if | |
| 59 | + * a Node was tenured while live, which generational GCs have a | |
| 60 | + * hard time dealing with, causing repeated major collections. | |
| 61 | + * However, only non-deleted Nodes need to be reachable from | |
| 62 | + * dequeued Nodes, and reachability does not necessarily have to | |
| 63 | + * be of the kind understood by the GC. We use the trick of | |
| 64 | + * linking a Node that has just been dequeued to itself. Such a | |
| 65 | + * self-link implicitly means to jump to "first" (for next links) | |
| 66 | + * or "last" (for prev links). | |
| 67 | + */ | |
| 68 | + | |
| 69 | + /* | |
| 70 | + * We have "diamond" multiple interface/abstract class inheritance | |
| 71 | + * here, and that introduces ambiguities. Often we want the | |
| 72 | + * BlockingDeque javadoc combined with the AbstractQueue | |
| 73 | + * implementation, so a lot of method specs are duplicated here. | |
| 74 | + */ | |
| 75 | + | |
| 76 | + private static final long serialVersionUID = -387911632671998426L; | |
| 77 | + | |
| 78 | + /** Doubly-linked list node class */ | |
| 79 | + static final class Node<E> { | |
| 80 | + /** | |
| 81 | + * The item, or null if this node has been removed. | |
| 82 | + */ | |
| 83 | + E item; | |
| 84 | + | |
| 85 | + /** | |
| 86 | + * One of: | |
| 87 | + * - the real predecessor Node | |
| 88 | + * - this Node, meaning the predecessor is tail | |
| 89 | + * - null, meaning there is no predecessor | |
| 90 | + */ | |
| 91 | + Node<E> prev; | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * One of: | |
| 95 | + * - the real successor Node | |
| 96 | + * - this Node, meaning the successor is head | |
| 97 | + * - null, meaning there is no successor | |
| 98 | + */ | |
| 99 | + Node<E> next; | |
| 100 | + | |
| 101 | + Node(E x) { | |
| 102 | + item = x; | |
| 103 | + } | |
| 104 | + } | |
| 105 | + | |
| 106 | + /** | |
| 107 | + * Pointer to first node. | |
| 108 | + * Invariant: (first == null && last == null) || | |
| 109 | + * (first.prev == null && first.item != null) | |
| 110 | + */ | |
| 111 | + transient Node<E> first; | |
| 112 | + | |
| 113 | + /** | |
| 114 | + * Pointer to last node. | |
| 115 | + * Invariant: (first == null && last == null) || | |
| 116 | + * (last.next == null && last.item != null) | |
| 117 | + */ | |
| 118 | + transient Node<E> last; | |
| 119 | + | |
| 120 | + /** Number of items in the deque */ | |
| 121 | + private transient int count; | |
| 122 | + | |
| 123 | + /** Maximum number of items in the deque */ | |
| 124 | + private final int capacity; | |
| 125 | + | |
| 126 | + /** Main lock guarding all access */ | |
| 127 | + final ReentrantLock lock = new ReentrantLock(); | |
| 128 | + | |
| 129 | + /** Condition for waiting takes */ | |
| 130 | + private final Condition notEmpty = lock.newCondition(); | |
| 131 | + | |
| 132 | + /** Condition for waiting puts */ | |
| 133 | + private final Condition notFull = lock.newCondition(); | |
| 134 | + | |
| 135 | + /** | |
| 136 | + * Creates a {@code LinkedBlockingDeque} with a capacity of | |
| 137 | + * {@link Integer#MAX_VALUE}. | |
| 138 | + */ | |
| 139 | + public LinkedBlockingDeque() { | |
| 140 | + this(Integer.MAX_VALUE); | |
| 141 | + } | |
| 142 | + | |
| 143 | + /** | |
| 144 | + * Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity. | |
| 145 | + * | |
| 146 | + * @param capacity the capacity of this deque | |
| 147 | + * @throws IllegalArgumentException if {@code capacity} is less than 1 | |
| 148 | + */ | |
| 149 | + public LinkedBlockingDeque(int capacity) { | |
| 150 | + if (capacity <= 0) throw new IllegalArgumentException(); | |
| 151 | + this.capacity = capacity; | |
| 152 | + } | |
| 153 | + | |
| 154 | + /** | |
| 155 | + * Creates a {@code LinkedBlockingDeque} with a capacity of | |
| 156 | + * {@link Integer#MAX_VALUE}, initially containing the elements of | |
| 157 | + * the given collection, added in traversal order of the | |
| 158 | + * collection's iterator. | |
| 159 | + * | |
| 160 | + * @param c the collection of elements to initially contain | |
| 161 | + * @throws NullPointerException if the specified collection or any | |
| 162 | + * of its elements are null | |
| 163 | + */ | |
| 164 | + public LinkedBlockingDeque(Collection<? extends E> c) { | |
| 165 | + this(Integer.MAX_VALUE); | |
| 166 | + final ReentrantLock lock = this.lock; | |
| 167 | + lock.lock(); // Never contended, but necessary for visibility | |
| 168 | + try { | |
| 169 | + for (E e : c) { | |
| 170 | + if (e == null) | |
| 171 | + throw new NullPointerException(); | |
| 172 | + if (!linkLast(new Node<E>(e))) | |
| 173 | + throw new IllegalStateException("Deque full"); | |
| 174 | + } | |
| 175 | + } finally { | |
| 176 | + lock.unlock(); | |
| 177 | + } | |
| 178 | + } | |
| 179 | + | |
| 180 | + | |
| 181 | + // Basic linking and unlinking operations, called only while holding lock | |
| 182 | + | |
| 183 | + /** | |
| 184 | + * Links node as first element, or returns false if full. | |
| 185 | + */ | |
| 186 | + private boolean linkFirst(Node<E> node) { | |
| 187 | + // assert lock.isHeldByCurrentThread(); | |
| 188 | + if (count >= capacity) | |
| 189 | + return false; | |
| 190 | + Node<E> f = first; | |
| 191 | + node.next = f; | |
| 192 | + first = node; | |
| 193 | + if (last == null) | |
| 194 | + last = node; | |
| 195 | + else | |
| 196 | + f.prev = node; | |
| 197 | + ++count; | |
| 198 | + notEmpty.signal(); | |
| 199 | + return true; | |
| 200 | + } | |
| 201 | + | |
| 202 | + /** | |
| 203 | + * Links node as last element, or returns false if full. | |
| 204 | + */ | |
| 205 | + private boolean linkLast(Node<E> node) { | |
| 206 | + // assert lock.isHeldByCurrentThread(); | |
| 207 | + if (count >= capacity) | |
| 208 | + return false; | |
| 209 | + Node<E> l = last; | |
| 210 | + node.prev = l; | |
| 211 | + last = node; | |
| 212 | + if (first == null) | |
| 213 | + first = node; | |
| 214 | + else | |
| 215 | + l.next = node; | |
| 216 | + ++count; | |
| 217 | + notEmpty.signal(); | |
| 218 | + return true; | |
| 219 | + } | |
| 220 | + | |
| 221 | + /** | |
| 222 | + * Removes and returns first element, or null if empty. | |
| 223 | + */ | |
| 224 | + private E unlinkFirst() { | |
| 225 | + // assert lock.isHeldByCurrentThread(); | |
| 226 | + Node<E> f = first; | |
| 227 | + if (f == null) | |
| 228 | + return null; | |
| 229 | + Node<E> n = f.next; | |
| 230 | + E item = f.item; | |
| 231 | + f.item = null; | |
| 232 | + f.next = f; // help GC | |
| 233 | + first = n; | |
| 234 | + if (n == null) | |
| 235 | + last = null; | |
| 236 | + else | |
| 237 | + n.prev = null; | |
| 238 | + --count; | |
| 239 | + notFull.signal(); | |
| 240 | + return item; | |
| 241 | + } | |
| 242 | + | |
| 243 | + /** | |
| 244 | + * Removes and returns last element, or null if empty. | |
| 245 | + */ | |
| 246 | + private E unlinkLast() { | |
| 247 | + // assert lock.isHeldByCurrentThread(); | |
| 248 | + Node<E> l = last; | |
| 249 | + if (l == null) | |
| 250 | + return null; | |
| 251 | + Node<E> p = l.prev; | |
| 252 | + E item = l.item; | |
| 253 | + l.item = null; | |
| 254 | + l.prev = l; // help GC | |
| 255 | + last = p; | |
| 256 | + if (p == null) | |
| 257 | + first = null; | |
| 258 | + else | |
| 259 | + p.next = null; | |
| 260 | + --count; | |
| 261 | + notFull.signal(); | |
| 262 | + return item; | |
| 263 | + } | |
| 264 | + | |
| 265 | + /** | |
| 266 | + * Unlinks x. | |
| 267 | + */ | |
| 268 | + void unlink(Node<E> x) { | |
| 269 | + // assert lock.isHeldByCurrentThread(); | |
| 270 | + Node<E> p = x.prev; | |
| 271 | + Node<E> n = x.next; | |
| 272 | + if (p == null) { | |
| 273 | + unlinkFirst(); | |
| 274 | + } else if (n == null) { | |
| 275 | + unlinkLast(); | |
| 276 | + } else { | |
| 277 | + p.next = n; | |
| 278 | + n.prev = p; | |
| 279 | + x.item = null; | |
| 280 | + // Don't mess with x's links. They may still be in use by | |
| 281 | + // an iterator. | |
| 282 | + --count; | |
| 283 | + notFull.signal(); | |
| 284 | + } | |
| 285 | + } | |
| 286 | + | |
| 287 | + // BlockingDeque methods | |
| 288 | + | |
| 289 | + /** | |
| 290 | + * @throws IllegalStateException {@inheritDoc} | |
| 291 | + * @throws NullPointerException {@inheritDoc} | |
| 292 | + */ | |
| 293 | + public void addFirst(E e) { | |
| 294 | + if (!offerFirst(e)) | |
| 295 | + throw new IllegalStateException("Deque full"); | |
| 296 | + } | |
| 297 | + | |
| 298 | + /** | |
| 299 | + * @throws IllegalStateException {@inheritDoc} | |
| 300 | + * @throws NullPointerException {@inheritDoc} | |
| 301 | + */ | |
| 302 | + public void addLast(E e) { | |
| 303 | + if (!offerLast(e)) | |
| 304 | + throw new IllegalStateException("Deque full"); | |
| 305 | + } | |
| 306 | + | |
| 307 | + /** | |
| 308 | + * @throws NullPointerException {@inheritDoc} | |
| 309 | + */ | |
| 310 | + public boolean offerFirst(E e) { | |
| 311 | + if (e == null) throw new NullPointerException(); | |
| 312 | + Node<E> node = new Node<E>(e); | |
| 313 | + final ReentrantLock lock = this.lock; | |
| 314 | + lock.lock(); | |
| 315 | + try { | |
| 316 | + return linkFirst(node); | |
| 317 | + } finally { | |
| 318 | + lock.unlock(); | |
| 319 | + } | |
| 320 | + } | |
| 321 | + | |
| 322 | + /** | |
| 323 | + * @throws NullPointerException {@inheritDoc} | |
| 324 | + */ | |
| 325 | + public boolean offerLast(E e) { | |
| 326 | + if (e == null) throw new NullPointerException(); | |
| 327 | + Node<E> node = new Node<E>(e); | |
| 328 | + final ReentrantLock lock = this.lock; | |
| 329 | + lock.lock(); | |
| 330 | + try { | |
| 331 | + return linkLast(node); | |
| 332 | + } finally { | |
| 333 | + lock.unlock(); | |
| 334 | + } | |
| 335 | + } | |
| 336 | + | |
| 337 | + /** | |
| 338 | + * @throws NullPointerException {@inheritDoc} | |
| 339 | + * @throws InterruptedException {@inheritDoc} | |
| 340 | + */ | |
| 341 | + public void putFirst(E e) throws InterruptedException { | |
| 342 | + if (e == null) throw new NullPointerException(); | |
| 343 | + Node<E> node = new Node<E>(e); | |
| 344 | + final ReentrantLock lock = this.lock; | |
| 345 | + lock.lock(); | |
| 346 | + try { | |
| 347 | + while (!linkFirst(node)) | |
| 348 | + notFull.await(); | |
| 349 | + } finally { | |
| 350 | + lock.unlock(); | |
| 351 | + } | |
| 352 | + } | |
| 353 | + | |
| 354 | + /** | |
| 355 | + * @throws NullPointerException {@inheritDoc} | |
| 356 | + * @throws InterruptedException {@inheritDoc} | |
| 357 | + */ | |
| 358 | + public void putLast(E e) throws InterruptedException { | |
| 359 | + if (e == null) throw new NullPointerException(); | |
| 360 | + Node<E> node = new Node<E>(e); | |
| 361 | + final ReentrantLock lock = this.lock; | |
| 362 | + lock.lock(); | |
| 363 | + try { | |
| 364 | + while (!linkLast(node)) | |
| 365 | + notFull.await(); | |
| 366 | + } finally { | |
| 367 | + lock.unlock(); | |
| 368 | + } | |
| 369 | + } | |
| 370 | + | |
| 371 | + /** | |
| 372 | + * @throws NullPointerException {@inheritDoc} | |
| 373 | + * @throws InterruptedException {@inheritDoc} | |
| 374 | + */ | |
| 375 | + public boolean offerFirst(E e, long timeout, TimeUnit unit) | |
| 376 | + throws InterruptedException { | |
| 377 | + if (e == null) throw new NullPointerException(); | |
| 378 | + Node<E> node = new Node<E>(e); | |
| 379 | + long nanos = unit.toNanos(timeout); | |
| 380 | + final ReentrantLock lock = this.lock; | |
| 381 | + lock.lockInterruptibly(); | |
| 382 | + try { | |
| 383 | + while (!linkFirst(node)) { | |
| 384 | + if (nanos <= 0) | |
| 385 | + return false; | |
| 386 | + nanos = notFull.awaitNanos(nanos); | |
| 387 | + } | |
| 388 | + return true; | |
| 389 | + } finally { | |
| 390 | + lock.unlock(); | |
| 391 | + } | |
| 392 | + } | |
| 393 | + | |
| 394 | + /** | |
| 395 | + * @throws NullPointerException {@inheritDoc} | |
| 396 | + * @throws InterruptedException {@inheritDoc} | |
| 397 | + */ | |
| 398 | + public boolean offerLast(E e, long timeout, TimeUnit unit) | |
| 399 | + throws InterruptedException { | |
| 400 | + if (e == null) throw new NullPointerException(); | |
| 401 | + Node<E> node = new Node<E>(e); | |
| 402 | + long nanos = unit.toNanos(timeout); | |
| 403 | + final ReentrantLock lock = this.lock; | |
| 404 | + lock.lockInterruptibly(); | |
| 405 | + try { | |
| 406 | + while (!linkLast(node)) { | |
| 407 | + if (nanos <= 0) | |
| 408 | + return false; | |
| 409 | + nanos = notFull.awaitNanos(nanos); | |
| 410 | + } | |
| 411 | + return true; | |
| 412 | + } finally { | |
| 413 | + lock.unlock(); | |
| 414 | + } | |
| 415 | + } | |
| 416 | + | |
| 417 | + /** | |
| 418 | + * @throws NoSuchElementException {@inheritDoc} | |
| 419 | + */ | |
| 420 | + public E removeFirst() { | |
| 421 | + E x = pollFirst(); | |
| 422 | + if (x == null) throw new NoSuchElementException(); | |
| 423 | + return x; | |
| 424 | + } | |
| 425 | + | |
| 426 | + /** | |
| 427 | + * @throws NoSuchElementException {@inheritDoc} | |
| 428 | + */ | |
| 429 | + public E removeLast() { | |
| 430 | + E x = pollLast(); | |
| 431 | + if (x == null) throw new NoSuchElementException(); | |
| 432 | + return x; | |
| 433 | + } | |
| 434 | + | |
| 435 | + public E pollFirst() { | |
| 436 | + final ReentrantLock lock = this.lock; | |
| 437 | + lock.lock(); | |
| 438 | + try { | |
| 439 | + return unlinkFirst(); | |
| 440 | + } finally { | |
| 441 | + lock.unlock(); | |
| 442 | + } | |
| 443 | + } | |
| 444 | + | |
| 445 | + public E pollLast() { | |
| 446 | + final ReentrantLock lock = this.lock; | |
| 447 | + lock.lock(); | |
| 448 | + try { | |
| 449 | + return unlinkLast(); | |
| 450 | + } finally { | |
| 451 | + lock.unlock(); | |
| 452 | + } | |
| 453 | + } | |
| 454 | + | |
| 455 | + public E takeFirst() throws InterruptedException { | |
| 456 | + final ReentrantLock lock = this.lock; | |
| 457 | + lock.lock(); | |
| 458 | + try { | |
| 459 | + E x; | |
| 460 | + while ( (x = unlinkFirst()) == null) | |
| 461 | + notEmpty.await(); | |
| 462 | + return x; | |
| 463 | + } finally { | |
| 464 | + lock.unlock(); | |
| 465 | + } | |
| 466 | + } | |
| 467 | + | |
| 468 | + public E takeLast() throws InterruptedException { | |
| 469 | + final ReentrantLock lock = this.lock; | |
| 470 | + lock.lock(); | |
| 471 | + try { | |
| 472 | + E x; | |
| 473 | + while ( (x = unlinkLast()) == null) | |
| 474 | + notEmpty.await(); | |
| 475 | + return x; | |
| 476 | + } finally { | |
| 477 | + lock.unlock(); | |
| 478 | + } | |
| 479 | + } | |
| 480 | + | |
| 481 | + public E pollFirst(long timeout, TimeUnit unit) | |
| 482 | + throws InterruptedException { | |
| 483 | + long nanos = unit.toNanos(timeout); | |
| 484 | + final ReentrantLock lock = this.lock; | |
| 485 | + lock.lockInterruptibly(); | |
| 486 | + try { | |
| 487 | + E x; | |
| 488 | + while ( (x = unlinkFirst()) == null) { | |
| 489 | + if (nanos <= 0) | |
| 490 | + return null; | |
| 491 | + nanos = notEmpty.awaitNanos(nanos); | |
| 492 | + } | |
| 493 | + return x; | |
| 494 | + } finally { | |
| 495 | + lock.unlock(); | |
| 496 | + } | |
| 497 | + } | |
| 498 | + | |
| 499 | + public E pollLast(long timeout, TimeUnit unit) | |
| 500 | + throws InterruptedException { | |
| 501 | + long nanos = unit.toNanos(timeout); | |
| 502 | + final ReentrantLock lock = this.lock; | |
| 503 | + lock.lockInterruptibly(); | |
| 504 | + try { | |
| 505 | + E x; | |
| 506 | + while ( (x = unlinkLast()) == null) { | |
| 507 | + if (nanos <= 0) | |
| 508 | + return null; | |
| 509 | + nanos = notEmpty.awaitNanos(nanos); | |
| 510 | + } | |
| 511 | + return x; | |
| 512 | + } finally { | |
| 513 | + lock.unlock(); | |
| 514 | + } | |
| 515 | + } | |
| 516 | + | |
| 517 | + /** | |
| 518 | + * @throws NoSuchElementException {@inheritDoc} | |
| 519 | + */ | |
| 520 | + public E getFirst() { | |
| 521 | + E x = peekFirst(); | |
| 522 | + if (x == null) throw new NoSuchElementException(); | |
| 523 | + return x; | |
| 524 | + } | |
| 525 | + | |
| 526 | + /** | |
| 527 | + * @throws NoSuchElementException {@inheritDoc} | |
| 528 | + */ | |
| 529 | + public E getLast() { | |
| 530 | + E x = peekLast(); | |
| 531 | + if (x == null) throw new NoSuchElementException(); | |
| 532 | + return x; | |
| 533 | + } | |
| 534 | + | |
| 535 | + public E peekFirst() { | |
| 536 | + final ReentrantLock lock = this.lock; | |
| 537 | + lock.lock(); | |
| 538 | + try { | |
| 539 | + return (first == null) ? null : first.item; | |
| 540 | + } finally { | |
| 541 | + lock.unlock(); | |
| 542 | + } | |
| 543 | + } | |
| 544 | + | |
| 545 | + public E peekLast() { | |
| 546 | + final ReentrantLock lock = this.lock; | |
| 547 | + lock.lock(); | |
| 548 | + try { | |
| 549 | + return (last == null) ? null : last.item; | |
| 550 | + } finally { | |
| 551 | + lock.unlock(); | |
| 552 | + } | |
| 553 | + } | |
| 554 | + | |
| 555 | + public boolean removeFirstOccurrence(Object o) { | |
| 556 | + if (o == null) return false; | |
| 557 | + final ReentrantLock lock = this.lock; | |
| 558 | + lock.lock(); | |
| 559 | + try { | |
| 560 | + for (Node<E> p = first; p != null; p = p.next) { | |
| 561 | + if (o.equals(p.item)) { | |
| 562 | + unlink(p); | |
| 563 | + return true; | |
| 564 | + } | |
| 565 | + } | |
| 566 | + return false; | |
| 567 | + } finally { | |
| 568 | + lock.unlock(); | |
| 569 | + } | |
| 570 | + } | |
| 571 | + | |
| 572 | + public boolean removeLastOccurrence(Object o) { | |
| 573 | + if (o == null) return false; | |
| 574 | + final ReentrantLock lock = this.lock; | |
| 575 | + lock.lock(); | |
| 576 | + try { | |
| 577 | + for (Node<E> p = last; p != null; p = p.prev) { | |
| 578 | + if (o.equals(p.item)) { | |
| 579 | + unlink(p); | |
| 580 | + return true; | |
| 581 | + } | |
| 582 | + } | |
| 583 | + return false; | |
| 584 | + } finally { | |
| 585 | + lock.unlock(); | |
| 586 | + } | |
| 587 | + } | |
| 588 | + | |
| 589 | + // BlockingQueue methods | |
| 590 | + | |
| 591 | + /** | |
| 592 | + * Inserts the specified element at the end of this deque unless it would | |
| 593 | + * violate capacity restrictions. When using a capacity-restricted deque, | |
| 594 | + * it is generally preferable to use method {@link #offer offer}. | |
| 595 | + * | |
| 596 | + * <p>This method is equivalent to {@link #addLast}. | |
| 597 | + * | |
| 598 | + * @throws IllegalStateException if the element cannot be added at this | |
| 599 | + * time due to capacity restrictions | |
| 600 | + * @throws NullPointerException if the specified element is null | |
| 601 | + */ | |
| 602 | + public boolean add(E e) { | |
| 603 | + addLast(e); | |
| 604 | + return true; | |
| 605 | + } | |
| 606 | + | |
| 607 | + /** | |
| 608 | + * @throws NullPointerException if the specified element is null | |
| 609 | + */ | |
| 610 | + public boolean offer(E e) { | |
| 611 | + return offerLast(e); | |
| 612 | + } | |
| 613 | + | |
| 614 | + /** | |
| 615 | + * @throws NullPointerException {@inheritDoc} | |
| 616 | + * @throws InterruptedException {@inheritDoc} | |
| 617 | + */ | |
| 618 | + public void put(E e) throws InterruptedException { | |
| 619 | + putLast(e); | |
| 620 | + } | |
| 621 | + | |
| 622 | + /** | |
| 623 | + * @throws NullPointerException {@inheritDoc} | |
| 624 | + * @throws InterruptedException {@inheritDoc} | |
| 625 | + */ | |
| 626 | + public boolean offer(E e, long timeout, TimeUnit unit) | |
| 627 | + throws InterruptedException { | |
| 628 | + return offerLast(e, timeout, unit); | |
| 629 | + } | |
| 630 | + | |
| 631 | + /** | |
| 632 | + * Retrieves and removes the head of the queue represented by this deque. | |
| 633 | + * This method differs from {@link #poll poll} only in that it throws an | |
| 634 | + * exception if this deque is empty. | |
| 635 | + * | |
| 636 | + * <p>This method is equivalent to {@link #removeFirst() removeFirst}. | |
| 637 | + * | |
| 638 | + * @return the head of the queue represented by this deque | |
| 639 | + * @throws NoSuchElementException if this deque is empty | |
| 640 | + */ | |
| 641 | + public E remove() { | |
| 642 | + return removeFirst(); | |
| 643 | + } | |
| 644 | + | |
| 645 | + public E poll() { | |
| 646 | + return pollFirst(); | |
| 647 | + } | |
| 648 | + | |
| 649 | + public E take() throws InterruptedException { | |
| 650 | + return takeFirst(); | |
| 651 | + } | |
| 652 | + | |
| 653 | + public E poll(long timeout, TimeUnit unit) throws InterruptedException { | |
| 654 | + return pollFirst(timeout, unit); | |
| 655 | + } | |
| 656 | + | |
| 657 | + /** | |
| 658 | + * Retrieves, but does not remove, the head of the queue represented by | |
| 659 | + * this deque. This method differs from {@link #peek peek} only in that | |
| 660 | + * it throws an exception if this deque is empty. | |
| 661 | + * | |
| 662 | + * <p>This method is equivalent to {@link #getFirst() getFirst}. | |
| 663 | + * | |
| 664 | + * @return the head of the queue represented by this deque | |
| 665 | + * @throws NoSuchElementException if this deque is empty | |
| 666 | + */ | |
| 667 | + public E element() { | |
| 668 | + return getFirst(); | |
| 669 | + } | |
| 670 | + | |
| 671 | + public E peek() { | |
| 672 | + return peekFirst(); | |
| 673 | + } | |
| 674 | + | |
| 675 | + /** | |
| 676 | + * Returns the number of additional elements that this deque can ideally | |
| 677 | + * (in the absence of memory or resource constraints) accept without | |
| 678 | + * blocking. This is always equal to the initial capacity of this deque | |
| 679 | + * less the current {@code size} of this deque. | |
| 680 | + * | |
| 681 | + * <p>Note that you <em>cannot</em> always tell if an attempt to insert | |
| 682 | + * an element will succeed by inspecting {@code remainingCapacity} | |
| 683 | + * because it may be the case that another thread is about to | |
| 684 | + * insert or remove an element. | |
| 685 | + */ | |
| 686 | + public int remainingCapacity() { | |
| 687 | + final ReentrantLock lock = this.lock; | |
| 688 | + lock.lock(); | |
| 689 | + try { | |
| 690 | + return capacity - count; | |
| 691 | + } finally { | |
| 692 | + lock.unlock(); | |
| 693 | + } | |
| 694 | + } | |
| 695 | + | |
| 696 | + /** | |
| 697 | + * @throws UnsupportedOperationException {@inheritDoc} | |
| 698 | + * @throws ClassCastException {@inheritDoc} | |
| 699 | + * @throws NullPointerException {@inheritDoc} | |
| 700 | + * @throws IllegalArgumentException {@inheritDoc} | |
| 701 | + */ | |
| 702 | + public int drainTo(Collection<? super E> c) { | |
| 703 | + return drainTo(c, Integer.MAX_VALUE); | |
| 704 | + } | |
| 705 | + | |
| 706 | + /** | |
| 707 | + * @throws UnsupportedOperationException {@inheritDoc} | |
| 708 | + * @throws ClassCastException {@inheritDoc} | |
| 709 | + * @throws NullPointerException {@inheritDoc} | |
| 710 | + * @throws IllegalArgumentException {@inheritDoc} | |
| 711 | + */ | |
| 712 | + public int drainTo(Collection<? super E> c, int maxElements) { | |
| 713 | + if (c == null) | |
| 714 | + throw new NullPointerException(); | |
| 715 | + if (c == this) | |
| 716 | + throw new IllegalArgumentException(); | |
| 717 | + final ReentrantLock lock = this.lock; | |
| 718 | + lock.lock(); | |
| 719 | + try { | |
| 720 | + int n = Math.min(maxElements, count); | |
| 721 | + for (int i = 0; i < n; i++) { | |
| 722 | + c.add(first.item); // In this order, in case add() throws. | |
| 723 | + unlinkFirst(); | |
| 724 | + } | |
| 725 | + return n; | |
| 726 | + } finally { | |
| 727 | + lock.unlock(); | |
| 728 | + } | |
| 729 | + } | |
| 730 | + | |
| 731 | + // Stack methods | |
| 732 | + | |
| 733 | + /** | |
| 734 | + * @throws IllegalStateException {@inheritDoc} | |
| 735 | + * @throws NullPointerException {@inheritDoc} | |
| 736 | + */ | |
| 737 | + public void push(E e) { | |
| 738 | + addFirst(e); | |
| 739 | + } | |
| 740 | + | |
| 741 | + /** | |
| 742 | + * @throws NoSuchElementException {@inheritDoc} | |
| 743 | + */ | |
| 744 | + public E pop() { | |
| 745 | + return removeFirst(); | |
| 746 | + } | |
| 747 | + | |
| 748 | + // Collection methods | |
| 749 | + | |
| 750 | + /** | |
| 751 | + * Removes the first occurrence of the specified element from this deque. | |
| 752 | + * If the deque does not contain the element, it is unchanged. | |
| 753 | + * More formally, removes the first element {@code e} such that | |
| 754 | + * {@code o.equals(e)} (if such an element exists). | |
| 755 | + * Returns {@code true} if this deque contained the specified element | |
| 756 | + * (or equivalently, if this deque changed as a result of the call). | |
| 757 | + * | |
| 758 | + * <p>This method is equivalent to | |
| 759 | + * {@link #removeFirstOccurrence(Object) removeFirstOccurrence}. | |
| 760 | + * | |
| 761 | + * @param o element to be removed from this deque, if present | |
| 762 | + * @return {@code true} if this deque changed as a result of the call | |
| 763 | + */ | |
| 764 | + public boolean remove(Object o) { | |
| 765 | + return removeFirstOccurrence(o); | |
| 766 | + } | |
| 767 | + | |
| 768 | + /** | |
| 769 | + * Returns the number of elements in this deque. | |
| 770 | + * | |
| 771 | + * @return the number of elements in this deque | |
| 772 | + */ | |
| 773 | + public int size() { | |
| 774 | + final ReentrantLock lock = this.lock; | |
| 775 | + lock.lock(); | |
| 776 | + try { | |
| 777 | + return count; | |
| 778 | + } finally { | |
| 779 | + lock.unlock(); | |
| 780 | + } | |
| 781 | + } | |
| 782 | + | |
| 783 | + /** | |
| 784 | + * Returns {@code true} if this deque contains the specified element. | |
| 785 | + * More formally, returns {@code true} if and only if this deque contains | |
| 786 | + * at least one element {@code e} such that {@code o.equals(e)}. | |
| 787 | + * | |
| 788 | + * @param o object to be checked for containment in this deque | |
| 789 | + * @return {@code true} if this deque contains the specified element | |
| 790 | + */ | |
| 791 | + public boolean contains(Object o) { | |
| 792 | + if (o == null) return false; | |
| 793 | + final ReentrantLock lock = this.lock; | |
| 794 | + lock.lock(); | |
| 795 | + try { | |
| 796 | + for (Node<E> p = first; p != null; p = p.next) | |
| 797 | + if (o.equals(p.item)) | |
| 798 | + return true; | |
| 799 | + return false; | |
| 800 | + } finally { | |
| 801 | + lock.unlock(); | |
| 802 | + } | |
| 803 | + } | |
| 804 | + | |
| 805 | + /* | |
| 806 | + * TODO: Add support for more efficient bulk operations. | |
| 807 | + * | |
| 808 | + * We don't want to acquire the lock for every iteration, but we | |
| 809 | + * also want other threads a chance to interact with the | |
| 810 | + * collection, especially when count is close to capacity. | |
| 811 | + */ | |
| 812 | + | |
| 813 | +// /** | |
| 814 | +// * Adds all of the elements in the specified collection to this | |
| 815 | +// * queue. Attempts to addAll of a queue to itself result in | |
| 816 | +// * {@code IllegalArgumentException}. Further, the behavior of | |
| 817 | +// * this operation is undefined if the specified collection is | |
| 818 | +// * modified while the operation is in progress. | |
| 819 | +// * | |
| 820 | +// * @param c collection containing elements to be added to this queue | |
| 821 | +// * @return {@code true} if this queue changed as a result of the call | |
| 822 | +// * @throws ClassCastException {@inheritDoc} | |
| 823 | +// * @throws NullPointerException {@inheritDoc} | |
| 824 | +// * @throws IllegalArgumentException {@inheritDoc} | |
| 825 | +// * @throws IllegalStateException {@inheritDoc} | |
| 826 | +// * @see #add(Object) | |
| 827 | +// */ | |
| 828 | +// public boolean addAll(Collection<? extends E> c) { | |
| 829 | +// if (c == null) | |
| 830 | +// throw new NullPointerException(); | |
| 831 | +// if (c == this) | |
| 832 | +// throw new IllegalArgumentException(); | |
| 833 | +// final ReentrantLock lock = this.lock; | |
| 834 | +// lock.lock(); | |
| 835 | +// try { | |
| 836 | +// boolean modified = false; | |
| 837 | +// for (E e : c) | |
| 838 | +// if (linkLast(e)) | |
| 839 | +// modified = true; | |
| 840 | +// return modified; | |
| 841 | +// } finally { | |
| 842 | +// lock.unlock(); | |
| 843 | +// } | |
| 844 | +// } | |
| 845 | + | |
| 846 | + /** | |
| 847 | + * Returns an array containing all of the elements in this deque, in | |
| 848 | + * proper sequence (from first to last element). | |
| 849 | + * | |
| 850 | + * <p>The returned array will be "safe" in that no references to it are | |
| 851 | + * maintained by this deque. (In other words, this method must allocate | |
| 852 | + * a new array). The caller is thus free to modify the returned array. | |
| 853 | + * | |
| 854 | + * <p>This method acts as bridge between array-based and collection-based | |
| 855 | + * APIs. | |
| 856 | + * | |
| 857 | + * @return an array containing all of the elements in this deque | |
| 858 | + */ | |
| 859 | + public Object[] toArray() { | |
| 860 | + final ReentrantLock lock = this.lock; | |
| 861 | + lock.lock(); | |
| 862 | + try { | |
| 863 | + Object[] a = new Object[count]; | |
| 864 | + int k = 0; | |
| 865 | + for (Node<E> p = first; p != null; p = p.next) | |
| 866 | + a[k++] = p.item; | |
| 867 | + return a; | |
| 868 | + } finally { | |
| 869 | + lock.unlock(); | |
| 870 | + } | |
| 871 | + } | |
| 872 | + | |
| 873 | + /** | |
| 874 | + * Returns an array containing all of the elements in this deque, in | |
| 875 | + * proper sequence; the runtime type of the returned array is that of | |
| 876 | + * the specified array. If the deque fits in the specified array, it | |
| 877 | + * is returned therein. Otherwise, a new array is allocated with the | |
| 878 | + * runtime type of the specified array and the size of this deque. | |
| 879 | + * | |
| 880 | + * <p>If this deque fits in the specified array with room to spare | |
| 881 | + * (i.e., the array has more elements than this deque), the element in | |
| 882 | + * the array immediately following the end of the deque is set to | |
| 883 | + * {@code null}. | |
| 884 | + * | |
| 885 | + * <p>Like the {@link #toArray()} method, this method acts as bridge between | |
| 886 | + * array-based and collection-based APIs. Further, this method allows | |
| 887 | + * precise control over the runtime type of the output array, and may, | |
| 888 | + * under certain circumstances, be used to save allocation costs. | |
| 889 | + * | |
| 890 | + * <p>Suppose {@code x} is a deque known to contain only strings. | |
| 891 | + * The following code can be used to dump the deque into a newly | |
| 892 | + * allocated array of {@code String}: | |
| 893 | + * | |
| 894 | + * <pre> | |
| 895 | + * String[] y = x.toArray(new String[0]);</pre> | |
| 896 | + * | |
| 897 | + * Note that {@code toArray(new Object[0])} is identical in function to | |
| 898 | + * {@code toArray()}. | |
| 899 | + * | |
| 900 | + * @param a the array into which the elements of the deque are to | |
| 901 | + * be stored, if it is big enough; otherwise, a new array of the | |
| 902 | + * same runtime type is allocated for this purpose | |
| 903 | + * @return an array containing all of the elements in this deque | |
| 904 | + * @throws ArrayStoreException if the runtime type of the specified array | |
| 905 | + * is not a supertype of the runtime type of every element in | |
| 906 | + * this deque | |
| 907 | + * @throws NullPointerException if the specified array is null | |
| 908 | + */ | |
| 909 | + @SuppressWarnings("unchecked") | |
| 910 | + public <T> T[] toArray(T[] a) { | |
| 911 | + final ReentrantLock lock = this.lock; | |
| 912 | + lock.lock(); | |
| 913 | + try { | |
| 914 | + if (a.length < count) | |
| 915 | + a = (T[])java.lang.reflect.Array.newInstance | |
| 916 | + (a.getClass().getComponentType(), count); | |
| 917 | + | |
| 918 | + int k = 0; | |
| 919 | + for (Node<E> p = first; p != null; p = p.next) | |
| 920 | + a[k++] = (T)p.item; | |
| 921 | + if (a.length > k) | |
| 922 | + a[k] = null; | |
| 923 | + return a; | |
| 924 | + } finally { | |
| 925 | + lock.unlock(); | |
| 926 | + } | |
| 927 | + } | |
| 928 | + | |
| 929 | + public String toString() { | |
| 930 | + final ReentrantLock lock = this.lock; | |
| 931 | + lock.lock(); | |
| 932 | + try { | |
| 933 | + Node<E> p = first; | |
| 934 | + if (p == null) | |
| 935 | + return "[]"; | |
| 936 | + | |
| 937 | + StringBuilder sb = new StringBuilder(); | |
| 938 | + sb.append('['); | |
| 939 | + for (;;) { | |
| 940 | + E e = p.item; | |
| 941 | + sb.append(e == this ? "(this Collection)" : e); | |
| 942 | + p = p.next; | |
| 943 | + if (p == null) | |
| 944 | + return sb.append(']').toString(); | |
| 945 | + sb.append(',').append(' '); | |
| 946 | + } | |
| 947 | + } finally { | |
| 948 | + lock.unlock(); | |
| 949 | + } | |
| 950 | + } | |
| 951 | + | |
| 952 | + /** | |
| 953 | + * Atomically removes all of the elements from this deque. | |
| 954 | + * The deque will be empty after this call returns. | |
| 955 | + */ | |
| 956 | + public void clear() { | |
| 957 | + final ReentrantLock lock = this.lock; | |
| 958 | + lock.lock(); | |
| 959 | + try { | |
| 960 | + for (Node<E> f = first; f != null; ) { | |
| 961 | + f.item = null; | |
| 962 | + Node<E> n = f.next; | |
| 963 | + f.prev = null; | |
| 964 | + f.next = null; | |
| 965 | + f = n; | |
| 966 | + } | |
| 967 | + first = last = null; | |
| 968 | + count = 0; | |
| 969 | + notFull.signalAll(); | |
| 970 | + } finally { | |
| 971 | + lock.unlock(); | |
| 972 | + } | |
| 973 | + } | |
| 974 | + | |
| 975 | + /** | |
| 976 | + * Returns an iterator over the elements in this deque in proper sequence. | |
| 977 | + * The elements will be returned in order from first (head) to last (tail). | |
| 978 | + * | |
| 979 | + * <p>The returned iterator is a "weakly consistent" iterator that | |
| 980 | + * will never throw {@link java.util.ConcurrentModificationException | |
| 981 | + * ConcurrentModificationException}, and guarantees to traverse | |
| 982 | + * elements as they existed upon construction of the iterator, and | |
| 983 | + * may (but is not guaranteed to) reflect any modifications | |
| 984 | + * subsequent to construction. | |
| 985 | + * | |
| 986 | + * @return an iterator over the elements in this deque in proper sequence | |
| 987 | + */ | |
| 988 | + public Iterator<E> iterator() { | |
| 989 | + return new Itr(); | |
| 990 | + } | |
| 991 | + | |
| 992 | + /** | |
| 993 | + * Returns an iterator over the elements in this deque in reverse | |
| 994 | + * sequential order. The elements will be returned in order from | |
| 995 | + * last (tail) to first (head). | |
| 996 | + * | |
| 997 | + * <p>The returned iterator is a "weakly consistent" iterator that | |
| 998 | + * will never throw {@link java.util.ConcurrentModificationException | |
| 999 | + * ConcurrentModificationException}, and guarantees to traverse | |
| 1000 | + * elements as they existed upon construction of the iterator, and | |
| 1001 | + * may (but is not guaranteed to) reflect any modifications | |
| 1002 | + * subsequent to construction. | |
| 1003 | + * | |
| 1004 | + * @return an iterator over the elements in this deque in reverse order | |
| 1005 | + */ | |
| 1006 | + public Iterator<E> descendingIterator() { | |
| 1007 | + return new DescendingItr(); | |
| 1008 | + } | |
| 1009 | + | |
| 1010 | + /** | |
| 1011 | + * Base class for Iterators for LinkedBlockingDeque | |
| 1012 | + */ | |
| 1013 | + private abstract class AbstractItr implements Iterator<E> { | |
| 1014 | + /** | |
| 1015 | + * The next node to return in next() | |
| 1016 | + */ | |
| 1017 | + Node<E> next; | |
| 1018 | + | |
| 1019 | + /** | |
| 1020 | + * nextItem holds on to item fields because once we claim that | |
| 1021 | + * an element exists in hasNext(), we must return item read | |
| 1022 | + * under lock (in advance()) even if it was in the process of | |
| 1023 | + * being removed when hasNext() was called. | |
| 1024 | + */ | |
| 1025 | + E nextItem; | |
| 1026 | + | |
| 1027 | + /** | |
| 1028 | + * Node returned by most recent call to next. Needed by remove. | |
| 1029 | + * Reset to null if this element is deleted by a call to remove. | |
| 1030 | + */ | |
| 1031 | + private Node<E> lastRet; | |
| 1032 | + | |
| 1033 | + abstract Node<E> firstNode(); | |
| 1034 | + abstract Node<E> nextNode(Node<E> n); | |
| 1035 | + | |
| 1036 | + AbstractItr() { | |
| 1037 | + // set to initial position | |
| 1038 | + final ReentrantLock lock = LinkedBlockingDeque.this.lock; | |
| 1039 | + lock.lock(); | |
| 1040 | + try { | |
| 1041 | + next = firstNode(); | |
| 1042 | + nextItem = (next == null) ? null : next.item; | |
| 1043 | + } finally { | |
| 1044 | + lock.unlock(); | |
| 1045 | + } | |
| 1046 | + } | |
| 1047 | + | |
| 1048 | + /** | |
| 1049 | + * Returns the successor node of the given non-null, but | |
| 1050 | + * possibly previously deleted, node. | |
| 1051 | + */ | |
| 1052 | + private Node<E> succ(Node<E> n) { | |
| 1053 | + // Chains of deleted nodes ending in null or self-links | |
| 1054 | + // are possible if multiple interior nodes are removed. | |
| 1055 | + for (;;) { | |
| 1056 | + Node<E> s = nextNode(n); | |
| 1057 | + if (s == null) | |
| 1058 | + return null; | |
| 1059 | + else if (s.item != null) | |
| 1060 | + return s; | |
| 1061 | + else if (s == n) | |
| 1062 | + return firstNode(); | |
| 1063 | + else | |
| 1064 | + n = s; | |
| 1065 | + } | |
| 1066 | + } | |
| 1067 | + | |
| 1068 | + /** | |
| 1069 | + * Advances next. | |
| 1070 | + */ | |
| 1071 | + void advance() { | |
| 1072 | + final ReentrantLock lock = LinkedBlockingDeque.this.lock; | |
| 1073 | + lock.lock(); | |
| 1074 | + try { | |
| 1075 | + // assert next != null; | |
| 1076 | + next = succ(next); | |
| 1077 | + nextItem = (next == null) ? null : next.item; | |
| 1078 | + } finally { | |
| 1079 | + lock.unlock(); | |
| 1080 | + } | |
| 1081 | + } | |
| 1082 | + | |
| 1083 | + public boolean hasNext() { | |
| 1084 | + return next != null; | |
| 1085 | + } | |
| 1086 | + | |
| 1087 | + public E next() { | |
| 1088 | + if (next == null) | |
| 1089 | + throw new NoSuchElementException(); | |
| 1090 | + lastRet = next; | |
| 1091 | + E x = nextItem; | |
| 1092 | + advance(); | |
| 1093 | + return x; | |
| 1094 | + } | |
| 1095 | + | |
| 1096 | + public void remove() { | |
| 1097 | + Node<E> n = lastRet; | |
| 1098 | + if (n == null) | |
| 1099 | + throw new IllegalStateException(); | |
| 1100 | + lastRet = null; | |
| 1101 | + final ReentrantLock lock = LinkedBlockingDeque.this.lock; | |
| 1102 | + lock.lock(); | |
| 1103 | + try { | |
| 1104 | + if (n.item != null) | |
| 1105 | + unlink(n); | |
| 1106 | + } finally { | |
| 1107 | + lock.unlock(); | |
| 1108 | + } | |
| 1109 | + } | |
| 1110 | + } | |
| 1111 | + | |
| 1112 | + /** Forward iterator */ | |
| 1113 | + private class Itr extends AbstractItr { | |
| 1114 | + Node<E> firstNode() { return first; } | |
| 1115 | + Node<E> nextNode(Node<E> n) { return n.next; } | |
| 1116 | + } | |
| 1117 | + | |
| 1118 | + /** Descending iterator */ | |
| 1119 | + private class DescendingItr extends AbstractItr { | |
| 1120 | + Node<E> firstNode() { return last; } | |
| 1121 | + Node<E> nextNode(Node<E> n) { return n.prev; } | |
| 1122 | + } | |
| 1123 | + | |
| 1124 | + /** | |
| 1125 | + * Save the state of this deque to a stream (that is, serialize it). | |
| 1126 | + * | |
| 1127 | + * @serialData The capacity (int), followed by elements (each an | |
| 1128 | + * {@code Object}) in the proper order, followed by a null | |
| 1129 | + * @param s the stream | |
| 1130 | + */ | |
| 1131 | + private void writeObject(java.io.ObjectOutputStream s) | |
| 1132 | + throws java.io.IOException { | |
| 1133 | + final ReentrantLock lock = this.lock; | |
| 1134 | + lock.lock(); | |
| 1135 | + try { | |
| 1136 | + // Write out capacity and any hidden stuff | |
| 1137 | + s.defaultWriteObject(); | |
| 1138 | + // Write out all elements in the proper order. | |
| 1139 | + for (Node<E> p = first; p != null; p = p.next) | |
| 1140 | + s.writeObject(p.item); | |
| 1141 | + // Use trailing null as sentinel | |
| 1142 | + s.writeObject(null); | |
| 1143 | + } finally { | |
| 1144 | + lock.unlock(); | |
| 1145 | + } | |
| 1146 | + } | |
| 1147 | + | |
| 1148 | + /** | |
| 1149 | + * Reconstitute this deque from a stream (that is, | |
| 1150 | + * deserialize it). | |
| 1151 | + * @param s the stream | |
| 1152 | + */ | |
| 1153 | + private void readObject(java.io.ObjectInputStream s) | |
| 1154 | + throws java.io.IOException, ClassNotFoundException { | |
| 1155 | + s.defaultReadObject(); | |
| 1156 | + count = 0; | |
| 1157 | + first = null; | |
| 1158 | + last = null; | |
| 1159 | + // Read in all elements and place in queue | |
| 1160 | + for (;;) { | |
| 1161 | + @SuppressWarnings("unchecked") | |
| 1162 | + E item = (E)s.readObject(); | |
| 1163 | + if (item == null) | |
| 1164 | + break; | |
| 1165 | + add(item); | |
| 1166 | + } | |
| 1167 | + } | |
| 1168 | + | |
| 1169 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.decode; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.graphics.BitmapFactory; | |
| 20 | +import android.graphics.BitmapFactory.Options; | |
| 21 | +import android.graphics.Matrix; | |
| 22 | +import android.media.ExifInterface; | |
| 23 | +import com.nostra13.universalimageloader.core.assist.ImageScaleType; | |
| 24 | +import com.nostra13.universalimageloader.core.assist.ImageSize; | |
| 25 | +import com.nostra13.universalimageloader.core.download.ImageDownloader.Scheme; | |
| 26 | +import com.nostra13.universalimageloader.utils.ImageSizeUtils; | |
| 27 | +import com.nostra13.universalimageloader.utils.IoUtils; | |
| 28 | +import com.nostra13.universalimageloader.utils.L; | |
| 29 | + | |
| 30 | +import java.io.IOException; | |
| 31 | +import java.io.InputStream; | |
| 32 | + | |
| 33 | +/** | |
| 34 | + * Decodes images to {@link Bitmap}, scales them to needed size | |
| 35 | + * | |
| 36 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 37 | + * @see ImageDecodingInfo | |
| 38 | + * @since 1.8.3 | |
| 39 | + */ | |
| 40 | +public class BaseImageDecoder implements ImageDecoder { | |
| 41 | + | |
| 42 | + protected static final String LOG_SUBSAMPLE_IMAGE = "Subsample original image (%1$s) to %2$s (scale = %3$d) [%4$s]"; | |
| 43 | + protected static final String LOG_SCALE_IMAGE = "Scale subsampled image (%1$s) to %2$s (scale = %3$.5f) [%4$s]"; | |
| 44 | + protected static final String LOG_ROTATE_IMAGE = "Rotate image on %1$d\u00B0 [%2$s]"; | |
| 45 | + protected static final String LOG_FLIP_IMAGE = "Flip image horizontally [%s]"; | |
| 46 | + protected static final String ERROR_NO_IMAGE_STREAM = "No stream for image [%s]"; | |
| 47 | + protected static final String ERROR_CANT_DECODE_IMAGE = "Image can't be decoded [%s]"; | |
| 48 | + | |
| 49 | + protected final boolean loggingEnabled; | |
| 50 | + | |
| 51 | + /** | |
| 52 | + * @param loggingEnabled Whether debug logs will be written to LogCat. Usually should match {@link | |
| 53 | + * com.nostra13.universalimageloader.core.ImageLoaderConfiguration.Builder#writeDebugLogs() | |
| 54 | + * ImageLoaderConfiguration.writeDebugLogs()} | |
| 55 | + */ | |
| 56 | + public BaseImageDecoder(boolean loggingEnabled) { | |
| 57 | + this.loggingEnabled = loggingEnabled; | |
| 58 | + } | |
| 59 | + | |
| 60 | + /** | |
| 61 | + * Decodes image from URI into {@link Bitmap}. Image is scaled close to incoming {@linkplain ImageSize target size} | |
| 62 | + * during decoding (depend on incoming parameters). | |
| 63 | + * | |
| 64 | + * @param decodingInfo Needed data for decoding image | |
| 65 | + * @return Decoded bitmap | |
| 66 | + * @throws IOException if some I/O exception occurs during image reading | |
| 67 | + * @throws UnsupportedOperationException if image URI has unsupported scheme(protocol) | |
| 68 | + */ | |
| 69 | + @Override | |
| 70 | + public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException { | |
| 71 | + Bitmap decodedBitmap; | |
| 72 | + ImageFileInfo imageInfo; | |
| 73 | + | |
| 74 | + InputStream imageStream = getImageStream(decodingInfo); | |
| 75 | + if (imageStream == null) { | |
| 76 | + L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey()); | |
| 77 | + return null; | |
| 78 | + } | |
| 79 | + try { | |
| 80 | + imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo); | |
| 81 | + imageStream = resetStream(imageStream, decodingInfo); | |
| 82 | + Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo); | |
| 83 | + decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions); | |
| 84 | + } finally { | |
| 85 | + IoUtils.closeSilently(imageStream); | |
| 86 | + } | |
| 87 | + | |
| 88 | + if (decodedBitmap == null) { | |
| 89 | + L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey()); | |
| 90 | + } else { | |
| 91 | + decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation, | |
| 92 | + imageInfo.exif.flipHorizontal); | |
| 93 | + } | |
| 94 | + return decodedBitmap; | |
| 95 | + } | |
| 96 | + | |
| 97 | + protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException { | |
| 98 | + return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader()); | |
| 99 | + } | |
| 100 | + | |
| 101 | + protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo) | |
| 102 | + throws IOException { | |
| 103 | + Options options = new Options(); | |
| 104 | + options.inJustDecodeBounds = true; | |
| 105 | + BitmapFactory.decodeStream(imageStream, null, options); | |
| 106 | + | |
| 107 | + ExifInfo exif; | |
| 108 | + String imageUri = decodingInfo.getImageUri(); | |
| 109 | + if (decodingInfo.shouldConsiderExifParams() && canDefineExifParams(imageUri, options.outMimeType)) { | |
| 110 | + exif = defineExifOrientation(imageUri); | |
| 111 | + } else { | |
| 112 | + exif = new ExifInfo(); | |
| 113 | + } | |
| 114 | + return new ImageFileInfo(new ImageSize(options.outWidth, options.outHeight, exif.rotation), exif); | |
| 115 | + } | |
| 116 | + | |
| 117 | + private boolean canDefineExifParams(String imageUri, String mimeType) { | |
| 118 | + return "image/jpeg".equalsIgnoreCase(mimeType) && (Scheme.ofUri(imageUri) == Scheme.FILE); | |
| 119 | + } | |
| 120 | + | |
| 121 | + protected ExifInfo defineExifOrientation(String imageUri) { | |
| 122 | + int rotation = 0; | |
| 123 | + boolean flip = false; | |
| 124 | + try { | |
| 125 | + ExifInterface exif = new ExifInterface(Scheme.FILE.crop(imageUri)); | |
| 126 | + int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); | |
| 127 | + switch (exifOrientation) { | |
| 128 | + case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: | |
| 129 | + flip = true; | |
| 130 | + case ExifInterface.ORIENTATION_NORMAL: | |
| 131 | + rotation = 0; | |
| 132 | + break; | |
| 133 | + case ExifInterface.ORIENTATION_TRANSVERSE: | |
| 134 | + flip = true; | |
| 135 | + case ExifInterface.ORIENTATION_ROTATE_90: | |
| 136 | + rotation = 90; | |
| 137 | + break; | |
| 138 | + case ExifInterface.ORIENTATION_FLIP_VERTICAL: | |
| 139 | + flip = true; | |
| 140 | + case ExifInterface.ORIENTATION_ROTATE_180: | |
| 141 | + rotation = 180; | |
| 142 | + break; | |
| 143 | + case ExifInterface.ORIENTATION_TRANSPOSE: | |
| 144 | + flip = true; | |
| 145 | + case ExifInterface.ORIENTATION_ROTATE_270: | |
| 146 | + rotation = 270; | |
| 147 | + break; | |
| 148 | + } | |
| 149 | + } catch (IOException e) { | |
| 150 | + L.w("Can't read EXIF tags from file [%s]", imageUri); | |
| 151 | + } | |
| 152 | + return new ExifInfo(rotation, flip); | |
| 153 | + } | |
| 154 | + | |
| 155 | + protected Options prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo) { | |
| 156 | + ImageScaleType scaleType = decodingInfo.getImageScaleType(); | |
| 157 | + int scale; | |
| 158 | + if (scaleType == ImageScaleType.NONE) { | |
| 159 | + scale = 1; | |
| 160 | + } else if (scaleType == ImageScaleType.NONE_SAFE) { | |
| 161 | + scale = ImageSizeUtils.computeMinImageSampleSize(imageSize); | |
| 162 | + } else { | |
| 163 | + ImageSize targetSize = decodingInfo.getTargetSize(); | |
| 164 | + boolean powerOf2 = scaleType == ImageScaleType.IN_SAMPLE_POWER_OF_2; | |
| 165 | + scale = ImageSizeUtils.computeImageSampleSize(imageSize, targetSize, decodingInfo.getViewScaleType(), powerOf2); | |
| 166 | + } | |
| 167 | + if (scale > 1 && loggingEnabled) { | |
| 168 | + L.d(LOG_SUBSAMPLE_IMAGE, imageSize, imageSize.scaleDown(scale), scale, decodingInfo.getImageKey()); | |
| 169 | + } | |
| 170 | + | |
| 171 | + Options decodingOptions = decodingInfo.getDecodingOptions(); | |
| 172 | + decodingOptions.inSampleSize = scale; | |
| 173 | + return decodingOptions; | |
| 174 | + } | |
| 175 | + | |
| 176 | + protected InputStream resetStream(InputStream imageStream, ImageDecodingInfo decodingInfo) throws IOException { | |
| 177 | + try { | |
| 178 | + imageStream.reset(); | |
| 179 | + } catch (IOException e) { | |
| 180 | + IoUtils.closeSilently(imageStream); | |
| 181 | + imageStream = getImageStream(decodingInfo); | |
| 182 | + } | |
| 183 | + return imageStream; | |
| 184 | + } | |
| 185 | + | |
| 186 | + protected Bitmap considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo, | |
| 187 | + int rotation, boolean flipHorizontal) { | |
| 188 | + Matrix m = new Matrix(); | |
| 189 | + // Scale to exact size if need | |
| 190 | + ImageScaleType scaleType = decodingInfo.getImageScaleType(); | |
| 191 | + if (scaleType == ImageScaleType.EXACTLY || scaleType == ImageScaleType.EXACTLY_STRETCHED) { | |
| 192 | + ImageSize srcSize = new ImageSize(subsampledBitmap.getWidth(), subsampledBitmap.getHeight(), rotation); | |
| 193 | + float scale = ImageSizeUtils.computeImageScale(srcSize, decodingInfo.getTargetSize(), decodingInfo | |
| 194 | + .getViewScaleType(), scaleType == ImageScaleType.EXACTLY_STRETCHED); | |
| 195 | + if (Float.compare(scale, 1f) != 0) { | |
| 196 | + m.setScale(scale, scale); | |
| 197 | + | |
| 198 | + if (loggingEnabled) { | |
| 199 | + L.d(LOG_SCALE_IMAGE, srcSize, srcSize.scale(scale), scale, decodingInfo.getImageKey()); | |
| 200 | + } | |
| 201 | + } | |
| 202 | + } | |
| 203 | + // Flip bitmap if need | |
| 204 | + if (flipHorizontal) { | |
| 205 | + m.postScale(-1, 1); | |
| 206 | + | |
| 207 | + if (loggingEnabled) L.d(LOG_FLIP_IMAGE, decodingInfo.getImageKey()); | |
| 208 | + } | |
| 209 | + // Rotate bitmap if need | |
| 210 | + if (rotation != 0) { | |
| 211 | + m.postRotate(rotation); | |
| 212 | + | |
| 213 | + if (loggingEnabled) L.d(LOG_ROTATE_IMAGE, rotation, decodingInfo.getImageKey()); | |
| 214 | + } | |
| 215 | + | |
| 216 | + Bitmap finalBitmap = Bitmap.createBitmap(subsampledBitmap, 0, 0, subsampledBitmap.getWidth(), subsampledBitmap | |
| 217 | + .getHeight(), m, true); | |
| 218 | + if (finalBitmap != subsampledBitmap) { | |
| 219 | + subsampledBitmap.recycle(); | |
| 220 | + } | |
| 221 | + return finalBitmap; | |
| 222 | + } | |
| 223 | + | |
| 224 | + protected static class ExifInfo { | |
| 225 | + | |
| 226 | + public final int rotation; | |
| 227 | + public final boolean flipHorizontal; | |
| 228 | + | |
| 229 | + protected ExifInfo() { | |
| 230 | + this.rotation = 0; | |
| 231 | + this.flipHorizontal = false; | |
| 232 | + } | |
| 233 | + | |
| 234 | + protected ExifInfo(int rotation, boolean flipHorizontal) { | |
| 235 | + this.rotation = rotation; | |
| 236 | + this.flipHorizontal = flipHorizontal; | |
| 237 | + } | |
| 238 | + } | |
| 239 | + | |
| 240 | + protected static class ImageFileInfo { | |
| 241 | + | |
| 242 | + public final ImageSize imageSize; | |
| 243 | + public final ExifInfo exif; | |
| 244 | + | |
| 245 | + protected ImageFileInfo(ImageSize imageSize, ExifInfo exif) { | |
| 246 | + this.imageSize = imageSize; | |
| 247 | + this.exif = exif; | |
| 248 | + } | |
| 249 | + } | |
| 250 | +} | |
| \ No newline at end of file | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.decode; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | + | |
| 20 | +import java.io.IOException; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * Provide decoding image to result {@link Bitmap}. | |
| 24 | + * | |
| 25 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 26 | + * @see ImageDecodingInfo | |
| 27 | + * @since 1.8.3 | |
| 28 | + */ | |
| 29 | +public interface ImageDecoder { | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * Decodes image to {@link Bitmap} according target size and other parameters. | |
| 33 | + * | |
| 34 | + * @param imageDecodingInfo | |
| 35 | + * @return | |
| 36 | + * @throws IOException | |
| 37 | + */ | |
| 38 | + Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException; | |
| 39 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2013-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.decode; | |
| 17 | + | |
| 18 | +import android.annotation.TargetApi; | |
| 19 | +import android.graphics.BitmapFactory.Options; | |
| 20 | +import android.os.Build; | |
| 21 | + | |
| 22 | +import com.nostra13.universalimageloader.core.DisplayImageOptions; | |
| 23 | +import com.nostra13.universalimageloader.core.assist.ImageScaleType; | |
| 24 | +import com.nostra13.universalimageloader.core.assist.ImageSize; | |
| 25 | +import com.nostra13.universalimageloader.core.assist.ViewScaleType; | |
| 26 | +import com.nostra13.universalimageloader.core.download.ImageDownloader; | |
| 27 | + | |
| 28 | +/** | |
| 29 | + * Contains needed information for decoding image to Bitmap | |
| 30 | + * | |
| 31 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 32 | + * @since 1.8.3 | |
| 33 | + */ | |
| 34 | +public class ImageDecodingInfo { | |
| 35 | + | |
| 36 | + private final String imageKey; | |
| 37 | + private final String imageUri; | |
| 38 | + private final String originalImageUri; | |
| 39 | + private final ImageSize targetSize; | |
| 40 | + | |
| 41 | + private final ImageScaleType imageScaleType; | |
| 42 | + private final ViewScaleType viewScaleType; | |
| 43 | + | |
| 44 | + private final ImageDownloader downloader; | |
| 45 | + private final Object extraForDownloader; | |
| 46 | + | |
| 47 | + private final boolean considerExifParams; | |
| 48 | + private final Options decodingOptions; | |
| 49 | + | |
| 50 | + public ImageDecodingInfo(String imageKey, String imageUri, String originalImageUri, ImageSize targetSize, ViewScaleType viewScaleType, | |
| 51 | + ImageDownloader downloader, DisplayImageOptions displayOptions) { | |
| 52 | + this.imageKey = imageKey; | |
| 53 | + this.imageUri = imageUri; | |
| 54 | + this.originalImageUri = originalImageUri; | |
| 55 | + this.targetSize = targetSize; | |
| 56 | + | |
| 57 | + this.imageScaleType = displayOptions.getImageScaleType(); | |
| 58 | + this.viewScaleType = viewScaleType; | |
| 59 | + | |
| 60 | + this.downloader = downloader; | |
| 61 | + this.extraForDownloader = displayOptions.getExtraForDownloader(); | |
| 62 | + | |
| 63 | + considerExifParams = displayOptions.isConsiderExifParams(); | |
| 64 | + decodingOptions = new Options(); | |
| 65 | + copyOptions(displayOptions.getDecodingOptions(), decodingOptions); | |
| 66 | + } | |
| 67 | + | |
| 68 | + private void copyOptions(Options srcOptions, Options destOptions) { | |
| 69 | + destOptions.inDensity = srcOptions.inDensity; | |
| 70 | + destOptions.inDither = srcOptions.inDither; | |
| 71 | + destOptions.inInputShareable = srcOptions.inInputShareable; | |
| 72 | + destOptions.inJustDecodeBounds = srcOptions.inJustDecodeBounds; | |
| 73 | + destOptions.inPreferredConfig = srcOptions.inPreferredConfig; | |
| 74 | + destOptions.inPurgeable = srcOptions.inPurgeable; | |
| 75 | + destOptions.inSampleSize = srcOptions.inSampleSize; | |
| 76 | + destOptions.inScaled = srcOptions.inScaled; | |
| 77 | + destOptions.inScreenDensity = srcOptions.inScreenDensity; | |
| 78 | + destOptions.inTargetDensity = srcOptions.inTargetDensity; | |
| 79 | + destOptions.inTempStorage = srcOptions.inTempStorage; | |
| 80 | + if (Build.VERSION.SDK_INT >= 10) copyOptions10(srcOptions, destOptions); | |
| 81 | + if (Build.VERSION.SDK_INT >= 11) copyOptions11(srcOptions, destOptions); | |
| 82 | + } | |
| 83 | + | |
| 84 | + @TargetApi(10) | |
| 85 | + private void copyOptions10(Options srcOptions, Options destOptions) { | |
| 86 | + destOptions.inPreferQualityOverSpeed = srcOptions.inPreferQualityOverSpeed; | |
| 87 | + } | |
| 88 | + | |
| 89 | + @TargetApi(11) | |
| 90 | + private void copyOptions11(Options srcOptions, Options destOptions) { | |
| 91 | + destOptions.inBitmap = srcOptions.inBitmap; | |
| 92 | + destOptions.inMutable = srcOptions.inMutable; | |
| 93 | + } | |
| 94 | + | |
| 95 | + /** @return Original {@linkplain com.nostra13.universalimageloader.utils.MemoryCacheUtils#generateKey(String, ImageSize) image key} (used in memory cache). */ | |
| 96 | + public String getImageKey() { | |
| 97 | + return imageKey; | |
| 98 | + } | |
| 99 | + | |
| 100 | + /** @return Image URI for decoding (usually image from disk cache) */ | |
| 101 | + public String getImageUri() { | |
| 102 | + return imageUri; | |
| 103 | + } | |
| 104 | + | |
| 105 | + /** @return The original image URI which was passed to ImageLoader */ | |
| 106 | + public String getOriginalImageUri() { | |
| 107 | + return originalImageUri; | |
| 108 | + } | |
| 109 | + | |
| 110 | + /** | |
| 111 | + * @return Target size for image. Decoded bitmap should close to this size according to {@linkplain ImageScaleType | |
| 112 | + * image scale type} and {@linkplain ViewScaleType view scale type}. | |
| 113 | + */ | |
| 114 | + public ImageSize getTargetSize() { | |
| 115 | + return targetSize; | |
| 116 | + } | |
| 117 | + | |
| 118 | + /** | |
| 119 | + * @return {@linkplain ImageScaleType Scale type for image sampling and scaling}. This parameter affects result size | |
| 120 | + * of decoded bitmap. | |
| 121 | + */ | |
| 122 | + public ImageScaleType getImageScaleType() { | |
| 123 | + return imageScaleType; | |
| 124 | + } | |
| 125 | + | |
| 126 | + /** @return {@linkplain ViewScaleType View scale type}. This parameter affects result size of decoded bitmap. */ | |
| 127 | + public ViewScaleType getViewScaleType() { | |
| 128 | + return viewScaleType; | |
| 129 | + } | |
| 130 | + | |
| 131 | + /** @return Downloader for image loading */ | |
| 132 | + public ImageDownloader getDownloader() { | |
| 133 | + return downloader; | |
| 134 | + } | |
| 135 | + | |
| 136 | + /** @return Auxiliary object for downloader */ | |
| 137 | + public Object getExtraForDownloader() { | |
| 138 | + return extraForDownloader; | |
| 139 | + } | |
| 140 | + | |
| 141 | + /** @return <b>true</b> - if EXIF params of image should be considered; <b>false</b> - otherwise */ | |
| 142 | + public boolean shouldConsiderExifParams() { | |
| 143 | + return considerExifParams; | |
| 144 | + } | |
| 145 | + | |
| 146 | + /** @return Decoding options */ | |
| 147 | + public Options getDecodingOptions() { | |
| 148 | + return decodingOptions; | |
| 149 | + } | |
| 150 | +} | |
| \ No newline at end of file | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.display; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.core.assist.LoadedFrom; | |
| 20 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * Displays {@link Bitmap} in {@link ImageAware}. Implementations can | |
| 24 | + * apply some changes to Bitmap or any animation for displaying Bitmap.<br /> | |
| 25 | + * Implementations have to be thread-safe. | |
| 26 | + * | |
| 27 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 28 | + * @see ImageAware | |
| 29 | + * @see LoadedFrom | |
| 30 | + * @since 1.5.6 | |
| 31 | + */ | |
| 32 | +public interface BitmapDisplayer { | |
| 33 | + /** | |
| 34 | + * Displays bitmap in {@link ImageAware}. | |
| 35 | + * <b>NOTE:</b> This method is called on UI thread so it's strongly recommended not to do any heavy work in it. | |
| 36 | + * | |
| 37 | + * @param bitmap Source bitmap | |
| 38 | + * @param imageAware {@linkplain ImageAware Image aware view} to | |
| 39 | + * display Bitmap | |
| 40 | + * @param loadedFrom Source of loaded image | |
| 41 | + */ | |
| 42 | + void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom); | |
| 43 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/display/FadeInBitmapDisplayer.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich, Daniel Martí | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.display; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.view.View; | |
| 20 | +import android.view.animation.AlphaAnimation; | |
| 21 | +import android.view.animation.DecelerateInterpolator; | |
| 22 | +import android.widget.ImageView; | |
| 23 | +import com.nostra13.universalimageloader.core.assist.LoadedFrom; | |
| 24 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 25 | + | |
| 26 | +/** | |
| 27 | + * Displays image with "fade in" animation | |
| 28 | + * | |
| 29 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com), Daniel Martí | |
| 30 | + * @since 1.6.4 | |
| 31 | + */ | |
| 32 | +public class FadeInBitmapDisplayer implements BitmapDisplayer { | |
| 33 | + | |
| 34 | + private final int durationMillis; | |
| 35 | + | |
| 36 | + private final boolean animateFromNetwork; | |
| 37 | + private final boolean animateFromDisk; | |
| 38 | + private final boolean animateFromMemory; | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * @param durationMillis Duration of "fade-in" animation (in milliseconds) | |
| 42 | + */ | |
| 43 | + public FadeInBitmapDisplayer(int durationMillis) { | |
| 44 | + this(durationMillis, true, true, true); | |
| 45 | + } | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * @param durationMillis Duration of "fade-in" animation (in milliseconds) | |
| 49 | + * @param animateFromNetwork Whether animation should be played if image is loaded from network | |
| 50 | + * @param animateFromDisk Whether animation should be played if image is loaded from disk cache | |
| 51 | + * @param animateFromMemory Whether animation should be played if image is loaded from memory cache | |
| 52 | + */ | |
| 53 | + public FadeInBitmapDisplayer(int durationMillis, boolean animateFromNetwork, boolean animateFromDisk, | |
| 54 | + boolean animateFromMemory) { | |
| 55 | + this.durationMillis = durationMillis; | |
| 56 | + this.animateFromNetwork = animateFromNetwork; | |
| 57 | + this.animateFromDisk = animateFromDisk; | |
| 58 | + this.animateFromMemory = animateFromMemory; | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Override | |
| 62 | + public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { | |
| 63 | + imageAware.setImageBitmap(bitmap); | |
| 64 | + | |
| 65 | + if ((animateFromNetwork && loadedFrom == LoadedFrom.NETWORK) || | |
| 66 | + (animateFromDisk && loadedFrom == LoadedFrom.DISC_CACHE) || | |
| 67 | + (animateFromMemory && loadedFrom == LoadedFrom.MEMORY_CACHE)) { | |
| 68 | + animate(imageAware.getWrappedView(), durationMillis); | |
| 69 | + } | |
| 70 | + } | |
| 71 | + | |
| 72 | + /** | |
| 73 | + * Animates {@link ImageView} with "fade-in" effect | |
| 74 | + * | |
| 75 | + * @param imageView {@link ImageView} which display image in | |
| 76 | + * @param durationMillis The length of the animation in milliseconds | |
| 77 | + */ | |
| 78 | + public static void animate(View imageView, int durationMillis) { | |
| 79 | + if (imageView != null) { | |
| 80 | + AlphaAnimation fadeImage = new AlphaAnimation(0, 1); | |
| 81 | + fadeImage.setDuration(durationMillis); | |
| 82 | + fadeImage.setInterpolator(new DecelerateInterpolator()); | |
| 83 | + imageView.startAnimation(fadeImage); | |
| 84 | + } | |
| 85 | + } | |
| 86 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/display/RoundedBitmapDisplayer.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.display; | |
| 17 | + | |
| 18 | +import android.graphics.*; | |
| 19 | +import android.graphics.drawable.Drawable; | |
| 20 | + | |
| 21 | +import com.nostra13.universalimageloader.core.assist.LoadedFrom; | |
| 22 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 23 | +import com.nostra13.universalimageloader.core.imageaware.ImageViewAware; | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * Can display bitmap with rounded corners. This implementation works only with ImageViews wrapped | |
| 27 | + * in ImageViewAware. | |
| 28 | + * <br /> | |
| 29 | + * This implementation is inspired by | |
| 30 | + * <a href="http://www.curious-creature.org/2012/12/11/android-recipe-1-image-with-rounded-corners/"> | |
| 31 | + * Romain Guy's article</a>. It rounds images using custom drawable drawing. Original bitmap isn't changed. | |
| 32 | + * <br /> | |
| 33 | + * <br /> | |
| 34 | + * If this implementation doesn't meet your needs then consider | |
| 35 | + * <a href="https://github.com/vinc3m1/RoundedImageView">RoundedImageView</a> or | |
| 36 | + * <a href="https://github.com/Pkmmte/CircularImageView">CircularImageView</a> projects for usage. | |
| 37 | + * | |
| 38 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 39 | + * @since 1.5.6 | |
| 40 | + */ | |
| 41 | +public class RoundedBitmapDisplayer implements BitmapDisplayer { | |
| 42 | + | |
| 43 | + protected final int cornerRadius; | |
| 44 | + protected final int margin; | |
| 45 | + | |
| 46 | + public RoundedBitmapDisplayer(int cornerRadiusPixels) { | |
| 47 | + this(cornerRadiusPixels, 0); | |
| 48 | + } | |
| 49 | + | |
| 50 | + public RoundedBitmapDisplayer(int cornerRadiusPixels, int marginPixels) { | |
| 51 | + this.cornerRadius = cornerRadiusPixels; | |
| 52 | + this.margin = marginPixels; | |
| 53 | + } | |
| 54 | + | |
| 55 | + @Override | |
| 56 | + public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { | |
| 57 | + if (!(imageAware instanceof ImageViewAware)) { | |
| 58 | + throw new IllegalArgumentException("ImageAware should wrap ImageView. ImageViewAware is expected."); | |
| 59 | + } | |
| 60 | + | |
| 61 | + imageAware.setImageDrawable(new RoundedDrawable(bitmap, cornerRadius, margin)); | |
| 62 | + } | |
| 63 | + | |
| 64 | + public static class RoundedDrawable extends Drawable { | |
| 65 | + | |
| 66 | + protected final float cornerRadius; | |
| 67 | + protected final int margin; | |
| 68 | + | |
| 69 | + protected final RectF mRect = new RectF(), | |
| 70 | + mBitmapRect; | |
| 71 | + protected final BitmapShader bitmapShader; | |
| 72 | + protected final Paint paint; | |
| 73 | + | |
| 74 | + public RoundedDrawable(Bitmap bitmap, int cornerRadius, int margin) { | |
| 75 | + this.cornerRadius = cornerRadius; | |
| 76 | + this.margin = margin; | |
| 77 | + | |
| 78 | + bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); | |
| 79 | + mBitmapRect = new RectF (margin, margin, bitmap.getWidth() - margin, bitmap.getHeight() - margin); | |
| 80 | + | |
| 81 | + paint = new Paint(); | |
| 82 | + paint.setAntiAlias(true); | |
| 83 | + paint.setShader(bitmapShader); | |
| 84 | + } | |
| 85 | + | |
| 86 | + @Override | |
| 87 | + protected void onBoundsChange(Rect bounds) { | |
| 88 | + super.onBoundsChange(bounds); | |
| 89 | + mRect.set(margin, margin, bounds.width() - margin, bounds.height() - margin); | |
| 90 | + | |
| 91 | + // Resize the original bitmap to fit the new bound | |
| 92 | + Matrix shaderMatrix = new Matrix(); | |
| 93 | + shaderMatrix.setRectToRect(mBitmapRect, mRect, Matrix.ScaleToFit.FILL); | |
| 94 | + bitmapShader.setLocalMatrix(shaderMatrix); | |
| 95 | + | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Override | |
| 99 | + public void draw(Canvas canvas) { | |
| 100 | + canvas.drawRoundRect(mRect, cornerRadius, cornerRadius, paint); | |
| 101 | + } | |
| 102 | + | |
| 103 | + @Override | |
| 104 | + public int getOpacity() { | |
| 105 | + return PixelFormat.TRANSLUCENT; | |
| 106 | + } | |
| 107 | + | |
| 108 | + @Override | |
| 109 | + public void setAlpha(int alpha) { | |
| 110 | + paint.setAlpha(alpha); | |
| 111 | + } | |
| 112 | + | |
| 113 | + @Override | |
| 114 | + public void setColorFilter(ColorFilter cf) { | |
| 115 | + paint.setColorFilter(cf); | |
| 116 | + } | |
| 117 | + } | |
| 118 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/display/RoundedVignetteBitmapDisplayer.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.display; | |
| 17 | + | |
| 18 | +import android.graphics.*; | |
| 19 | +import com.nostra13.universalimageloader.core.assist.LoadedFrom; | |
| 20 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 21 | +import com.nostra13.universalimageloader.core.imageaware.ImageViewAware; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * Can display bitmap with rounded corners and vignette effect. This implementation works only with ImageViews wrapped | |
| 25 | + * in ImageViewAware. | |
| 26 | + * <br /> | |
| 27 | + * This implementation is inspired by | |
| 28 | + * <a href="http://www.curious-creature.org/2012/12/11/android-recipe-1-image-with-rounded-corners/"> | |
| 29 | + * Romain Guy's article</a>. It rounds images using custom drawable drawing. Original bitmap isn't changed. | |
| 30 | + * <br /> | |
| 31 | + * <br /> | |
| 32 | + * If this implementation doesn't meet your needs then consider | |
| 33 | + * <a href="https://github.com/vinc3m1/RoundedImageView">this project</a> for usage. | |
| 34 | + * | |
| 35 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 36 | + * @since 1.9.1 | |
| 37 | + */ | |
| 38 | +public class RoundedVignetteBitmapDisplayer extends RoundedBitmapDisplayer { | |
| 39 | + | |
| 40 | + public RoundedVignetteBitmapDisplayer(int cornerRadiusPixels, int marginPixels) { | |
| 41 | + super(cornerRadiusPixels, marginPixels); | |
| 42 | + } | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { | |
| 46 | + if (!(imageAware instanceof ImageViewAware)) { | |
| 47 | + throw new IllegalArgumentException("ImageAware should wrap ImageView. ImageViewAware is expected."); | |
| 48 | + } | |
| 49 | + | |
| 50 | + imageAware.setImageDrawable(new RoundedVignetteDrawable(bitmap, cornerRadius, margin)); | |
| 51 | + } | |
| 52 | + | |
| 53 | + protected static class RoundedVignetteDrawable extends RoundedDrawable { | |
| 54 | + | |
| 55 | + RoundedVignetteDrawable(Bitmap bitmap, int cornerRadius, int margin) { | |
| 56 | + super(bitmap, cornerRadius, margin); | |
| 57 | + } | |
| 58 | + | |
| 59 | + @Override | |
| 60 | + protected void onBoundsChange(Rect bounds) { | |
| 61 | + super.onBoundsChange(bounds); | |
| 62 | + RadialGradient vignette = new RadialGradient( | |
| 63 | + mRect.centerX(), mRect.centerY() * 1.0f / 0.7f, mRect.centerX() * 1.3f, | |
| 64 | + new int[]{0, 0, 0x7f000000}, new float[]{0.0f, 0.7f, 1.0f}, | |
| 65 | + Shader.TileMode.CLAMP); | |
| 66 | + | |
| 67 | + Matrix oval = new Matrix(); | |
| 68 | + oval.setScale(1.0f, 0.7f); | |
| 69 | + vignette.setLocalMatrix(oval); | |
| 70 | + | |
| 71 | + paint.setShader(new ComposeShader(bitmapShader, vignette, PorterDuff.Mode.SRC_OVER)); | |
| 72 | + } | |
| 73 | + } | |
| 74 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/display/SimpleBitmapDisplayer.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.display; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.core.assist.LoadedFrom; | |
| 20 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * Just displays {@link Bitmap} in {@link ImageAware} | |
| 24 | + * | |
| 25 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 26 | + * @since 1.5.6 | |
| 27 | + */ | |
| 28 | +public final class SimpleBitmapDisplayer implements BitmapDisplayer { | |
| 29 | + @Override | |
| 30 | + public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { | |
| 31 | + imageAware.setImageBitmap(bitmap); | |
| 32 | + } | |
| 33 | +} | |
| \ No newline at end of file | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/download/BaseImageDownloader.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * <p> | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * <p> | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * <p> | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.download; | |
| 17 | + | |
| 18 | +import android.annotation.TargetApi; | |
| 19 | +import android.content.ContentResolver; | |
| 20 | +import android.content.Context; | |
| 21 | +import android.graphics.Bitmap; | |
| 22 | +import android.graphics.Bitmap.CompressFormat; | |
| 23 | +import android.media.ThumbnailUtils; | |
| 24 | +import android.net.Uri; | |
| 25 | +import android.os.Build; | |
| 26 | +import android.provider.ContactsContract; | |
| 27 | +import android.provider.MediaStore; | |
| 28 | +import android.webkit.MimeTypeMap; | |
| 29 | +import com.nostra13.universalimageloader.core.DisplayImageOptions; | |
| 30 | +import com.nostra13.universalimageloader.core.assist.ContentLengthInputStream; | |
| 31 | +import com.nostra13.universalimageloader.utils.IoUtils; | |
| 32 | + | |
| 33 | +import java.io.*; | |
| 34 | +import java.net.HttpURLConnection; | |
| 35 | +import java.net.URL; | |
| 36 | +import java.net.URLConnection; | |
| 37 | + | |
| 38 | +/** | |
| 39 | + * Provides retrieving of {@link InputStream} of image by URI from network or file system or app resources.<br /> | |
| 40 | + * {@link URLConnection} is used to retrieve image stream from network. | |
| 41 | + * | |
| 42 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 43 | + * @since 1.8.0 | |
| 44 | + */ | |
| 45 | +public class BaseImageDownloader implements ImageDownloader { | |
| 46 | + /** | |
| 47 | + * {@value} | |
| 48 | + */ | |
| 49 | + public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 5 * 1000; // milliseconds | |
| 50 | + /** | |
| 51 | + * {@value} | |
| 52 | + */ | |
| 53 | + public static final int DEFAULT_HTTP_READ_TIMEOUT = 20 * 1000; // milliseconds | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * {@value} | |
| 57 | + */ | |
| 58 | + protected static final int BUFFER_SIZE = 32 * 1024; // 32 Kb | |
| 59 | + /** | |
| 60 | + * {@value} | |
| 61 | + */ | |
| 62 | + protected static final String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%"; | |
| 63 | + | |
| 64 | + protected static final int MAX_REDIRECT_COUNT = 5; | |
| 65 | + | |
| 66 | + protected static final String CONTENT_CONTACTS_URI_PREFIX = "content://com.android.contacts/"; | |
| 67 | + | |
| 68 | + private static final String ERROR_UNSUPPORTED_SCHEME = "UIL doesn't support scheme(protocol) by default [%s]. " + "You should implement this support yourself (BaseImageDownloader.getStreamFromOtherSource(...))"; | |
| 69 | + | |
| 70 | + protected final Context context; | |
| 71 | + protected final int connectTimeout; | |
| 72 | + protected final int readTimeout; | |
| 73 | + | |
| 74 | + public BaseImageDownloader(Context context) { | |
| 75 | + this(context, DEFAULT_HTTP_CONNECT_TIMEOUT, DEFAULT_HTTP_READ_TIMEOUT); | |
| 76 | + } | |
| 77 | + | |
| 78 | + public BaseImageDownloader(Context context, int connectTimeout, int readTimeout) { | |
| 79 | + this.context = context.getApplicationContext(); | |
| 80 | + this.connectTimeout = connectTimeout; | |
| 81 | + this.readTimeout = readTimeout; | |
| 82 | + } | |
| 83 | + | |
| 84 | + @Override | |
| 85 | + public InputStream getStream(String imageUri, Object extra) throws IOException { | |
| 86 | + switch (Scheme.ofUri(imageUri)) { | |
| 87 | + case HTTP: | |
| 88 | + case HTTPS: | |
| 89 | + return getStreamFromNetwork(imageUri, extra); | |
| 90 | + case FILE: | |
| 91 | + return getStreamFromFile(imageUri, extra); | |
| 92 | + case CONTENT: | |
| 93 | + return getStreamFromContent(imageUri, extra); | |
| 94 | + case ASSETS: | |
| 95 | + return getStreamFromAssets(imageUri, extra); | |
| 96 | + case DRAWABLE: | |
| 97 | + return getStreamFromDrawable(imageUri, extra); | |
| 98 | + case UNKNOWN: | |
| 99 | + default: | |
| 100 | + return getStreamFromOtherSource(imageUri, extra); | |
| 101 | + } | |
| 102 | + } | |
| 103 | + | |
| 104 | + /** | |
| 105 | + * Retrieves {@link InputStream} of image by URI (image is located in the network). | |
| 106 | + * | |
| 107 | + * @param imageUri Image URI | |
| 108 | + * @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) | |
| 109 | + * DisplayImageOptions.extraForDownloader(Object)}; can be null | |
| 110 | + * @return {@link InputStream} of image | |
| 111 | + * @throws IOException if some I/O error occurs during network request or if no InputStream could be created for | |
| 112 | + * URL. | |
| 113 | + */ | |
| 114 | + protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException { | |
| 115 | + HttpURLConnection conn = createConnection(imageUri, extra); | |
| 116 | + | |
| 117 | + int redirectCount = 0; | |
| 118 | + while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) { | |
| 119 | + conn = createConnection(conn.getHeaderField("Location"), extra); | |
| 120 | + redirectCount++; | |
| 121 | + } | |
| 122 | + | |
| 123 | + InputStream imageStream; | |
| 124 | + try { | |
| 125 | + imageStream = conn.getInputStream(); | |
| 126 | + } catch (IOException e) { | |
| 127 | + // Read all data to allow reuse connection (http://bit.ly/1ad35PY) | |
| 128 | + IoUtils.readAndCloseStream(conn.getErrorStream()); | |
| 129 | + throw e; | |
| 130 | + } | |
| 131 | + if (!shouldBeProcessed(conn)) { | |
| 132 | + IoUtils.closeSilently(imageStream); | |
| 133 | + throw new IOException("Image request failed with response code " + conn.getResponseCode()); | |
| 134 | + } | |
| 135 | + | |
| 136 | + return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength()); | |
| 137 | + } | |
| 138 | + | |
| 139 | + /** | |
| 140 | + * @param conn Opened request connection (response code is available) | |
| 141 | + * @return <b>true</b> - if data from connection is correct and should be read and processed; | |
| 142 | + * <b>false</b> - if response contains irrelevant data and shouldn't be processed | |
| 143 | + * @throws IOException | |
| 144 | + */ | |
| 145 | + protected boolean shouldBeProcessed(HttpURLConnection conn) throws IOException { | |
| 146 | + return conn.getResponseCode() == 200; | |
| 147 | + } | |
| 148 | + | |
| 149 | + /** | |
| 150 | + * Create {@linkplain HttpURLConnection HTTP connection} for incoming URL | |
| 151 | + * | |
| 152 | + * @param url URL to connect to | |
| 153 | + * @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) | |
| 154 | + * DisplayImageOptions.extraForDownloader(Object)}; can be null | |
| 155 | + * @return {@linkplain HttpURLConnection Connection} for incoming URL. Connection isn't established so it still configurable. | |
| 156 | + * @throws IOException if some I/O error occurs during network request or if no InputStream could be created for | |
| 157 | + * URL. | |
| 158 | + */ | |
| 159 | + protected HttpURLConnection createConnection(String url, Object extra) throws IOException { | |
| 160 | + String encodedUrl = Uri.encode(url, ALLOWED_URI_CHARS); | |
| 161 | + HttpURLConnection conn = (HttpURLConnection) new URL(encodedUrl).openConnection(); | |
| 162 | + conn.setConnectTimeout(connectTimeout); | |
| 163 | + conn.setReadTimeout(readTimeout); | |
| 164 | + return conn; | |
| 165 | + } | |
| 166 | + | |
| 167 | + public static final int XOR_CONST = 0X99; //密钥 | |
| 168 | + | |
| 169 | + /** | |
| 170 | + * Retrieves {@link InputStream} of image by URI (image is located on the local file system or SD card). | |
| 171 | + * | |
| 172 | + * @param imageUri Image URI | |
| 173 | + * @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) | |
| 174 | + * DisplayImageOptions.extraForDownloader(Object)}; can be null | |
| 175 | + * @return {@link InputStream} of image | |
| 176 | + * @throws IOException if some I/O error occurs reading from file system | |
| 177 | + */ | |
| 178 | + protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException { | |
| 179 | + String filePath = Scheme.FILE.crop(imageUri); | |
| 180 | + if (isVideoFileUri(imageUri)) { | |
| 181 | + return getVideoThumbnailStream(filePath); | |
| 182 | + } else { | |
| 183 | + InputStream in = new FileInputStream(filePath); | |
| 184 | + if (filePath.endsWith("qnj")) { | |
| 185 | + in = getInputStream(in); | |
| 186 | + } else { | |
| 187 | + in = new FileInputStream(filePath); | |
| 188 | + } | |
| 189 | + | |
| 190 | + assert in != null; | |
| 191 | + BufferedInputStream imageStream = new BufferedInputStream(in, BUFFER_SIZE); | |
| 192 | + return new ContentLengthInputStream(imageStream, (int) new File(filePath).length()); | |
| 193 | + } | |
| 194 | + } | |
| 195 | + | |
| 196 | + public static InputStream getInputStream(InputStream fis) { | |
| 197 | + ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
| 198 | + InputStream inputStream = null; | |
| 199 | + try { | |
| 200 | + int read; | |
| 201 | + int bytesWritten = 0; | |
| 202 | + byte[] buffer = new byte[4096]; | |
| 203 | + while ((read = fis.read(buffer)) > -1) { | |
| 204 | + byte[] otherBuffer = new byte[read - 1]; | |
| 205 | + baos.write(buffer[0] ^ XOR_CONST); | |
| 206 | + for (int i = 1; i < read; i++) { | |
| 207 | + otherBuffer[i - 1] = buffer[i]; | |
| 208 | + } | |
| 209 | + baos.write(otherBuffer, bytesWritten, read - 1); | |
| 210 | + } | |
| 211 | + byte[] byteArray = baos.toByteArray(); | |
| 212 | + inputStream = new ByteArrayInputStream(byteArray); | |
| 213 | + return inputStream; | |
| 214 | + } catch (FileNotFoundException e) { | |
| 215 | + e.printStackTrace(); | |
| 216 | + return null; | |
| 217 | + } catch (IOException e) { | |
| 218 | + e.printStackTrace(); | |
| 219 | + return null; | |
| 220 | + } finally { | |
| 221 | + if (inputStream != null) { | |
| 222 | + try { | |
| 223 | + inputStream.close(); | |
| 224 | + } catch (IOException e) { | |
| 225 | + e.printStackTrace(); | |
| 226 | + } | |
| 227 | + } | |
| 228 | + } | |
| 229 | + } | |
| 230 | + | |
| 231 | + | |
| 232 | + @TargetApi(Build.VERSION_CODES.FROYO) | |
| 233 | + private InputStream getVideoThumbnailStream(String filePath) { | |
| 234 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { | |
| 235 | + Bitmap bitmap = ThumbnailUtils | |
| 236 | + .createVideoThumbnail(filePath, MediaStore.Images.Thumbnails.FULL_SCREEN_KIND); | |
| 237 | + if (bitmap != null) { | |
| 238 | + ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
| 239 | + bitmap.compress(CompressFormat.PNG, 0, bos); | |
| 240 | + return new ByteArrayInputStream(bos.toByteArray()); | |
| 241 | + } | |
| 242 | + } | |
| 243 | + return null; | |
| 244 | + } | |
| 245 | + | |
| 246 | + /** | |
| 247 | + * Retrieves {@link InputStream} of image by URI (image is accessed using {@link ContentResolver}). | |
| 248 | + * | |
| 249 | + * @param imageUri Image URI | |
| 250 | + * @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) | |
| 251 | + * DisplayImageOptions.extraForDownloader(Object)}; can be null | |
| 252 | + * @return {@link InputStream} of image | |
| 253 | + * @throws FileNotFoundException if the provided URI could not be opened | |
| 254 | + */ | |
| 255 | + protected InputStream getStreamFromContent(String imageUri, Object extra) throws FileNotFoundException { | |
| 256 | + ContentResolver res = context.getContentResolver(); | |
| 257 | + | |
| 258 | + Uri uri = Uri.parse(imageUri); | |
| 259 | + if (isVideoContentUri(uri)) { // video thumbnail | |
| 260 | + Long origId = Long.valueOf(uri.getLastPathSegment()); | |
| 261 | + Bitmap bitmap = MediaStore.Video.Thumbnails | |
| 262 | + .getThumbnail(res, origId, MediaStore.Images.Thumbnails.MINI_KIND, null); | |
| 263 | + if (bitmap != null) { | |
| 264 | + ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
| 265 | + bitmap.compress(CompressFormat.PNG, 0, bos); | |
| 266 | + return new ByteArrayInputStream(bos.toByteArray()); | |
| 267 | + } | |
| 268 | + } else if (imageUri.startsWith(CONTENT_CONTACTS_URI_PREFIX)) { // contacts photo | |
| 269 | + return getContactPhotoStream(uri); | |
| 270 | + } | |
| 271 | + | |
| 272 | + return res.openInputStream(uri); | |
| 273 | + } | |
| 274 | + | |
| 275 | + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) | |
| 276 | + protected InputStream getContactPhotoStream(Uri uri) { | |
| 277 | + ContentResolver res = context.getContentResolver(); | |
| 278 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { | |
| 279 | + return ContactsContract.Contacts.openContactPhotoInputStream(res, uri, true); | |
| 280 | + } else { | |
| 281 | + return ContactsContract.Contacts.openContactPhotoInputStream(res, uri); | |
| 282 | + } | |
| 283 | + } | |
| 284 | + | |
| 285 | + /** | |
| 286 | + * Retrieves {@link InputStream} of image by URI (image is located in assets of application). | |
| 287 | + * | |
| 288 | + * @param imageUri Image URI | |
| 289 | + * @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) | |
| 290 | + * DisplayImageOptions.extraForDownloader(Object)}; can be null | |
| 291 | + * @return {@link InputStream} of image | |
| 292 | + * @throws IOException if some I/O error occurs file reading | |
| 293 | + */ | |
| 294 | + protected InputStream getStreamFromAssets(String imageUri, Object extra) throws IOException { | |
| 295 | + String filePath = Scheme.ASSETS.crop(imageUri); | |
| 296 | + return context.getAssets().open(filePath); | |
| 297 | + } | |
| 298 | + | |
| 299 | + /** | |
| 300 | + * Retrieves {@link InputStream} of image by URI (image is located in drawable resources of application). | |
| 301 | + * | |
| 302 | + * @param imageUri Image URI | |
| 303 | + * @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) | |
| 304 | + * DisplayImageOptions.extraForDownloader(Object)}; can be null | |
| 305 | + * @return {@link InputStream} of image | |
| 306 | + */ | |
| 307 | + protected InputStream getStreamFromDrawable(String imageUri, Object extra) { | |
| 308 | + String drawableIdString = Scheme.DRAWABLE.crop(imageUri); | |
| 309 | + int drawableId = Integer.parseInt(drawableIdString); | |
| 310 | + return context.getResources().openRawResource(drawableId); | |
| 311 | + } | |
| 312 | + | |
| 313 | + /** | |
| 314 | + * Retrieves {@link InputStream} of image by URI from other source with unsupported scheme. Should be overriden by | |
| 315 | + * successors to implement image downloading from special sources.<br /> | |
| 316 | + * This method is called only if image URI has unsupported scheme. Throws {@link UnsupportedOperationException} by | |
| 317 | + * default. | |
| 318 | + * | |
| 319 | + * @param imageUri Image URI | |
| 320 | + * @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) | |
| 321 | + * DisplayImageOptions.extraForDownloader(Object)}; can be null | |
| 322 | + * @return {@link InputStream} of image | |
| 323 | + * @throws IOException if some I/O error occurs | |
| 324 | + * @throws UnsupportedOperationException if image URI has unsupported scheme(protocol) | |
| 325 | + */ | |
| 326 | + protected InputStream getStreamFromOtherSource(String imageUri, Object extra) throws IOException { | |
| 327 | + throw new UnsupportedOperationException(String.format(ERROR_UNSUPPORTED_SCHEME, imageUri)); | |
| 328 | + } | |
| 329 | + | |
| 330 | + private boolean isVideoContentUri(Uri uri) { | |
| 331 | + String mimeType = context.getContentResolver().getType(uri); | |
| 332 | + return mimeType != null && mimeType.startsWith("video/"); | |
| 333 | + } | |
| 334 | + | |
| 335 | + private boolean isVideoFileUri(String uri) { | |
| 336 | + String extension = MimeTypeMap.getFileExtensionFromUrl(uri); | |
| 337 | + String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); | |
| 338 | + return mimeType != null && mimeType.startsWith("video/"); | |
| 339 | + } | |
| 340 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.download; | |
| 17 | + | |
| 18 | +import com.nostra13.universalimageloader.core.DisplayImageOptions; | |
| 19 | + | |
| 20 | +import java.io.IOException; | |
| 21 | +import java.io.InputStream; | |
| 22 | +import java.util.Locale; | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * Provides retrieving of {@link InputStream} of image by URI.<br /> | |
| 26 | + * Implementations have to be thread-safe. | |
| 27 | + * | |
| 28 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 29 | + * @since 1.4.0 | |
| 30 | + */ | |
| 31 | +public interface ImageDownloader { | |
| 32 | + /** | |
| 33 | + * Retrieves {@link InputStream} of image by URI. | |
| 34 | + * | |
| 35 | + * @param imageUri Image URI | |
| 36 | + * @param extra Auxiliary object which was passed to {@link DisplayImageOptions.Builder#extraForDownloader(Object) | |
| 37 | + * DisplayImageOptions.extraForDownloader(Object)}; can be null | |
| 38 | + * @return {@link InputStream} of image | |
| 39 | + * @throws IOException if some I/O error occurs during getting image stream | |
| 40 | + * @throws UnsupportedOperationException if image URI has unsupported scheme(protocol) | |
| 41 | + */ | |
| 42 | + InputStream getStream(String imageUri, Object extra) throws IOException; | |
| 43 | + | |
| 44 | + /** Represents supported schemes(protocols) of URI. Provides convenient methods for work with schemes and URIs. */ | |
| 45 | + public enum Scheme { | |
| 46 | + HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN(""); | |
| 47 | + | |
| 48 | + private String scheme; | |
| 49 | + private String uriPrefix; | |
| 50 | + | |
| 51 | + Scheme(String scheme) { | |
| 52 | + this.scheme = scheme; | |
| 53 | + uriPrefix = scheme + "://"; | |
| 54 | + } | |
| 55 | + | |
| 56 | + /** | |
| 57 | + * Defines scheme of incoming URI | |
| 58 | + * | |
| 59 | + * @param uri URI for scheme detection | |
| 60 | + * @return Scheme of incoming URI | |
| 61 | + */ | |
| 62 | + public static Scheme ofUri(String uri) { | |
| 63 | + if (uri != null) { | |
| 64 | + for (Scheme s : values()) { | |
| 65 | + if (s.belongsTo(uri)) { | |
| 66 | + return s; | |
| 67 | + } | |
| 68 | + } | |
| 69 | + } | |
| 70 | + return UNKNOWN; | |
| 71 | + } | |
| 72 | + | |
| 73 | + private boolean belongsTo(String uri) { | |
| 74 | + return uri.toLowerCase(Locale.US).startsWith(uriPrefix); | |
| 75 | + } | |
| 76 | + | |
| 77 | + /** Appends scheme to incoming path */ | |
| 78 | + public String wrap(String path) { | |
| 79 | + return uriPrefix + path; | |
| 80 | + } | |
| 81 | + | |
| 82 | + /** Removed scheme part ("scheme://") from incoming URI */ | |
| 83 | + public String crop(String uri) { | |
| 84 | + if (!belongsTo(uri)) { | |
| 85 | + throw new IllegalArgumentException(String.format("URI [%1$s] doesn't have expected scheme [%2$s]", uri, scheme)); | |
| 86 | + } | |
| 87 | + return uri.substring(uriPrefix.length()); | |
| 88 | + } | |
| 89 | + } | |
| 90 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2013-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.imageaware; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.graphics.drawable.Drawable; | |
| 20 | +import android.view.View; | |
| 21 | +import com.nostra13.universalimageloader.core.assist.ViewScaleType; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * Represents image aware view which provides all needed properties and behavior for image processing and displaying | |
| 25 | + * through {@link com.nostra13.universalimageloader.core.ImageLoader ImageLoader}. | |
| 26 | + * It can wrap any Android {@link View View} which can be accessed by {@link #getWrappedView()}. Wrapped | |
| 27 | + * view is returned in {@link com.nostra13.universalimageloader.core.listener.ImageLoadingListener ImageLoadingListener}'s | |
| 28 | + * callbacks. | |
| 29 | + * | |
| 30 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 31 | + * @see ViewAware | |
| 32 | + * @see ImageViewAware | |
| 33 | + * @see NonViewAware | |
| 34 | + * @since 1.9.0 | |
| 35 | + */ | |
| 36 | +public interface ImageAware { | |
| 37 | + /** | |
| 38 | + * Returns width of image aware view. This value is used to define scale size for original image. | |
| 39 | + * Can return 0 if width is undefined.<br /> | |
| 40 | + * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread. | |
| 41 | + */ | |
| 42 | + int getWidth(); | |
| 43 | + | |
| 44 | + /** | |
| 45 | + * Returns height of image aware view. This value is used to define scale size for original image. | |
| 46 | + * Can return 0 if height is undefined.<br /> | |
| 47 | + * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread. | |
| 48 | + */ | |
| 49 | + int getHeight(); | |
| 50 | + | |
| 51 | + /** | |
| 52 | + * Returns {@linkplain ViewScaleType scale type} which is used for | |
| 53 | + * scaling image for this image aware view. Must <b>NOT</b> return <b>null</b>. | |
| 54 | + */ | |
| 55 | + ViewScaleType getScaleType(); | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * Returns wrapped Android {@link View View}. Can return <b>null</b> if no view is wrapped or view was | |
| 59 | + * collected by GC.<br /> | |
| 60 | + * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread. | |
| 61 | + */ | |
| 62 | + View getWrappedView(); | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * Returns a flag whether image aware view is collected by GC or whatsoever. If so then ImageLoader stop processing | |
| 66 | + * of task for this image aware view and fires | |
| 67 | + * {@link com.nostra13.universalimageloader.core.listener.ImageLoadingListener#onLoadingCancelled(String, | |
| 68 | + * View) ImageLoadingListener#onLoadingCancelled(String, View)} callback.<br /> | |
| 69 | + * Mey be called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread. | |
| 70 | + * | |
| 71 | + * @return <b>true</b> - if view is collected by GC and ImageLoader should stop processing this image aware view; | |
| 72 | + * <b>false</b> - otherwise | |
| 73 | + */ | |
| 74 | + boolean isCollected(); | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * Returns ID of image aware view. Point of ID is similar to Object's hashCode. This ID should be unique for every | |
| 78 | + * image view instance and should be the same for same instances. This ID identifies processing task in ImageLoader | |
| 79 | + * so ImageLoader won't process two image aware views with the same ID in one time. When ImageLoader get new task | |
| 80 | + * it cancels old task with this ID (if any) and starts new task. | |
| 81 | + * <p/> | |
| 82 | + * It's reasonable to return hash code of wrapped view (if any) to prevent displaying non-actual images in view | |
| 83 | + * because of view re-using. | |
| 84 | + */ | |
| 85 | + int getId(); | |
| 86 | + | |
| 87 | + /** | |
| 88 | + * Sets image drawable into this image aware view.<br /> | |
| 89 | + * Displays drawable in this image aware view | |
| 90 | + * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageForEmptyUri( | |
| 91 | + *Drawable) for empty Uri}, | |
| 92 | + * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageOnLoading( | |
| 93 | + *Drawable) on loading} or | |
| 94 | + * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions.Builder#showImageOnFail( | |
| 95 | + *Drawable) on loading fail}. These drawables can be specified in | |
| 96 | + * {@linkplain com.nostra13.universalimageloader.core.DisplayImageOptions display options}.<br /> | |
| 97 | + * Also can be called in {@link com.nostra13.universalimageloader.core.display.BitmapDisplayer BitmapDisplayer}.< br /> | |
| 98 | + * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread. | |
| 99 | + * | |
| 100 | + * @return <b>true</b> if drawable was set successfully; <b>false</b> - otherwise | |
| 101 | + */ | |
| 102 | + boolean setImageDrawable(Drawable drawable); | |
| 103 | + | |
| 104 | + /** | |
| 105 | + * Sets image bitmap into this image aware view.<br /> | |
| 106 | + * Displays loaded and decoded image {@link Bitmap} in this image view aware. | |
| 107 | + * Actually it's used only in | |
| 108 | + * {@link com.nostra13.universalimageloader.core.display.BitmapDisplayer BitmapDisplayer}.< br /> | |
| 109 | + * Is called on UI thread if ImageLoader was called on UI thread. Otherwise - on background thread. | |
| 110 | + * | |
| 111 | + * @return <b>true</b> if bitmap was set successfully; <b>false</b> - otherwise | |
| 112 | + */ | |
| 113 | + boolean setImageBitmap(Bitmap bitmap); | |
| 114 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2013-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.imageaware; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.graphics.drawable.AnimationDrawable; | |
| 20 | +import android.graphics.drawable.Drawable; | |
| 21 | +import android.view.View; | |
| 22 | +import android.widget.ImageView; | |
| 23 | +import com.nostra13.universalimageloader.core.assist.ViewScaleType; | |
| 24 | +import com.nostra13.universalimageloader.utils.L; | |
| 25 | + | |
| 26 | +import java.lang.reflect.Field; | |
| 27 | + | |
| 28 | +/** | |
| 29 | + * Wrapper for Android {@link ImageView ImageView}. Keeps weak reference of ImageView to prevent memory | |
| 30 | + * leaks. | |
| 31 | + * | |
| 32 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 33 | + * @since 1.9.0 | |
| 34 | + */ | |
| 35 | +public class ImageViewAware extends ViewAware { | |
| 36 | + | |
| 37 | + /** | |
| 38 | + * Constructor. <br /> | |
| 39 | + * References {@link #ImageViewAware(ImageView, boolean) ImageViewAware(imageView, true)}. | |
| 40 | + * | |
| 41 | + * @param imageView {@link ImageView ImageView} to work with | |
| 42 | + */ | |
| 43 | + public ImageViewAware(ImageView imageView) { | |
| 44 | + super(imageView); | |
| 45 | + } | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * Constructor | |
| 49 | + * | |
| 50 | + * @param imageView {@link ImageView ImageView} to work with | |
| 51 | + * @param checkActualViewSize <b>true</b> - then {@link #getWidth()} and {@link #getHeight()} will check actual | |
| 52 | + * size of ImageView. It can cause known issues like | |
| 53 | + * <a href="https://github.com/nostra13/Android-Universal-Image-Loader/issues/376">this</a>. | |
| 54 | + * But it helps to save memory because memory cache keeps bitmaps of actual (less in | |
| 55 | + * general) size. | |
| 56 | + * <p/> | |
| 57 | + * <b>false</b> - then {@link #getWidth()} and {@link #getHeight()} will <b>NOT</b> | |
| 58 | + * consider actual size of ImageView, just layout parameters. <br /> If you set 'false' | |
| 59 | + * it's recommended 'android:layout_width' and 'android:layout_height' (or | |
| 60 | + * 'android:maxWidth' and 'android:maxHeight') are set with concrete values. It helps to | |
| 61 | + * save memory. | |
| 62 | + * <p/> | |
| 63 | + */ | |
| 64 | + public ImageViewAware(ImageView imageView, boolean checkActualViewSize) { | |
| 65 | + super(imageView, checkActualViewSize); | |
| 66 | + } | |
| 67 | + | |
| 68 | + /** | |
| 69 | + * {@inheritDoc} | |
| 70 | + * <br /> | |
| 71 | + * 3) Get <b>maxWidth</b>. | |
| 72 | + */ | |
| 73 | + @Override | |
| 74 | + public int getWidth() { | |
| 75 | + int width = super.getWidth(); | |
| 76 | + if (width <= 0) { | |
| 77 | + ImageView imageView = (ImageView) viewRef.get(); | |
| 78 | + if (imageView != null) { | |
| 79 | + width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check maxWidth parameter | |
| 80 | + } | |
| 81 | + } | |
| 82 | + return width; | |
| 83 | + } | |
| 84 | + | |
| 85 | + /** | |
| 86 | + * {@inheritDoc} | |
| 87 | + * <br /> | |
| 88 | + * 3) Get <b>maxHeight</b> | |
| 89 | + */ | |
| 90 | + @Override | |
| 91 | + public int getHeight() { | |
| 92 | + int height = super.getHeight(); | |
| 93 | + if (height <= 0) { | |
| 94 | + ImageView imageView = (ImageView) viewRef.get(); | |
| 95 | + if (imageView != null) { | |
| 96 | + height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check maxHeight parameter | |
| 97 | + } | |
| 98 | + } | |
| 99 | + return height; | |
| 100 | + } | |
| 101 | + | |
| 102 | + @Override | |
| 103 | + public ViewScaleType getScaleType() { | |
| 104 | + ImageView imageView = (ImageView) viewRef.get(); | |
| 105 | + if (imageView != null) { | |
| 106 | + return ViewScaleType.fromImageView(imageView); | |
| 107 | + } | |
| 108 | + return super.getScaleType(); | |
| 109 | + } | |
| 110 | + | |
| 111 | + @Override | |
| 112 | + public ImageView getWrappedView() { | |
| 113 | + return (ImageView) super.getWrappedView(); | |
| 114 | + } | |
| 115 | + | |
| 116 | + @Override | |
| 117 | + protected void setImageDrawableInto(Drawable drawable, View view) { | |
| 118 | + ((ImageView) view).setImageDrawable(drawable); | |
| 119 | + if (drawable instanceof AnimationDrawable) { | |
| 120 | + ((AnimationDrawable)drawable).start(); | |
| 121 | + } | |
| 122 | + } | |
| 123 | + | |
| 124 | + @Override | |
| 125 | + protected void setImageBitmapInto(Bitmap bitmap, View view) { | |
| 126 | + ((ImageView) view).setImageBitmap(bitmap); | |
| 127 | + } | |
| 128 | + | |
| 129 | + private static int getImageViewFieldValue(Object object, String fieldName) { | |
| 130 | + int value = 0; | |
| 131 | + try { | |
| 132 | + Field field = ImageView.class.getDeclaredField(fieldName); | |
| 133 | + field.setAccessible(true); | |
| 134 | + int fieldValue = (Integer) field.get(object); | |
| 135 | + if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) { | |
| 136 | + value = fieldValue; | |
| 137 | + } | |
| 138 | + } catch (Exception e) { | |
| 139 | + L.e(e); | |
| 140 | + } | |
| 141 | + return value; | |
| 142 | + } | |
| 143 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2013-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.imageaware; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.graphics.drawable.Drawable; | |
| 20 | +import android.text.TextUtils; | |
| 21 | +import android.view.View; | |
| 22 | +import com.nostra13.universalimageloader.core.assist.ImageSize; | |
| 23 | +import com.nostra13.universalimageloader.core.assist.ViewScaleType; | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * ImageAware which provides needed info for processing of original image but do nothing for displaying image. It's | |
| 27 | + * used when user need just load and decode image and get it in {@linkplain | |
| 28 | + * com.nostra13.universalimageloader.core.listener.ImageLoadingListener#onLoadingComplete(String, View, | |
| 29 | + * Bitmap) callback}. | |
| 30 | + * | |
| 31 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 32 | + * @since 1.9.0 | |
| 33 | + */ | |
| 34 | +public class NonViewAware implements ImageAware { | |
| 35 | + | |
| 36 | + protected final String imageUri; | |
| 37 | + protected final ImageSize imageSize; | |
| 38 | + protected final ViewScaleType scaleType; | |
| 39 | + | |
| 40 | + public NonViewAware(ImageSize imageSize, ViewScaleType scaleType) { | |
| 41 | + this(null, imageSize, scaleType); | |
| 42 | + } | |
| 43 | + | |
| 44 | + public NonViewAware(String imageUri, ImageSize imageSize, ViewScaleType scaleType) { | |
| 45 | + if (imageSize == null) throw new IllegalArgumentException("imageSize must not be null"); | |
| 46 | + if (scaleType == null) throw new IllegalArgumentException("scaleType must not be null"); | |
| 47 | + | |
| 48 | + this.imageUri = imageUri; | |
| 49 | + this.imageSize = imageSize; | |
| 50 | + this.scaleType = scaleType; | |
| 51 | + } | |
| 52 | + | |
| 53 | + @Override | |
| 54 | + public int getWidth() { | |
| 55 | + return imageSize.getWidth(); | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Override | |
| 59 | + public int getHeight() { | |
| 60 | + return imageSize.getHeight(); | |
| 61 | + } | |
| 62 | + | |
| 63 | + @Override | |
| 64 | + public ViewScaleType getScaleType() { | |
| 65 | + return scaleType; | |
| 66 | + } | |
| 67 | + | |
| 68 | + @Override | |
| 69 | + public View getWrappedView() { | |
| 70 | + return null; | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + public boolean isCollected() { | |
| 75 | + return false; | |
| 76 | + } | |
| 77 | + | |
| 78 | + @Override | |
| 79 | + public int getId() { | |
| 80 | + return TextUtils.isEmpty(imageUri) ? super.hashCode() : imageUri.hashCode(); | |
| 81 | + } | |
| 82 | + | |
| 83 | + @Override | |
| 84 | + public boolean setImageDrawable(Drawable drawable) { // Do nothing | |
| 85 | + return true; | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + public boolean setImageBitmap(Bitmap bitmap) { // Do nothing | |
| 90 | + return true; | |
| 91 | + } | |
| 92 | +} | |
| \ No newline at end of file | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.imageaware; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.graphics.drawable.Drawable; | |
| 20 | +import android.os.Looper; | |
| 21 | +import android.view.View; | |
| 22 | +import android.view.ViewGroup; | |
| 23 | +import com.nostra13.universalimageloader.core.assist.ViewScaleType; | |
| 24 | +import com.nostra13.universalimageloader.utils.L; | |
| 25 | + | |
| 26 | +import java.lang.ref.Reference; | |
| 27 | +import java.lang.ref.WeakReference; | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * Wrapper for Android {@link View View}. Keeps weak reference of View to prevent memory leaks. | |
| 31 | + * | |
| 32 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 33 | + * @since 1.9.2 | |
| 34 | + */ | |
| 35 | +public abstract class ViewAware implements ImageAware { | |
| 36 | + | |
| 37 | + public static final String WARN_CANT_SET_DRAWABLE = "Can't set a drawable into view. You should call ImageLoader on UI thread for it."; | |
| 38 | + public static final String WARN_CANT_SET_BITMAP = "Can't set a bitmap into view. You should call ImageLoader on UI thread for it."; | |
| 39 | + | |
| 40 | + protected Reference<View> viewRef; | |
| 41 | + protected boolean checkActualViewSize; | |
| 42 | + | |
| 43 | + /** | |
| 44 | + * Constructor. <br /> | |
| 45 | + * References {@link #ViewAware(View, boolean) ImageViewAware(imageView, true)}. | |
| 46 | + * | |
| 47 | + * @param view {@link View View} to work with | |
| 48 | + */ | |
| 49 | + public ViewAware(View view) { | |
| 50 | + this(view, true); | |
| 51 | + } | |
| 52 | + | |
| 53 | + /** | |
| 54 | + * Constructor | |
| 55 | + * | |
| 56 | + * @param view {@link View View} to work with | |
| 57 | + * @param checkActualViewSize <b>true</b> - then {@link #getWidth()} and {@link #getHeight()} will check actual | |
| 58 | + * size of View. It can cause known issues like | |
| 59 | + * <a href="https://github.com/nostra13/Android-Universal-Image-Loader/issues/376">this</a>. | |
| 60 | + * But it helps to save memory because memory cache keeps bitmaps of actual (less in | |
| 61 | + * general) size. | |
| 62 | + * <p/> | |
| 63 | + * <b>false</b> - then {@link #getWidth()} and {@link #getHeight()} will <b>NOT</b> | |
| 64 | + * consider actual size of View, just layout parameters. <br /> If you set 'false' | |
| 65 | + * it's recommended 'android:layout_width' and 'android:layout_height' (or | |
| 66 | + * 'android:maxWidth' and 'android:maxHeight') are set with concrete values. It helps to | |
| 67 | + * save memory. | |
| 68 | + */ | |
| 69 | + public ViewAware(View view, boolean checkActualViewSize) { | |
| 70 | + if (view == null) throw new IllegalArgumentException("view must not be null"); | |
| 71 | + | |
| 72 | + this.viewRef = new WeakReference<View>(view); | |
| 73 | + this.checkActualViewSize = checkActualViewSize; | |
| 74 | + } | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * {@inheritDoc} | |
| 78 | + * <p/> | |
| 79 | + * Width is defined by target {@link View view} parameters, configuration | |
| 80 | + * parameters or device display dimensions.<br /> | |
| 81 | + * Size computing algorithm (go by steps until get non-zero value):<br /> | |
| 82 | + * 1) Get the actual drawn <b>getWidth()</b> of the View<br /> | |
| 83 | + * 2) Get <b>layout_width</b> | |
| 84 | + */ | |
| 85 | + @Override | |
| 86 | + public int getWidth() { | |
| 87 | + View view = viewRef.get(); | |
| 88 | + if (view != null) { | |
| 89 | + final ViewGroup.LayoutParams params = view.getLayoutParams(); | |
| 90 | + int width = 0; | |
| 91 | + if (checkActualViewSize && params != null && params.width != ViewGroup.LayoutParams.WRAP_CONTENT) { | |
| 92 | + width = view.getWidth(); // Get actual image width | |
| 93 | + } | |
| 94 | + if (width <= 0 && params != null) width = params.width; // Get layout width parameter | |
| 95 | + return width; | |
| 96 | + } | |
| 97 | + return 0; | |
| 98 | + } | |
| 99 | + | |
| 100 | + /** | |
| 101 | + * {@inheritDoc} | |
| 102 | + * <p/> | |
| 103 | + * Height is defined by target {@link View view} parameters, configuration | |
| 104 | + * parameters or device display dimensions.<br /> | |
| 105 | + * Size computing algorithm (go by steps until get non-zero value):<br /> | |
| 106 | + * 1) Get the actual drawn <b>getHeight()</b> of the View<br /> | |
| 107 | + * 2) Get <b>layout_height</b> | |
| 108 | + */ | |
| 109 | + @Override | |
| 110 | + public int getHeight() { | |
| 111 | + View view = viewRef.get(); | |
| 112 | + if (view != null) { | |
| 113 | + final ViewGroup.LayoutParams params = view.getLayoutParams(); | |
| 114 | + int height = 0; | |
| 115 | + if (checkActualViewSize && params != null && params.height != ViewGroup.LayoutParams.WRAP_CONTENT) { | |
| 116 | + height = view.getHeight(); // Get actual image height | |
| 117 | + } | |
| 118 | + if (height <= 0 && params != null) height = params.height; // Get layout height parameter | |
| 119 | + return height; | |
| 120 | + } | |
| 121 | + return 0; | |
| 122 | + } | |
| 123 | + | |
| 124 | + @Override | |
| 125 | + public ViewScaleType getScaleType() { | |
| 126 | + return ViewScaleType.CROP; | |
| 127 | + } | |
| 128 | + | |
| 129 | + @Override | |
| 130 | + public View getWrappedView() { | |
| 131 | + return viewRef.get(); | |
| 132 | + } | |
| 133 | + | |
| 134 | + @Override | |
| 135 | + public boolean isCollected() { | |
| 136 | + return viewRef.get() == null; | |
| 137 | + } | |
| 138 | + | |
| 139 | + @Override | |
| 140 | + public int getId() { | |
| 141 | + View view = viewRef.get(); | |
| 142 | + return view == null ? super.hashCode() : view.hashCode(); | |
| 143 | + } | |
| 144 | + | |
| 145 | + @Override | |
| 146 | + public boolean setImageDrawable(Drawable drawable) { | |
| 147 | + if (Looper.myLooper() == Looper.getMainLooper()) { | |
| 148 | + View view = viewRef.get(); | |
| 149 | + if (view != null) { | |
| 150 | + setImageDrawableInto(drawable, view); | |
| 151 | + return true; | |
| 152 | + } | |
| 153 | + } else { | |
| 154 | + L.w(WARN_CANT_SET_DRAWABLE); | |
| 155 | + } | |
| 156 | + return false; | |
| 157 | + } | |
| 158 | + | |
| 159 | + @Override | |
| 160 | + public boolean setImageBitmap(Bitmap bitmap) { | |
| 161 | + if (Looper.myLooper() == Looper.getMainLooper()) { | |
| 162 | + View view = viewRef.get(); | |
| 163 | + if (view != null) { | |
| 164 | + setImageBitmapInto(bitmap, view); | |
| 165 | + return true; | |
| 166 | + } | |
| 167 | + } else { | |
| 168 | + L.w(WARN_CANT_SET_BITMAP); | |
| 169 | + } | |
| 170 | + return false; | |
| 171 | + } | |
| 172 | + | |
| 173 | + /** | |
| 174 | + * Should set drawable into incoming view. Incoming view is guaranteed not null.<br /> | |
| 175 | + * This method is called on UI thread. | |
| 176 | + */ | |
| 177 | + protected abstract void setImageDrawableInto(Drawable drawable, View view); | |
| 178 | + | |
| 179 | + /** | |
| 180 | + * Should set Bitmap into incoming view. Incoming view is guaranteed not null.< br /> | |
| 181 | + * This method is called on UI thread. | |
| 182 | + */ | |
| 183 | + protected abstract void setImageBitmapInto(Bitmap bitmap, View view); | |
| 184 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/listener/ImageLoadingListener.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.listener; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.view.View; | |
| 20 | +import com.nostra13.universalimageloader.core.assist.FailReason; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * Listener for image loading process.<br /> | |
| 24 | + * You can use {@link SimpleImageLoadingListener} for implementing only needed methods. | |
| 25 | + * | |
| 26 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 27 | + * @see SimpleImageLoadingListener | |
| 28 | + * @see FailReason | |
| 29 | + * @since 1.0.0 | |
| 30 | + */ | |
| 31 | +public interface ImageLoadingListener { | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * Is called when image loading task was started | |
| 35 | + * | |
| 36 | + * @param imageUri Loading image URI | |
| 37 | + * @param view View for image | |
| 38 | + */ | |
| 39 | + void onLoadingStarted(String imageUri, View view); | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * Is called when an error was occurred during image loading | |
| 43 | + * | |
| 44 | + * @param imageUri Loading image URI | |
| 45 | + * @param view View for image. Can be <b>null</b>. | |
| 46 | + * @param failReason {@linkplain FailReason The reason} why image | |
| 47 | + * loading was failed | |
| 48 | + */ | |
| 49 | + void onLoadingFailed(String imageUri, View view, FailReason failReason); | |
| 50 | + | |
| 51 | + /** | |
| 52 | + * Is called when image is loaded successfully (and displayed in View if one was specified) | |
| 53 | + * | |
| 54 | + * @param imageUri Loaded image URI | |
| 55 | + * @param view View for image. Can be <b>null</b>. | |
| 56 | + * @param loadedImage Bitmap of loaded and decoded image | |
| 57 | + */ | |
| 58 | + void onLoadingComplete(String imageUri, View view, Bitmap loadedImage); | |
| 59 | + | |
| 60 | + /** | |
| 61 | + * Is called when image loading task was cancelled because View for image was reused in newer task | |
| 62 | + * | |
| 63 | + * @param imageUri Loading image URI | |
| 64 | + * @param view View for image. Can be <b>null</b>. | |
| 65 | + */ | |
| 66 | + void onLoadingCancelled(String imageUri, View view); | |
| 67 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/listener/ImageLoadingProgressListener.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.listener; | |
| 17 | + | |
| 18 | +import android.view.View; | |
| 19 | + | |
| 20 | +/** | |
| 21 | + * Listener for image loading progress. | |
| 22 | + * | |
| 23 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 24 | + * @since 1.9.1 | |
| 25 | + */ | |
| 26 | +public interface ImageLoadingProgressListener { | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * Is called when image loading progress changed. | |
| 30 | + * | |
| 31 | + * @param imageUri Image URI | |
| 32 | + * @param view View for image. Can be <b>null</b>. | |
| 33 | + * @param current Downloaded size in bytes | |
| 34 | + * @param total Total size in bytes | |
| 35 | + */ | |
| 36 | + void onProgressUpdate(String imageUri, View view, int current, int total); | |
| 37 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/listener/PauseOnScrollListener.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.listener; | |
| 17 | + | |
| 18 | +import android.widget.AbsListView; | |
| 19 | +import android.widget.AbsListView.OnScrollListener; | |
| 20 | +import android.widget.GridView; | |
| 21 | +import android.widget.ListView; | |
| 22 | +import com.nostra13.universalimageloader.core.ImageLoader; | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * Listener-helper for {@linkplain AbsListView list views} ({@link ListView}, {@link GridView}) which can | |
| 26 | + * {@linkplain ImageLoader#pause() pause ImageLoader's tasks} while list view is scrolling (touch scrolling and/or | |
| 27 | + * fling). It prevents redundant loadings.<br /> | |
| 28 | + * Set it to your list view's {@link AbsListView#setOnScrollListener(OnScrollListener) setOnScrollListener(...)}.<br /> | |
| 29 | + * This listener can wrap your custom {@linkplain OnScrollListener listener}. | |
| 30 | + * | |
| 31 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 32 | + * @since 1.7.0 | |
| 33 | + */ | |
| 34 | +public class PauseOnScrollListener implements OnScrollListener { | |
| 35 | + | |
| 36 | + private ImageLoader imageLoader; | |
| 37 | + | |
| 38 | + private final boolean pauseOnScroll; | |
| 39 | + private final boolean pauseOnFling; | |
| 40 | + private final OnScrollListener externalListener; | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * Constructor | |
| 44 | + * | |
| 45 | + * @param imageLoader {@linkplain ImageLoader} instance for controlling | |
| 46 | + * @param pauseOnScroll Whether {@linkplain ImageLoader#pause() pause ImageLoader} during touch scrolling | |
| 47 | + * @param pauseOnFling Whether {@linkplain ImageLoader#pause() pause ImageLoader} during fling | |
| 48 | + */ | |
| 49 | + public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling) { | |
| 50 | + this(imageLoader, pauseOnScroll, pauseOnFling, null); | |
| 51 | + } | |
| 52 | + | |
| 53 | + /** | |
| 54 | + * Constructor | |
| 55 | + * | |
| 56 | + * @param imageLoader {@linkplain ImageLoader} instance for controlling | |
| 57 | + * @param pauseOnScroll Whether {@linkplain ImageLoader#pause() pause ImageLoader} during touch scrolling | |
| 58 | + * @param pauseOnFling Whether {@linkplain ImageLoader#pause() pause ImageLoader} during fling | |
| 59 | + * @param customListener Your custom {@link OnScrollListener} for {@linkplain AbsListView list view} which also | |
| 60 | + * will be get scroll events | |
| 61 | + */ | |
| 62 | + public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling, | |
| 63 | + OnScrollListener customListener) { | |
| 64 | + this.imageLoader = imageLoader; | |
| 65 | + this.pauseOnScroll = pauseOnScroll; | |
| 66 | + this.pauseOnFling = pauseOnFling; | |
| 67 | + externalListener = customListener; | |
| 68 | + } | |
| 69 | + | |
| 70 | + @Override | |
| 71 | + public void onScrollStateChanged(AbsListView view, int scrollState) { | |
| 72 | + switch (scrollState) { | |
| 73 | + case OnScrollListener.SCROLL_STATE_IDLE: | |
| 74 | + imageLoader.resume(); | |
| 75 | + break; | |
| 76 | + case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: | |
| 77 | + if (pauseOnScroll) { | |
| 78 | + imageLoader.pause(); | |
| 79 | + } | |
| 80 | + break; | |
| 81 | + case OnScrollListener.SCROLL_STATE_FLING: | |
| 82 | + if (pauseOnFling) { | |
| 83 | + imageLoader.pause(); | |
| 84 | + } | |
| 85 | + break; | |
| 86 | + } | |
| 87 | + if (externalListener != null) { | |
| 88 | + externalListener.onScrollStateChanged(view, scrollState); | |
| 89 | + } | |
| 90 | + } | |
| 91 | + | |
| 92 | + @Override | |
| 93 | + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { | |
| 94 | + if (externalListener != null) { | |
| 95 | + externalListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); | |
| 96 | + } | |
| 97 | + } | |
| 98 | +} | ... | ... |
uil/src/main/java/com/nostra13/universalimageloader/core/listener/SimpleImageLoadingListener.java
0 → 100644
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.listener; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import android.view.View; | |
| 20 | +import com.nostra13.universalimageloader.core.assist.FailReason; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * A convenient class to extend when you only want to listen for a subset of all the image loading events. This | |
| 24 | + * implements all methods in the {@link ImageLoadingListener} but does | |
| 25 | + * nothing. | |
| 26 | + * | |
| 27 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 28 | + * @since 1.4.0 | |
| 29 | + */ | |
| 30 | +public class SimpleImageLoadingListener implements ImageLoadingListener { | |
| 31 | + @Override | |
| 32 | + public void onLoadingStarted(String imageUri, View view) { | |
| 33 | + // Empty implementation | |
| 34 | + } | |
| 35 | + | |
| 36 | + @Override | |
| 37 | + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { | |
| 38 | + // Empty implementation | |
| 39 | + } | |
| 40 | + | |
| 41 | + @Override | |
| 42 | + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { | |
| 43 | + // Empty implementation | |
| 44 | + } | |
| 45 | + | |
| 46 | + @Override | |
| 47 | + public void onLoadingCancelled(String imageUri, View view) { | |
| 48 | + // Empty implementation | |
| 49 | + } | |
| 50 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2013 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.core.process; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | +import com.nostra13.universalimageloader.core.DisplayImageOptions; | |
| 20 | + | |
| 21 | +/** | |
| 22 | + * Makes some processing on {@link Bitmap}. Implementations can apply any changes to original {@link Bitmap}.<br /> | |
| 23 | + * Implementations have to be thread-safe. | |
| 24 | + * | |
| 25 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 26 | + * @since 1.8.0 | |
| 27 | + */ | |
| 28 | +public interface BitmapProcessor { | |
| 29 | + /** | |
| 30 | + * Makes some processing of incoming bitmap.<br /> | |
| 31 | + * This method is executing on additional thread (not on UI thread).<br /> | |
| 32 | + * <b>Note:</b> If this processor is used as {@linkplain DisplayImageOptions.Builder#preProcessor(BitmapProcessor) | |
| 33 | + * pre-processor} then don't forget {@linkplain Bitmap#recycle() to recycle} incoming bitmap if you return a new | |
| 34 | + * created one. | |
| 35 | + * | |
| 36 | + * @param bitmap Original {@linkplain Bitmap bitmap} | |
| 37 | + * @return Processed {@linkplain Bitmap bitmap} | |
| 38 | + */ | |
| 39 | + Bitmap process(Bitmap bitmap); | |
| 40 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.utils; | |
| 17 | + | |
| 18 | +import com.nostra13.universalimageloader.cache.disc.DiskCache; | |
| 19 | + | |
| 20 | +import java.io.File; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * Utility for convenient work with disk cache.<br /> | |
| 24 | + * <b>NOTE:</b> This utility works with file system so avoid using it on application main thread. | |
| 25 | + * | |
| 26 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 27 | + * @since 1.8.0 | |
| 28 | + */ | |
| 29 | +public final class DiskCacheUtils { | |
| 30 | + | |
| 31 | + private DiskCacheUtils() { | |
| 32 | + } | |
| 33 | + | |
| 34 | + /** Returns {@link File} of cached image or <b>null</b> if image was not cached in disk cache */ | |
| 35 | + public static File findInCache(String imageUri, DiskCache diskCache) { | |
| 36 | + File image = diskCache.get(imageUri); | |
| 37 | + return image != null && image.exists() ? image : null; | |
| 38 | + } | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * Removed cached image file from disk cache (if image was cached in disk cache before) | |
| 42 | + * | |
| 43 | + * @return <b>true</b> - if cached image file existed and was deleted; <b>false</b> - otherwise. | |
| 44 | + */ | |
| 45 | + public static boolean removeFromCache(String imageUri, DiskCache diskCache) { | |
| 46 | + File image = diskCache.get(imageUri); | |
| 47 | + return image != null && image.exists() && image.delete(); | |
| 48 | + } | |
| 49 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2013-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.utils; | |
| 17 | + | |
| 18 | +import android.graphics.BitmapFactory; | |
| 19 | +import android.opengl.GLES10; | |
| 20 | +import com.nostra13.universalimageloader.core.assist.ImageSize; | |
| 21 | +import com.nostra13.universalimageloader.core.assist.ViewScaleType; | |
| 22 | +import com.nostra13.universalimageloader.core.imageaware.ImageAware; | |
| 23 | + | |
| 24 | +import javax.microedition.khronos.opengles.GL10; | |
| 25 | + | |
| 26 | +/** | |
| 27 | + * Provides calculations with image sizes, scales | |
| 28 | + * | |
| 29 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 30 | + * @since 1.8.3 | |
| 31 | + */ | |
| 32 | +public final class ImageSizeUtils { | |
| 33 | + | |
| 34 | + private static final int DEFAULT_MAX_BITMAP_DIMENSION = 2048; | |
| 35 | + | |
| 36 | + private static ImageSize maxBitmapSize; | |
| 37 | + | |
| 38 | + static { | |
| 39 | + int[] maxTextureSize = new int[1]; | |
| 40 | + GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0); | |
| 41 | + int maxBitmapDimension = Math.max(maxTextureSize[0], DEFAULT_MAX_BITMAP_DIMENSION); | |
| 42 | + maxBitmapSize = new ImageSize(maxBitmapDimension, maxBitmapDimension); | |
| 43 | + } | |
| 44 | + | |
| 45 | + private ImageSizeUtils() { | |
| 46 | + } | |
| 47 | + | |
| 48 | + /** | |
| 49 | + * Defines target size for image aware view. Size is defined by target | |
| 50 | + * {@link ImageAware view} parameters, configuration | |
| 51 | + * parameters or device display dimensions.<br /> | |
| 52 | + */ | |
| 53 | + public static ImageSize defineTargetSizeForView(ImageAware imageAware, ImageSize maxImageSize) { | |
| 54 | + int width = imageAware.getWidth(); | |
| 55 | + if (width <= 0) width = maxImageSize.getWidth(); | |
| 56 | + | |
| 57 | + int height = imageAware.getHeight(); | |
| 58 | + if (height <= 0) height = maxImageSize.getHeight(); | |
| 59 | + | |
| 60 | + return new ImageSize(width, height); | |
| 61 | + } | |
| 62 | + | |
| 63 | + /** | |
| 64 | + * Computes sample size for downscaling image size (<b>srcSize</b>) to view size (<b>targetSize</b>). This sample | |
| 65 | + * size is used during | |
| 66 | + * {@linkplain BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, BitmapFactory.Options) | |
| 67 | + * decoding image} to bitmap.<br /> | |
| 68 | + * <br /> | |
| 69 | + * <b>Examples:</b><br /> | |
| 70 | + * <p/> | |
| 71 | + * <pre> | |
| 72 | + * srcSize(100x100), targetSize(10x10), powerOf2Scale = true -> sampleSize = 8 | |
| 73 | + * srcSize(100x100), targetSize(10x10), powerOf2Scale = false -> sampleSize = 10 | |
| 74 | + * | |
| 75 | + * srcSize(100x100), targetSize(20x40), viewScaleType = FIT_INSIDE -> sampleSize = 5 | |
| 76 | + * srcSize(100x100), targetSize(20x40), viewScaleType = CROP -> sampleSize = 2 | |
| 77 | + * </pre> | |
| 78 | + * <p/> | |
| 79 | + * <br /> | |
| 80 | + * The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded | |
| 81 | + * bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 | |
| 82 | + * the number of pixels. Any value <= 1 is treated the same as 1. | |
| 83 | + * | |
| 84 | + * @param srcSize Original (image) size | |
| 85 | + * @param targetSize Target (view) size | |
| 86 | + * @param viewScaleType {@linkplain ViewScaleType Scale type} for placing image in view | |
| 87 | + * @param powerOf2Scale <i>true</i> - if sample size be a power of 2 (1, 2, 4, 8, ...) | |
| 88 | + * @return Computed sample size | |
| 89 | + */ | |
| 90 | + public static int computeImageSampleSize(ImageSize srcSize, ImageSize targetSize, ViewScaleType viewScaleType, | |
| 91 | + boolean powerOf2Scale) { | |
| 92 | + final int srcWidth = srcSize.getWidth(); | |
| 93 | + final int srcHeight = srcSize.getHeight(); | |
| 94 | + final int targetWidth = targetSize.getWidth(); | |
| 95 | + final int targetHeight = targetSize.getHeight(); | |
| 96 | + | |
| 97 | + int scale = 1; | |
| 98 | + | |
| 99 | + switch (viewScaleType) { | |
| 100 | + case FIT_INSIDE: | |
| 101 | + if (powerOf2Scale) { | |
| 102 | + final int halfWidth = srcWidth / 2; | |
| 103 | + final int halfHeight = srcHeight / 2; | |
| 104 | + while ((halfWidth / scale) > targetWidth || (halfHeight / scale) > targetHeight) { // || | |
| 105 | + scale *= 2; | |
| 106 | + } | |
| 107 | + } else { | |
| 108 | + scale = Math.max(srcWidth / targetWidth, srcHeight / targetHeight); // max | |
| 109 | + } | |
| 110 | + break; | |
| 111 | + case CROP: | |
| 112 | + if (powerOf2Scale) { | |
| 113 | + final int halfWidth = srcWidth / 2; | |
| 114 | + final int halfHeight = srcHeight / 2; | |
| 115 | + while ((halfWidth / scale) > targetWidth && (halfHeight / scale) > targetHeight) { // && | |
| 116 | + scale *= 2; | |
| 117 | + } | |
| 118 | + } else { | |
| 119 | + scale = Math.min(srcWidth / targetWidth, srcHeight / targetHeight); // min | |
| 120 | + } | |
| 121 | + break; | |
| 122 | + } | |
| 123 | + | |
| 124 | + if (scale < 1) { | |
| 125 | + scale = 1; | |
| 126 | + } | |
| 127 | + scale = considerMaxTextureSize(srcWidth, srcHeight, scale, powerOf2Scale); | |
| 128 | + | |
| 129 | + return scale; | |
| 130 | + } | |
| 131 | + | |
| 132 | + private static int considerMaxTextureSize(int srcWidth, int srcHeight, int scale, boolean powerOf2) { | |
| 133 | + final int maxWidth = maxBitmapSize.getWidth(); | |
| 134 | + final int maxHeight = maxBitmapSize.getHeight(); | |
| 135 | + while ((srcWidth / scale) > maxWidth || (srcHeight / scale) > maxHeight) { | |
| 136 | + if (powerOf2) { | |
| 137 | + scale *= 2; | |
| 138 | + } else { | |
| 139 | + scale++; | |
| 140 | + } | |
| 141 | + } | |
| 142 | + return scale; | |
| 143 | + } | |
| 144 | + | |
| 145 | + /** | |
| 146 | + * Computes minimal sample size for downscaling image so result image size won't exceed max acceptable OpenGL | |
| 147 | + * texture size.<br /> | |
| 148 | + * We can't create Bitmap in memory with size exceed max texture size (usually this is 2048x2048) so this method | |
| 149 | + * calculate minimal sample size which should be applied to image to fit into these limits. | |
| 150 | + * | |
| 151 | + * @param srcSize Original image size | |
| 152 | + * @return Minimal sample size | |
| 153 | + */ | |
| 154 | + public static int computeMinImageSampleSize(ImageSize srcSize) { | |
| 155 | + final int srcWidth = srcSize.getWidth(); | |
| 156 | + final int srcHeight = srcSize.getHeight(); | |
| 157 | + final int targetWidth = maxBitmapSize.getWidth(); | |
| 158 | + final int targetHeight = maxBitmapSize.getHeight(); | |
| 159 | + | |
| 160 | + final int widthScale = (int) Math.ceil((float) srcWidth / targetWidth); | |
| 161 | + final int heightScale = (int) Math.ceil((float) srcHeight / targetHeight); | |
| 162 | + | |
| 163 | + return Math.max(widthScale, heightScale); // max | |
| 164 | + } | |
| 165 | + | |
| 166 | + /** | |
| 167 | + * Computes scale of target size (<b>targetSize</b>) to source size (<b>srcSize</b>).<br /> | |
| 168 | + * <br /> | |
| 169 | + * <b>Examples:</b><br /> | |
| 170 | + * <p/> | |
| 171 | + * <pre> | |
| 172 | + * srcSize(40x40), targetSize(10x10) -> scale = 0.25 | |
| 173 | + * | |
| 174 | + * srcSize(10x10), targetSize(20x20), stretch = false -> scale = 1 | |
| 175 | + * srcSize(10x10), targetSize(20x20), stretch = true -> scale = 2 | |
| 176 | + * | |
| 177 | + * srcSize(100x100), targetSize(20x40), viewScaleType = FIT_INSIDE -> scale = 0.2 | |
| 178 | + * srcSize(100x100), targetSize(20x40), viewScaleType = CROP -> scale = 0.4 | |
| 179 | + * </pre> | |
| 180 | + * | |
| 181 | + * @param srcSize Source (image) size | |
| 182 | + * @param targetSize Target (view) size | |
| 183 | + * @param viewScaleType {@linkplain ViewScaleType Scale type} for placing image in view | |
| 184 | + * @param stretch Whether source size should be stretched if target size is larger than source size. If <b>false</b> | |
| 185 | + * then result scale value can't be greater than 1. | |
| 186 | + * @return Computed scale | |
| 187 | + */ | |
| 188 | + public static float computeImageScale(ImageSize srcSize, ImageSize targetSize, ViewScaleType viewScaleType, | |
| 189 | + boolean stretch) { | |
| 190 | + final int srcWidth = srcSize.getWidth(); | |
| 191 | + final int srcHeight = srcSize.getHeight(); | |
| 192 | + final int targetWidth = targetSize.getWidth(); | |
| 193 | + final int targetHeight = targetSize.getHeight(); | |
| 194 | + | |
| 195 | + final float widthScale = (float) srcWidth / targetWidth; | |
| 196 | + final float heightScale = (float) srcHeight / targetHeight; | |
| 197 | + | |
| 198 | + final int destWidth; | |
| 199 | + final int destHeight; | |
| 200 | + if ((viewScaleType == ViewScaleType.FIT_INSIDE && widthScale >= heightScale) || (viewScaleType == ViewScaleType.CROP && widthScale < heightScale)) { | |
| 201 | + destWidth = targetWidth; | |
| 202 | + destHeight = (int) (srcHeight / widthScale); | |
| 203 | + } else { | |
| 204 | + destWidth = (int) (srcWidth / heightScale); | |
| 205 | + destHeight = targetHeight; | |
| 206 | + } | |
| 207 | + | |
| 208 | + float scale = 1; | |
| 209 | + if ((!stretch && destWidth < srcWidth && destHeight < srcHeight) || (stretch && destWidth != srcWidth && destHeight != srcHeight)) { | |
| 210 | + scale = (float) destWidth / srcWidth; | |
| 211 | + } | |
| 212 | + | |
| 213 | + return scale; | |
| 214 | + } | |
| 215 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.utils; | |
| 17 | + | |
| 18 | +import java.io.Closeable; | |
| 19 | +import java.io.IOException; | |
| 20 | +import java.io.InputStream; | |
| 21 | +import java.io.OutputStream; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * Provides I/O operations | |
| 25 | + * | |
| 26 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 27 | + * @since 1.0.0 | |
| 28 | + */ | |
| 29 | +public final class IoUtils { | |
| 30 | + | |
| 31 | + /** {@value} */ | |
| 32 | + public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 KB | |
| 33 | + /** {@value} */ | |
| 34 | + public static final int DEFAULT_IMAGE_TOTAL_SIZE = 500 * 1024; // 500 Kb | |
| 35 | + /** {@value} */ | |
| 36 | + public static final int CONTINUE_LOADING_PERCENTAGE = 75; | |
| 37 | + | |
| 38 | + private IoUtils() { | |
| 39 | + } | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * Copies stream, fires progress events by listener, can be interrupted by listener. Uses buffer size = | |
| 43 | + * {@value #DEFAULT_BUFFER_SIZE} bytes. | |
| 44 | + * | |
| 45 | + * @param is Input stream | |
| 46 | + * @param os Output stream | |
| 47 | + * @param listener null-ok; Listener of copying progress and controller of copying interrupting | |
| 48 | + * @return <b>true</b> - if stream copied successfully; <b>false</b> - if copying was interrupted by listener | |
| 49 | + * @throws IOException | |
| 50 | + */ | |
| 51 | + public static boolean copyStream(InputStream is, OutputStream os, CopyListener listener) throws IOException { | |
| 52 | + return copyStream(is, os, listener, DEFAULT_BUFFER_SIZE); | |
| 53 | + } | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * Copies stream, fires progress events by listener, can be interrupted by listener. | |
| 57 | + * | |
| 58 | + * @param is Input stream | |
| 59 | + * @param os Output stream | |
| 60 | + * @param listener null-ok; Listener of copying progress and controller of copying interrupting | |
| 61 | + * @param bufferSize Buffer size for copying, also represents a step for firing progress listener callback, i.e. | |
| 62 | + * progress event will be fired after every copied <b>bufferSize</b> bytes | |
| 63 | + * @return <b>true</b> - if stream copied successfully; <b>false</b> - if copying was interrupted by listener | |
| 64 | + * @throws IOException | |
| 65 | + */ | |
| 66 | + public static boolean copyStream(InputStream is, OutputStream os, CopyListener listener, int bufferSize) | |
| 67 | + throws IOException { | |
| 68 | + int current = 0; | |
| 69 | + int total = is.available(); | |
| 70 | + if (total <= 0) { | |
| 71 | + total = DEFAULT_IMAGE_TOTAL_SIZE; | |
| 72 | + } | |
| 73 | + | |
| 74 | + final byte[] bytes = new byte[bufferSize]; | |
| 75 | + int count; | |
| 76 | + if (shouldStopLoading(listener, current, total)) return false; | |
| 77 | + while ((count = is.read(bytes, 0, bufferSize)) != -1) { | |
| 78 | + os.write(bytes, 0, count); | |
| 79 | + current += count; | |
| 80 | + if (shouldStopLoading(listener, current, total)) return false; | |
| 81 | + } | |
| 82 | + os.flush(); | |
| 83 | + return true; | |
| 84 | + } | |
| 85 | + | |
| 86 | + private static boolean shouldStopLoading(CopyListener listener, int current, int total) { | |
| 87 | + if (listener != null) { | |
| 88 | + boolean shouldContinue = listener.onBytesCopied(current, total); | |
| 89 | + if (!shouldContinue) { | |
| 90 | + if (100 * current / total < CONTINUE_LOADING_PERCENTAGE) { | |
| 91 | + return true; // if loaded more than 75% then continue loading anyway | |
| 92 | + } | |
| 93 | + } | |
| 94 | + } | |
| 95 | + return false; | |
| 96 | + } | |
| 97 | + | |
| 98 | + /** | |
| 99 | + * Reads all data from stream and close it silently | |
| 100 | + * | |
| 101 | + * @param is Input stream | |
| 102 | + */ | |
| 103 | + public static void readAndCloseStream(InputStream is) { | |
| 104 | + final byte[] bytes = new byte[DEFAULT_BUFFER_SIZE]; | |
| 105 | + try { | |
| 106 | + while (is.read(bytes, 0, DEFAULT_BUFFER_SIZE) != -1); | |
| 107 | + } catch (IOException ignored) { | |
| 108 | + } finally { | |
| 109 | + closeSilently(is); | |
| 110 | + } | |
| 111 | + } | |
| 112 | + | |
| 113 | + public static void closeSilently(Closeable closeable) { | |
| 114 | + if (closeable != null) { | |
| 115 | + try { | |
| 116 | + closeable.close(); | |
| 117 | + } catch (Exception ignored) { | |
| 118 | + } | |
| 119 | + } | |
| 120 | + } | |
| 121 | + | |
| 122 | + /** Listener and controller for copy process */ | |
| 123 | + public static interface CopyListener { | |
| 124 | + /** | |
| 125 | + * @param current Loaded bytes | |
| 126 | + * @param total Total bytes for loading | |
| 127 | + * @return <b>true</b> - if copying should be continued; <b>false</b> - if copying should be interrupted | |
| 128 | + */ | |
| 129 | + boolean onBytesCopied(int current, int total); | |
| 130 | + } | |
| 131 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.utils; | |
| 17 | + | |
| 18 | +import android.util.Log; | |
| 19 | +import com.nostra13.universalimageloader.core.ImageLoader; | |
| 20 | + | |
| 21 | +/** | |
| 22 | + * "Less-word" analog of Android {@link Log logger} | |
| 23 | + * | |
| 24 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 25 | + * @since 1.6.4 | |
| 26 | + */ | |
| 27 | +public final class L { | |
| 28 | + | |
| 29 | + private static final String LOG_FORMAT = "%1$s\n%2$s"; | |
| 30 | + private static volatile boolean writeDebugLogs = false; | |
| 31 | + private static volatile boolean writeLogs = true; | |
| 32 | + | |
| 33 | + private L() { | |
| 34 | + } | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * Enables logger (if {@link #disableLogging()} was called before) | |
| 38 | + * | |
| 39 | + * @deprecated Use {@link #writeLogs(boolean) writeLogs(true)} instead | |
| 40 | + */ | |
| 41 | + @Deprecated | |
| 42 | + public static void enableLogging() { | |
| 43 | + writeLogs(true); | |
| 44 | + } | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * Disables logger, no logs will be passed to LogCat, all log methods will do nothing | |
| 48 | + * | |
| 49 | + * @deprecated Use {@link #writeLogs(boolean) writeLogs(false)} instead | |
| 50 | + */ | |
| 51 | + @Deprecated | |
| 52 | + public static void disableLogging() { | |
| 53 | + writeLogs(false); | |
| 54 | + } | |
| 55 | + | |
| 56 | + /** | |
| 57 | + * Enables/disables detail logging of {@link ImageLoader} work. | |
| 58 | + * Consider {@link L#disableLogging()} to disable | |
| 59 | + * ImageLoader logging completely (even error logs)<br /> | |
| 60 | + * Debug logs are disabled by default. | |
| 61 | + */ | |
| 62 | + public static void writeDebugLogs(boolean writeDebugLogs) { | |
| 63 | + L.writeDebugLogs = writeDebugLogs; | |
| 64 | + } | |
| 65 | + | |
| 66 | + /** Enables/disables logging of {@link ImageLoader} completely (even error logs). */ | |
| 67 | + public static void writeLogs(boolean writeLogs) { | |
| 68 | + L.writeLogs = writeLogs; | |
| 69 | + } | |
| 70 | + | |
| 71 | + public static void d(String message, Object... args) { | |
| 72 | + if (writeDebugLogs) { | |
| 73 | + log(Log.DEBUG, null, message, args); | |
| 74 | + } | |
| 75 | + } | |
| 76 | + | |
| 77 | + public static void i(String message, Object... args) { | |
| 78 | + log(Log.INFO, null, message, args); | |
| 79 | + } | |
| 80 | + | |
| 81 | + public static void w(String message, Object... args) { | |
| 82 | + log(Log.WARN, null, message, args); | |
| 83 | + } | |
| 84 | + | |
| 85 | + public static void e(Throwable ex) { | |
| 86 | + log(Log.ERROR, ex, null); | |
| 87 | + } | |
| 88 | + | |
| 89 | + public static void e(String message, Object... args) { | |
| 90 | + log(Log.ERROR, null, message, args); | |
| 91 | + } | |
| 92 | + | |
| 93 | + public static void e(Throwable ex, String message, Object... args) { | |
| 94 | + log(Log.ERROR, ex, message, args); | |
| 95 | + } | |
| 96 | + | |
| 97 | + private static void log(int priority, Throwable ex, String message, Object... args) { | |
| 98 | + if (!writeLogs) return; | |
| 99 | + if (args.length > 0) { | |
| 100 | + message = String.format(message, args); | |
| 101 | + } | |
| 102 | + | |
| 103 | + String log; | |
| 104 | + if (ex == null) { | |
| 105 | + log = message; | |
| 106 | + } else { | |
| 107 | + String logMessage = message == null ? ex.getMessage() : message; | |
| 108 | + String logBody = Log.getStackTraceString(ex); | |
| 109 | + log = String.format(LOG_FORMAT, logMessage, logBody); | |
| 110 | + } | |
| 111 | + Log.println(priority, ImageLoader.TAG, log); | |
| 112 | + } | |
| 113 | +} | |
| \ No newline at end of file | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.utils; | |
| 17 | + | |
| 18 | +import android.graphics.Bitmap; | |
| 19 | + | |
| 20 | +import com.nostra13.universalimageloader.cache.memory.MemoryCache; | |
| 21 | +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; | |
| 22 | +import com.nostra13.universalimageloader.core.assist.ImageSize; | |
| 23 | + | |
| 24 | +import java.util.ArrayList; | |
| 25 | +import java.util.Comparator; | |
| 26 | +import java.util.List; | |
| 27 | + | |
| 28 | +/** | |
| 29 | + * Utility for generating of keys for memory cache, key comparing and other work with memory cache | |
| 30 | + * | |
| 31 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 32 | + * @since 1.6.3 | |
| 33 | + */ | |
| 34 | +public final class MemoryCacheUtils { | |
| 35 | + | |
| 36 | + private static final String URI_AND_SIZE_SEPARATOR = "_"; | |
| 37 | + private static final String WIDTH_AND_HEIGHT_SEPARATOR = "x"; | |
| 38 | + | |
| 39 | + private MemoryCacheUtils() { | |
| 40 | + } | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * Generates key for memory cache for incoming image (URI + size).<br /> | |
| 44 | + * Pattern for cache key - <b>[imageUri]_[width]x[height]</b>. | |
| 45 | + */ | |
| 46 | + public static String generateKey(String imageUri, ImageSize targetSize) { | |
| 47 | + return new StringBuilder(imageUri).append(URI_AND_SIZE_SEPARATOR).append(targetSize.getWidth()).append(WIDTH_AND_HEIGHT_SEPARATOR).append(targetSize.getHeight()).toString(); | |
| 48 | + } | |
| 49 | + | |
| 50 | + public static Comparator<String> createFuzzyKeyComparator() { | |
| 51 | + return new Comparator<String>() { | |
| 52 | + @Override | |
| 53 | + public int compare(String key1, String key2) { | |
| 54 | + String imageUri1 = key1.substring(0, key1.lastIndexOf(URI_AND_SIZE_SEPARATOR)); | |
| 55 | + String imageUri2 = key2.substring(0, key2.lastIndexOf(URI_AND_SIZE_SEPARATOR)); | |
| 56 | + return imageUri1.compareTo(imageUri2); | |
| 57 | + } | |
| 58 | + }; | |
| 59 | + } | |
| 60 | + | |
| 61 | + /** | |
| 62 | + * Searches all bitmaps in memory cache which are corresponded to incoming URI.<br /> | |
| 63 | + * <b>Note:</b> Memory cache can contain multiple sizes of the same image if only you didn't set | |
| 64 | + * {@link ImageLoaderConfiguration.Builder#denyCacheImageMultipleSizesInMemory() | |
| 65 | + * denyCacheImageMultipleSizesInMemory()} option in {@linkplain ImageLoaderConfiguration configuration} | |
| 66 | + */ | |
| 67 | + public static List<Bitmap> findCachedBitmapsForImageUri(String imageUri, MemoryCache memoryCache) { | |
| 68 | + List<Bitmap> values = new ArrayList<Bitmap>(); | |
| 69 | + for (String key : memoryCache.keys()) { | |
| 70 | + if (key.startsWith(imageUri)) { | |
| 71 | + values.add(memoryCache.get(key)); | |
| 72 | + } | |
| 73 | + } | |
| 74 | + return values; | |
| 75 | + } | |
| 76 | + | |
| 77 | + /** | |
| 78 | + * Searches all keys in memory cache which are corresponded to incoming URI.<br /> | |
| 79 | + * <b>Note:</b> Memory cache can contain multiple sizes of the same image if only you didn't set | |
| 80 | + * {@link ImageLoaderConfiguration.Builder#denyCacheImageMultipleSizesInMemory() | |
| 81 | + * denyCacheImageMultipleSizesInMemory()} option in {@linkplain ImageLoaderConfiguration configuration} | |
| 82 | + */ | |
| 83 | + public static List<String> findCacheKeysForImageUri(String imageUri, MemoryCache memoryCache) { | |
| 84 | + List<String> values = new ArrayList<String>(); | |
| 85 | + for (String key : memoryCache.keys()) { | |
| 86 | + if (key.startsWith(imageUri)) { | |
| 87 | + values.add(key); | |
| 88 | + } | |
| 89 | + } | |
| 90 | + return values; | |
| 91 | + } | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * Removes from memory cache all images for incoming URI.<br /> | |
| 95 | + * <b>Note:</b> Memory cache can contain multiple sizes of the same image if only you didn't set | |
| 96 | + * {@link ImageLoaderConfiguration.Builder#denyCacheImageMultipleSizesInMemory() | |
| 97 | + * denyCacheImageMultipleSizesInMemory()} option in {@linkplain ImageLoaderConfiguration configuration} | |
| 98 | + */ | |
| 99 | + public static void removeFromCache(String imageUri, MemoryCache memoryCache) { | |
| 100 | + List<String> keysToRemove = new ArrayList<String>(); | |
| 101 | + for (String key : memoryCache.keys()) { | |
| 102 | + if (key.startsWith(imageUri)) { | |
| 103 | + keysToRemove.add(key); | |
| 104 | + } | |
| 105 | + } | |
| 106 | + for (String keyToRemove : keysToRemove) { | |
| 107 | + memoryCache.remove(keyToRemove); | |
| 108 | + } | |
| 109 | + } | |
| 110 | +} | ... | ... |
| 1 | +/******************************************************************************* | |
| 2 | + * Copyright 2011-2014 Sergey Tarasevich | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + *******************************************************************************/ | |
| 16 | +package com.nostra13.universalimageloader.utils; | |
| 17 | + | |
| 18 | +import android.content.Context; | |
| 19 | +import android.content.pm.PackageManager; | |
| 20 | +import android.os.Environment; | |
| 21 | + | |
| 22 | +import java.io.File; | |
| 23 | +import java.io.IOException; | |
| 24 | + | |
| 25 | +import static android.os.Environment.MEDIA_MOUNTED; | |
| 26 | + | |
| 27 | +/** | |
| 28 | + * Provides application storage paths | |
| 29 | + * | |
| 30 | + * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) | |
| 31 | + * @since 1.0.0 | |
| 32 | + */ | |
| 33 | +public final class StorageUtils { | |
| 34 | + | |
| 35 | + private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE"; | |
| 36 | + private static final String INDIVIDUAL_DIR_NAME = "uil-images"; | |
| 37 | + | |
| 38 | + private StorageUtils() { | |
| 39 | + } | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * Returns application cache directory. Cache directory will be created on SD card | |
| 43 | + * <i>("/Android/data/[app_package_name]/cache")</i> if card is mounted and app has appropriate permission. Else - | |
| 44 | + * Android defines cache directory on device's file system. | |
| 45 | + * | |
| 46 | + * @param context Application context | |
| 47 | + * @return Cache {@link File directory}.<br /> | |
| 48 | + * <b>NOTE:</b> Can be null in some unpredictable cases (if SD card is unmounted and | |
| 49 | + * {@link Context#getCacheDir() Context.getCacheDir()} returns null). | |
| 50 | + */ | |
| 51 | + public static File getCacheDirectory(Context context) { | |
| 52 | + return getCacheDirectory(context, true); | |
| 53 | + } | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * Returns application cache directory. Cache directory will be created on SD card | |
| 57 | + * <i>("/Android/data/[app_package_name]/cache")</i> (if card is mounted and app has appropriate permission) or | |
| 58 | + * on device's file system depending incoming parameters. | |
| 59 | + * | |
| 60 | + * @param context Application context | |
| 61 | + * @param preferExternal Whether prefer external location for cache | |
| 62 | + * @return Cache {@link File directory}.<br /> | |
| 63 | + * <b>NOTE:</b> Can be null in some unpredictable cases (if SD card is unmounted and | |
| 64 | + * {@link Context#getCacheDir() Context.getCacheDir()} returns null). | |
| 65 | + */ | |
| 66 | + public static File getCacheDirectory(Context context, boolean preferExternal) { | |
| 67 | + File appCacheDir = null; | |
| 68 | + String externalStorageState; | |
| 69 | + try { | |
| 70 | + externalStorageState = Environment.getExternalStorageState(); | |
| 71 | + } catch (NullPointerException e) { // (sh)it happens (Issue #660) | |
| 72 | + externalStorageState = ""; | |
| 73 | + } catch (IncompatibleClassChangeError e) { // (sh)it happens too (Issue #989) | |
| 74 | + externalStorageState = ""; | |
| 75 | + } | |
| 76 | + if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState) && hasExternalStoragePermission(context)) { | |
| 77 | + appCacheDir = getExternalCacheDir(context); | |
| 78 | + } | |
| 79 | + if (appCacheDir == null) { | |
| 80 | + appCacheDir = context.getCacheDir(); | |
| 81 | + } | |
| 82 | + if (appCacheDir == null) { | |
| 83 | + String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/"; | |
| 84 | + L.w("Can't define system cache directory! '%s' will be used.", cacheDirPath); | |
| 85 | + appCacheDir = new File(cacheDirPath); | |
| 86 | + } | |
| 87 | + return appCacheDir; | |
| 88 | + } | |
| 89 | + | |
| 90 | + /** | |
| 91 | + * Returns individual application cache directory (for only image caching from ImageLoader). Cache directory will be | |
| 92 | + * created on SD card <i>("/Android/data/[app_package_name]/cache/uil-images")</i> if card is mounted and app has | |
| 93 | + * appropriate permission. Else - Android defines cache directory on device's file system. | |
| 94 | + * | |
| 95 | + * @param context Application context | |
| 96 | + * @return Cache {@link File directory} | |
| 97 | + */ | |
| 98 | + public static File getIndividualCacheDirectory(Context context) { | |
| 99 | + return getIndividualCacheDirectory(context, INDIVIDUAL_DIR_NAME); | |
| 100 | + } | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * Returns individual application cache directory (for only image caching from ImageLoader). Cache directory will be | |
| 104 | + * created on SD card <i>("/Android/data/[app_package_name]/cache/uil-images")</i> if card is mounted and app has | |
| 105 | + * appropriate permission. Else - Android defines cache directory on device's file system. | |
| 106 | + * | |
| 107 | + * @param context Application context | |
| 108 | + * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images") | |
| 109 | + * @return Cache {@link File directory} | |
| 110 | + */ | |
| 111 | + public static File getIndividualCacheDirectory(Context context, String cacheDir) { | |
| 112 | + File appCacheDir = getCacheDirectory(context); | |
| 113 | + File individualCacheDir = new File(appCacheDir, cacheDir); | |
| 114 | + if (!individualCacheDir.exists()) { | |
| 115 | + if (!individualCacheDir.mkdir()) { | |
| 116 | + individualCacheDir = appCacheDir; | |
| 117 | + } | |
| 118 | + } | |
| 119 | + return individualCacheDir; | |
| 120 | + } | |
| 121 | + | |
| 122 | + /** | |
| 123 | + * Returns specified application cache directory. Cache directory will be created on SD card by defined path if card | |
| 124 | + * is mounted and app has appropriate permission. Else - Android defines cache directory on device's file system. | |
| 125 | + * | |
| 126 | + * @param context Application context | |
| 127 | + * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images") | |
| 128 | + * @return Cache {@link File directory} | |
| 129 | + */ | |
| 130 | + public static File getOwnCacheDirectory(Context context, String cacheDir) { | |
| 131 | + File appCacheDir = null; | |
| 132 | + if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) { | |
| 133 | + appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir); | |
| 134 | + } | |
| 135 | + if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) { | |
| 136 | + appCacheDir = context.getCacheDir(); | |
| 137 | + } | |
| 138 | + return appCacheDir; | |
| 139 | + } | |
| 140 | + | |
| 141 | + /** | |
| 142 | + * Returns specified application cache directory. Cache directory will be created on SD card by defined path if card | |
| 143 | + * is mounted and app has appropriate permission. Else - Android defines cache directory on device's file system. | |
| 144 | + * | |
| 145 | + * @param context Application context | |
| 146 | + * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images") | |
| 147 | + * @return Cache {@link File directory} | |
| 148 | + */ | |
| 149 | + public static File getOwnCacheDirectory(Context context, String cacheDir, boolean preferExternal) { | |
| 150 | + File appCacheDir = null; | |
| 151 | + if (preferExternal && MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) { | |
| 152 | + appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir); | |
| 153 | + } | |
| 154 | + if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) { | |
| 155 | + appCacheDir = context.getCacheDir(); | |
| 156 | + } | |
| 157 | + return appCacheDir; | |
| 158 | + } | |
| 159 | + | |
| 160 | + private static File getExternalCacheDir(Context context) { | |
| 161 | + File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data"); | |
| 162 | + File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache"); | |
| 163 | + if (!appCacheDir.exists()) { | |
| 164 | + if (!appCacheDir.mkdirs()) { | |
| 165 | + L.w("Unable to create external cache directory"); | |
| 166 | + return null; | |
| 167 | + } | |
| 168 | + try { | |
| 169 | + new File(appCacheDir, ".nomedia").createNewFile(); | |
| 170 | + } catch (IOException e) { | |
| 171 | + L.i("Can't create \".nomedia\" file in application external cache directory"); | |
| 172 | + } | |
| 173 | + } | |
| 174 | + return appCacheDir; | |
| 175 | + } | |
| 176 | + | |
| 177 | + private static boolean hasExternalStoragePermission(Context context) { | |
| 178 | + int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION); | |
| 179 | + return perm == PackageManager.PERMISSION_GRANTED; | |
| 180 | + } | |
| 181 | +} | ... | ... |
uil/src/main/res/values/strings.xml
0 → 100644
| 1 | +<!-- | |
| 2 | + ~ Copyright (c) 2016. wugian | |
| 3 | + ~ Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 | + ~ you may not use this file except in compliance with the License. | |
| 5 | + ~ You may obtain a copy of the License at | |
| 6 | + ~ | |
| 7 | + ~ http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | + ~ | |
| 9 | + ~ Unless required by applicable law or agreed to in writing, software | |
| 10 | + ~ distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 | + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 | + ~ See the License for the specific language governing permissions and | |
| 13 | + ~ limitations under the License. | |
| 14 | + ~ | |
| 15 | + --> | |
| 16 | + | |
| 17 | +<resources> | |
| 18 | +</resources> | ... | ... |
Please
register
or
login
to post a comment