forked from nostra13/Android-Universal-Image-Loader
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLoadAndDisplayImageTask.java
396 lines (352 loc) · 13.9 KB
/
LoadAndDisplayImageTask.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
/*******************************************************************************
* Copyright 2011-2013 Sergey Tarasevich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.nostra13.universalimageloader.core;
import android.graphics.Bitmap;
import android.os.Handler;
import android.widget.ImageView;
import com.nostra13.universalimageloader.core.assist.*;
import com.nostra13.universalimageloader.core.assist.FailReason.FailType;
import com.nostra13.universalimageloader.core.decode.ImageDecoder;
import com.nostra13.universalimageloader.core.decode.ImageDecodingInfo;
import com.nostra13.universalimageloader.core.download.ImageDownloader;
import com.nostra13.universalimageloader.core.download.ImageDownloader.Scheme;
import com.nostra13.universalimageloader.utils.L;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Reference;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
/**
* Presents load'n'display image task. Used to load image from Internet or file system, decode it to {@link Bitmap}, and
* display it in {@link ImageView} using {@link DisplayBitmapTask}.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @see ImageLoaderConfiguration
* @see ImageLoadingInfo
* @since 1.3.1
*/
final class LoadAndDisplayImageTask implements Runnable {
private static final String LOG_WAITING_FOR_RESUME = "ImageLoader is paused. Waiting... [%s]";
private static final String LOG_RESUME_AFTER_PAUSE = ".. Resume loading [%s]";
private static final String LOG_DELAY_BEFORE_LOADING = "Delay %d ms before loading... [%s]";
private static final String LOG_START_DISPLAY_IMAGE_TASK = "Start display image task [%s]";
private static final String LOG_WAITING_FOR_IMAGE_LOADED = "Image already is loading. Waiting... [%s]";
private static final String LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING = "...Get cached bitmap from memory after waiting. [%s]";
private static final String LOG_LOAD_IMAGE_FROM_NETWORK = "Load image from network [%s]";
private static final String LOG_LOAD_IMAGE_FROM_DISC_CACHE = "Load image from disc cache [%s]";
private static final String LOG_PREPROCESS_IMAGE = "PreProcess image before caching in memory [%s]";
private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]";
private static final String LOG_CACHE_IMAGE_IN_MEMORY = "Cache image in memory [%s]";
private static final String LOG_CACHE_IMAGE_ON_DISC = "Cache image on disc [%s]";
private static final String LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISC = "Process image before cache on disc [%s]";
private static final String LOG_TASK_CANCELLED_IMAGEVIEW_REUSED = "ImageView is reused for another image. Task is cancelled. [%s]";
private static final String LOG_TASK_CANCELLED_IMAGEVIEW_LOST = "ImageView was collected by GC. Task is cancelled. [%s]";
private static final String LOG_TASK_INTERRUPTED = "Task was interrupted [%s]";
private static final String ERROR_PRE_PROCESSOR_NULL = "Pre-processor returned null [%s]";
private static final String ERROR_POST_PROCESSOR_NULL = "Pre-processor returned null [%s]";
private static final String ERROR_PROCESSOR_FOR_DISC_CACHE_NULL = "Bitmap processor for disc cache returned null [%s]";
private final ImageLoaderEngine engine;
private final ImageLoadingInfo imageLoadingInfo;
private final Handler handler;
// Helper references
private final ImageLoaderConfiguration configuration;
private final ImageDownloader downloader;
private final ImageDownloader networkDeniedDownloader;
private final ImageDownloader slowNetworkDownloader;
private final ImageDecoder decoder;
private final boolean writeLogs;
final String uri;
private final String memoryCacheKey;
final Reference<ImageView> imageViewRef;
private final ImageSize targetSize;
final DisplayImageOptions options;
final ImageLoadingListener listener;
// State vars
private LoadedFrom loadedFrom = LoadedFrom.NETWORK;
private boolean imageViewCollected = false;
public LoadAndDisplayImageTask(ImageLoaderEngine engine, ImageLoadingInfo imageLoadingInfo, Handler handler) {
this.engine = engine;
this.imageLoadingInfo = imageLoadingInfo;
this.handler = handler;
configuration = engine.configuration;
downloader = configuration.downloader;
networkDeniedDownloader = configuration.networkDeniedDownloader;
slowNetworkDownloader = configuration.slowNetworkDownloader;
decoder = configuration.decoder;
writeLogs = configuration.writeLogs;
uri = imageLoadingInfo.uri;
memoryCacheKey = imageLoadingInfo.memoryCacheKey;
imageViewRef = imageLoadingInfo.imageViewRef;
targetSize = imageLoadingInfo.targetSize;
options = imageLoadingInfo.options;
listener = imageLoadingInfo.listener;
}
@Override
public void run() {
if (waitIfPaused()) return;
if (delayIfNeed()) return;
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
log(LOG_START_DISPLAY_IMAGE_TASK);
if (loadFromUriLock.isLocked()) {
log(LOG_WAITING_FOR_IMAGE_LOADED);
}
loadFromUriLock.lock();
Bitmap bmp;
try {
if (checkTaskIsNotActual()) return;
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null) {
bmp = tryLoadBitmap();
if (imageViewCollected) return; // listener callback already was fired
if (bmp == null) return; // listener callback already was fired
if (checkTaskIsNotActual() || checkTaskIsInterrupted()) return;
if (options.shouldPreProcess()) {
log(LOG_PREPROCESS_IMAGE);
bmp = options.getPreProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_PRE_PROCESSOR_NULL);
}
}
if (bmp != null && options.isCacheInMemory()) {
log(LOG_CACHE_IMAGE_IN_MEMORY);
configuration.memoryCache.put(memoryCacheKey, bmp);
}
} else {
loadedFrom = LoadedFrom.MEMORY_CACHE;
log(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING);
}
if (bmp != null && options.shouldPostProcess()) {
log(LOG_POSTPROCESS_IMAGE);
bmp = options.getPostProcessor().process(bmp);
if (bmp == null) {
L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
}
}
} finally {
loadFromUriLock.unlock();
}
if (checkTaskIsNotActual() || checkTaskIsInterrupted()) return;
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
displayBitmapTask.setLoggingEnabled(writeLogs);
handler.post(displayBitmapTask);
}
/** @return true - if task should be interrupted; false - otherwise */
private boolean waitIfPaused() {
AtomicBoolean pause = engine.getPause();
synchronized (pause) {
if (pause.get()) {
log(LOG_WAITING_FOR_RESUME);
try {
pause.wait();
} catch (InterruptedException e) {
L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
return true;
}
log(LOG_RESUME_AFTER_PAUSE);
}
}
return checkTaskIsNotActual();
}
/** @return true - if task should be interrupted; false - otherwise */
private boolean delayIfNeed() {
if (options.shouldDelayBeforeLoading()) {
log(LOG_DELAY_BEFORE_LOADING, options.getDelayBeforeLoading(), memoryCacheKey);
try {
Thread.sleep(options.getDelayBeforeLoading());
} catch (InterruptedException e) {
L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
return true;
}
return checkTaskIsNotActual();
}
return false;
}
/**
* Check whether target ImageView wasn't collected by GC and the image URI of this task matches to image URI which is actual
* for current ImageView at this moment and fire {@link ImageLoadingListener#onLoadingCancelled(String, android.view.View)}}
* event if it doesn't.
*/
private boolean checkTaskIsNotActual() {
ImageView imageView = checkImageViewRef();
return imageView == null || checkImageViewReused(imageView);
}
private ImageView checkImageViewRef() {
ImageView imageView = imageViewRef.get();
if (imageView == null) {
imageViewCollected = true;
log(LOG_TASK_CANCELLED_IMAGEVIEW_LOST);
fireCancelEvent();
}
return imageView;
}
private boolean checkImageViewReused(ImageView imageView) {
String currentCacheKey = engine.getLoadingUriForView(imageView);
// Check whether memory cache key (image URI) for current ImageView is actual.
// If ImageView is reused for another task then current task should be cancelled.
boolean imageViewWasReused = !memoryCacheKey.equals(currentCacheKey);
if (imageViewWasReused) {
log(LOG_TASK_CANCELLED_IMAGEVIEW_REUSED);
fireCancelEvent();
}
return imageViewWasReused;
}
/** Check whether the current task was interrupted */
private boolean checkTaskIsInterrupted() {
boolean interrupted = Thread.interrupted();
if (interrupted) log(LOG_TASK_INTERRUPTED);
return interrupted;
}
private Bitmap tryLoadBitmap() {
File imageFile = configuration.discCache.get(uri);
Bitmap bitmap = null;
try {
if (imageFile != null && imageFile.exists()) {
log(LOG_LOAD_IMAGE_FROM_DISC_CACHE);
loadedFrom = LoadedFrom.DISC_CACHE;
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
if (imageViewCollected) return null;
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
log(LOG_LOAD_IMAGE_FROM_NETWORK);
loadedFrom = LoadedFrom.NETWORK;
String imageUriForDecoding = options.isCacheOnDisc() ? tryCacheImageOnDisc() : uri;
if (!checkTaskIsNotActual()) {
bitmap = decodeImage(imageUriForDecoding);
if (imageViewCollected) return null;
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null);
}
}
}
} catch (IllegalStateException e) {
fireFailEvent(FailType.NETWORK_DENIED, null);
} catch (IOException e) {
L.e(e);
fireFailEvent(FailType.IO_ERROR, e);
if (imageFile != null && imageFile.exists()) {
imageFile.delete();
}
} catch (OutOfMemoryError e) {
L.e(e);
fireFailEvent(FailType.OUT_OF_MEMORY, e);
} catch (Throwable e) {
L.e(e);
fireFailEvent(FailType.UNKNOWN, e);
}
return bitmap;
}
private Bitmap decodeImage(String imageUri) throws IOException {
ImageView imageView = checkImageViewRef();
if (imageView == null) return null;
ViewScaleType viewScaleType = ViewScaleType.fromImageView(imageView);
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, targetSize, viewScaleType, getDownloader(), options);
return decoder.decode(decodingInfo);
}
/** @return Cached image URI; or original image URI if caching failed */
private String tryCacheImageOnDisc() {
log(LOG_CACHE_IMAGE_ON_DISC);
try {
int width = configuration.maxImageWidthForDiscCache;
int height = configuration.maxImageHeightForDiscCache;
boolean saved = false;
if (width > 0 || height > 0) {
saved = downloadSizedImage(width, height);
}
if (!saved) {
downloadImage();
}
File imageFile = configuration.discCache.get(uri);
if (imageFile != null && imageFile.exists()) {
return Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
} catch (IOException e) {
L.e(e);
}
return uri;
}
private boolean downloadSizedImage(int maxWidth, int maxHeight) throws IOException {
// Download, decode, compress and save image
ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options).imageScaleType(ImageScaleType.IN_SAMPLE_INT).build();
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, uri, targetImageSize, ViewScaleType.FIT_INSIDE, getDownloader(), specialOptions);
Bitmap bmp = decoder.decode(decodingInfo);
if (bmp == null) return false;
if (configuration.processorForDiscCache != null) {
log(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISC);
bmp = configuration.processorForDiscCache.process(bmp);
if (bmp == null) {
L.e(ERROR_PROCESSOR_FOR_DISC_CACHE_NULL, memoryCacheKey);
return false;
}
}
boolean saved = configuration.discCache.save(uri, bmp, configuration.imageCompressFormatForDiscCache, configuration.imageQualityForDiscCache);
bmp.recycle();
return saved;
}
private void downloadImage() throws IOException {
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
configuration.discCache.save(uri, is);
}
private void fireFailEvent(final FailType failType, final Throwable failCause) {
if (!Thread.interrupted()) {
handler.post(new Runnable() {
@Override
public void run() {
ImageView imageView = imageViewRef.get();
if (imageView != null) {
if (options.shouldShowImageResOnFail()) {
imageView.setImageResource(options.getImageResOnFail());
} else if (options.shouldShowImageOnFail()) {
imageView.setImageDrawable(options.getImageOnFail());
}
}
listener.onLoadingFailed(uri, imageView, new FailReason(failType, failCause));
}
});
}
}
private void fireCancelEvent() {
if (!Thread.interrupted()) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onLoadingCancelled(uri, imageViewRef.get());
}
});
}
}
private ImageDownloader getDownloader() {
ImageDownloader d;
if (engine.isNetworkDenied()) {
d = networkDeniedDownloader;
} else if (engine.isSlowNetwork()) {
d = slowNetworkDownloader;
} else {
d = downloader;
}
return d;
}
String getLoadingUri() {
return uri;
}
private void log(String message) {
if (writeLogs) L.d(message, memoryCacheKey);
}
private void log(String message, Object... args) {
if (writeLogs) L.d(message, args);
}
}