Showing
30 changed files
with
4668 additions
and
0 deletions
Too many changes to show.
To preserve performance only 30 of 30+ files are displayed.
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 | +} | ... | ... |
Please
register
or
login
to post a comment