close
The Wayback Machine - https://web.archive.org/web/20171110032626/https://developer.android.com/samples/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/ImageWorker.html
Skip to content

Most visited

Recently visited

navigation
DisplayingBitmaps / src / com.example.android.displayingbitmaps / util /

ImageWorker.java

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
 
17
package com.example.android.displayingbitmaps.util;
18
 
19
import android.content.Context;
20
import android.content.res.Resources;
21
import android.graphics.Bitmap;
22
import android.graphics.BitmapFactory;
23
import android.graphics.drawable.BitmapDrawable;
24
import android.graphics.drawable.ColorDrawable;
25
import android.graphics.drawable.Drawable;
26
import android.graphics.drawable.TransitionDrawable;
27
import android.support.v4.app.FragmentActivity;
28
import android.support.v4.app.FragmentManager;
29
import android.widget.ImageView;
30
 
31
import com.example.android.common.logger.Log;
32
import com.example.android.displayingbitmaps.BuildConfig;
33
 
34
import java.lang.ref.WeakReference;
35
 
36
/**
37
 * This class wraps up completing some arbitrary long running work when loading a bitmap to an
38
 * ImageView. It handles things like using a memory and disk cache, running the work in a background
39
 * thread and setting a placeholder image.
40
 */
41
public abstract class ImageWorker {
42
    private static final String TAG = "ImageWorker";
43
    private static final int FADE_IN_TIME = 200;
44
 
45
    private ImageCache mImageCache;
46
    private ImageCache.ImageCacheParams mImageCacheParams;
47
    private Bitmap mLoadingBitmap;
48
    private boolean mFadeInBitmap = true;
49
    private boolean mExitTasksEarly = false;
50
    protected boolean mPauseWork = false;
51
    private final Object mPauseWorkLock = new Object();
52
 
53
    protected Resources mResources;
54
 
55
    private static final int MESSAGE_CLEAR = 0;
56
    private static final int MESSAGE_INIT_DISK_CACHE = 1;
57
    private static final int MESSAGE_FLUSH = 2;
58
    private static final int MESSAGE_CLOSE = 3;
59
 
60
    protected ImageWorker(Context context) {
61
        mResources = context.getResources();
62
    }
63
 
64
    /**
65
     * Load an image specified by the data parameter into an ImageView (override
66
     * {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and
67
     * disk cache will be used if an {@link ImageCache} has been added using
68
     * {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCache.ImageCacheParams)}. If the
69
     * image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask}
70
     * will be created to asynchronously load the bitmap.
71
     *
72
     * @param data The URL of the image to download.
73
     * @param imageView The ImageView to bind the downloaded image to.
74
     * @param listener A listener that will be called back once the image has been loaded.
75
     */
76
    public void loadImage(Object data, ImageView imageView, OnImageLoadedListener listener) {
77
        if (data == null) {
78
            return;
79
        }
80
 
81
        BitmapDrawable value = null;
82
 
83
        if (mImageCache != null) {
84
            value = mImageCache.getBitmapFromMemCache(String.valueOf(data));
85
        }
86
 
87
        if (value != null) {
88
            // Bitmap found in memory cache
89
            imageView.setImageDrawable(value);
90
            if (listener != null) {
91
                listener.onImageLoaded(true);
92
            }
93
        } else if (cancelPotentialWork(data, imageView)) {
95
            final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView, listener);
96
            final AsyncDrawable asyncDrawable =
97
                    new AsyncDrawable(mResources, mLoadingBitmap, task);
98
            imageView.setImageDrawable(asyncDrawable);
99
 
100
            // NOTE: This uses a custom version of AsyncTask that has been pulled from the
101
            // framework and slightly modified. Refer to the docs at the top of the class
102
            // for more info on what was changed.
103
            task.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR);
105
        }
106
    }
107
 
108
    /**
109
     * Load an image specified by the data parameter into an ImageView (override
110
     * {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and
111
     * disk cache will be used if an {@link ImageCache} has been added using
112
     * {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCache.ImageCacheParams)}. If the
113
     * image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask}
114
     * will be created to asynchronously load the bitmap.
115
     *
116
     * @param data The URL of the image to download.
117
     * @param imageView The ImageView to bind the downloaded image to.
118
     */
119
    public void loadImage(Object data, ImageView imageView) {
120
        loadImage(data, imageView, null);
121
    }
122
 
123
    /**
124
     * Set placeholder bitmap that shows when the the background thread is running.
125
     *
126
     * @param bitmap
127
     */
128
    public void setLoadingImage(Bitmap bitmap) {
129
        mLoadingBitmap = bitmap;
130
    }
131
 
132
    /**
133
     * Set placeholder bitmap that shows when the the background thread is running.
134
     *
135
     * @param resId
136
     */
137
    public void setLoadingImage(int resId) {
138
        mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId);
139
    }
140
 
141
    /**
142
     * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
143
     * caching.
144
     * @param fragmentManager
145
     * @param cacheParams The cache parameters to use for the image cache.
146
     */
147
    public void addImageCache(FragmentManager fragmentManager,
148
            ImageCache.ImageCacheParams cacheParams) {
149
        mImageCacheParams = cacheParams;
150
        mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);
151
        new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
152
    }
153
 
154
    /**
155
     * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
156
     * caching.
157
     * @param activity
158
     * @param diskCacheDirectoryName See
159
     * {@link ImageCache.ImageCacheParams#ImageCacheParams(android.content.Context, String)}.
160
     */
161
    public void addImageCache(FragmentActivity activity, String diskCacheDirectoryName) {
162
        mImageCacheParams = new ImageCache.ImageCacheParams(activity, diskCacheDirectoryName);
163
        mImageCache = ImageCache.getInstance(activity.getSupportFragmentManager(), mImageCacheParams);
164
        new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
165
    }
166
 
167
    /**
168
     * If set to true, the image will fade-in once it has been loaded by the background thread.
169
     */
170
    public void setImageFadeIn(boolean fadeIn) {
171
        mFadeInBitmap = fadeIn;
172
    }
173
 
174
    public void setExitTasksEarly(boolean exitTasksEarly) {
175
        mExitTasksEarly = exitTasksEarly;
176
        setPauseWork(false);
177
    }
178
 
179
    /**
180
     * Subclasses should override this to define any processing or work that must happen to produce
181
     * the final bitmap. This will be executed in a background thread and be long running. For
182
     * example, you could resize a large bitmap here, or pull down an image from the network.
183
     *
184
     * @param data The data to identify which image to process, as provided by
185
     *            {@link ImageWorker#loadImage(Object, android.widget.ImageView)}
186
     * @return The processed bitmap
187
     */
188
    protected abstract Bitmap processBitmap(Object data);
189
 
190
    /**
191
     * @return The {@link ImageCache} object currently being used by this ImageWorker.
192
     */
193
    protected ImageCache getImageCache() {
194
        return mImageCache;
195
    }
196
 
197
    /**
198
     * Cancels any pending work attached to the provided ImageView.
199
     * @param imageView
200
     */
201
    public static void cancelWork(ImageView imageView) {
202
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
203
        if (bitmapWorkerTask != null) {
204
            bitmapWorkerTask.cancel(true);
205
            if (BuildConfig.DEBUG) {
206
                final Object bitmapData = bitmapWorkerTask.mData;
207
                Log.d(TAG, "cancelWork - cancelled work for " + bitmapData);
208
            }
209
        }
210
    }
211
 
212
    /**
213
     * Returns true if the current work has been canceled or if there was no work in
214
     * progress on this image view.
215
     * Returns false if the work in progress deals with the same data. The work is not
216
     * stopped in that case.
217
     */
218
    public static boolean cancelPotentialWork(Object data, ImageView imageView) {
220
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
221
 
222
        if (bitmapWorkerTask != null) {
223
            final Object bitmapData = bitmapWorkerTask.mData;
224
            if (bitmapData == null || !bitmapData.equals(data)) {
225
                bitmapWorkerTask.cancel(true);
226
                if (BuildConfig.DEBUG) {
227
                    Log.d(TAG, "cancelPotentialWork - cancelled work for " + data);
228
                }
229
            } else {
230
                // The same work is already in progress.
231
                return false;
232
            }
233
        }
234
        return true;
236
    }
237
 
238
    /**
239
     * @param imageView Any imageView
240
     * @return Retrieve the currently active work task (if any) associated with this imageView.
241
     * null if there is no such task.
242
     */
243
    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
244
        if (imageView != null) {
245
            final Drawable drawable = imageView.getDrawable();
246
            if (drawable instanceof AsyncDrawable) {
247
                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
248
                return asyncDrawable.getBitmapWorkerTask();
249
            }
250
        }
251
        return null;
252
    }
253
 
254
    /**
255
     * The actual AsyncTask that will asynchronously process the image.
256
     */
257
    private class BitmapWorkerTask extends AsyncTask<Void, Void, BitmapDrawable> {
258
        private Object mData;
259
        private final WeakReference<ImageView> imageViewReference;
260
        private final OnImageLoadedListener mOnImageLoadedListener;
261
 
262
        public BitmapWorkerTask(Object data, ImageView imageView) {
263
            mData = data;
264
            imageViewReference = new WeakReference<ImageView>(imageView);
265
            mOnImageLoadedListener = null;
266
        }
267
 
268
        public BitmapWorkerTask(Object data, ImageView imageView, OnImageLoadedListener listener) {
269
            mData = data;
270
            imageViewReference = new WeakReference<ImageView>(imageView);
271
            mOnImageLoadedListener = listener;
272
        }
273
 
274
        /**
275
         * Background processing.
276
         */
277
        @Override
278
        protected BitmapDrawable doInBackground(Void... params) {
280
            if (BuildConfig.DEBUG) {
281
                Log.d(TAG, "doInBackground - starting work");
282
            }
283
 
284
            final String dataString = String.valueOf(mData);
285
            Bitmap bitmap = null;
286
            BitmapDrawable drawable = null;
287
 
288
            // Wait here if work is paused and the task is not cancelled
289
            synchronized (mPauseWorkLock) {
290
                while (mPauseWork && !isCancelled()) {
291
                    try {
292
                        mPauseWorkLock.wait();
293
                    } catch (InterruptedException e) {}
294
                }
295
            }
296
 
297
            // If the image cache is available and this task has not been cancelled by another
298
            // thread and the ImageView that was originally bound to this task is still bound back
299
            // to this task and our "exit early" flag is not set then try and fetch the bitmap from
300
            // the cache
301
            if (mImageCache != null && !isCancelled() && getAttachedImageView() != null
302
                    && !mExitTasksEarly) {
303
                bitmap = mImageCache.getBitmapFromDiskCache(dataString);
304
            }
305
 
306
            // If the bitmap was not found in the cache and this task has not been cancelled by
307
            // another thread and the ImageView that was originally bound to this task is still
308
            // bound back to this task and our "exit early" flag is not set, then call the main
309
            // process method (as implemented by a subclass)
310
            if (bitmap == null && !isCancelled() && getAttachedImageView() != null
311
                    && !mExitTasksEarly) {
312
                bitmap = processBitmap(mData);
313
            }
314
 
315
            // If the bitmap was processed and the image cache is available, then add the processed
316
            // bitmap to the cache for future use. Note we don't check if the task was cancelled
317
            // here, if it was, and the thread is still running, we may as well add the processed
318
            // bitmap to our cache as it might be used again in the future
319
            if (bitmap != null) {
320
                if (Utils.hasHoneycomb()) {
321
                    // Running on Honeycomb or newer, so wrap in a standard BitmapDrawable
322
                    drawable = new BitmapDrawable(mResources, bitmap);
323
                } else {
324
                    // Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable
325
                    // which will recycle automagically
326
                    drawable = new RecyclingBitmapDrawable(mResources, bitmap);
327
                }
328
 
329
                if (mImageCache != null) {
330
                    mImageCache.addBitmapToCache(dataString, drawable);
331
                }
332
            }
333
 
334
            if (BuildConfig.DEBUG) {
335
                Log.d(TAG, "doInBackground - finished work");
336
            }
337
 
338
            return drawable;
340
        }
341
 
342
        /**
343
         * Once the image is processed, associates it to the imageView
344
         */
345
        @Override
346
        protected void onPostExecute(BitmapDrawable value) {
348
            boolean success = false;
349
            // if cancel was called on this task or the "exit early" flag is set then we're done
350
            if (isCancelled() || mExitTasksEarly) {
351
                value = null;
352
            }
353
 
354
            final ImageView imageView = getAttachedImageView();
355
            if (value != null && imageView != null) {
356
                if (BuildConfig.DEBUG) {
357
                    Log.d(TAG, "onPostExecute - setting bitmap");
358
                }
359
                success = true;
360
                setImageDrawable(imageView, value);
361
            }
362
            if (mOnImageLoadedListener != null) {
363
                mOnImageLoadedListener.onImageLoaded(success);
364
            }
366
        }
367
 
368
        @Override
369
        protected void onCancelled(BitmapDrawable value) {
370
            super.onCancelled(value);
371
            synchronized (mPauseWorkLock) {
372
                mPauseWorkLock.notifyAll();
373
            }
374
        }
375
 
376
        /**
377
         * Returns the ImageView associated with this task as long as the ImageView's task still
378
         * points to this task as well. Returns null otherwise.
379
         */
380
        private ImageView getAttachedImageView() {
381
            final ImageView imageView = imageViewReference.get();
382
            final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
383
 
384
            if (this == bitmapWorkerTask) {
385
                return imageView;
386
            }
387
 
388
            return null;
389
        }
390
    }
391
 
392
    /**
393
     * Interface definition for callback on image loaded successfully.
394
     */
395
    public interface OnImageLoadedListener {
396
 
397
        /**
398
         * Called once the image has been loaded.
399
         * @param success True if the image was loaded successfully, false if
400
         *                there was an error.
401
         */
402
        void onImageLoaded(boolean success);
403
    }
404
 
405
    /**
406
     * A custom Drawable that will be attached to the imageView while the work is in progress.
407
     * Contains a reference to the actual worker task, so that it can be stopped if a new binding is
408
     * required, and makes sure that only the last started worker process can bind its result,
409
     * independently of the finish order.
410
     */
411
    private static class AsyncDrawable extends BitmapDrawable {
412
        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
413
 
414
        public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
415
            super(res, bitmap);
416
            bitmapWorkerTaskReference =
417
                new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
418
        }
419
 
420
        public BitmapWorkerTask getBitmapWorkerTask() {
421
            return bitmapWorkerTaskReference.get();
422
        }
423
    }
424
 
425
    /**
426
     * Called when the processing is complete and the final drawable should be 
427
     * set on the ImageView.
428
     *
429
     * @param imageView
430
     * @param drawable
431
     */
432
    private void setImageDrawable(ImageView imageView, Drawable drawable) {
433
        if (mFadeInBitmap) {
434
            // Transition drawable with a transparent drawable and the final drawable
435
            final TransitionDrawable td =
436
                    new TransitionDrawable(new Drawable[] {
437
                            new ColorDrawable(android.R.color.transparent),
438
                            drawable
439
                    });
440
            // Set background to loading bitmap
441
            imageView.setBackgroundDrawable(
442
                    new BitmapDrawable(mResources, mLoadingBitmap));
443
 
444
            imageView.setImageDrawable(td);
445
            td.startTransition(FADE_IN_TIME);
446
        } else {
447
            imageView.setImageDrawable(drawable);
448
        }
449
    }
450
 
451
    /**
452
     * Pause any ongoing background work. This can be used as a temporary
453
     * measure to improve performance. For example background work could
454
     * be paused when a ListView or GridView is being scrolled using a
455
     * {@link android.widget.AbsListView.OnScrollListener} to keep
456
     * scrolling smooth.
457
     * <p>
458
     * If work is paused, be sure setPauseWork(false) is called again
459
     * before your fragment or activity is destroyed (for example during
460
     * {@link android.app.Activity#onPause()}), or there is a risk the
461
     * background thread will never finish.
462
     */
463
    public void setPauseWork(boolean pauseWork) {
464
        synchronized (mPauseWorkLock) {
465
            mPauseWork = pauseWork;
466
            if (!mPauseWork) {
467
                mPauseWorkLock.notifyAll();
468
            }
469
        }
470
    }
471
 
472
    protected class CacheAsyncTask extends AsyncTask<Object, Void, Void> {
473
 
474
        @Override
475
        protected Void doInBackground(Object... params) {
476
            switch ((Integer)params[0]) {
477
                case MESSAGE_CLEAR:
478
                    clearCacheInternal();
479
                    break;
480
                case MESSAGE_INIT_DISK_CACHE:
481
                    initDiskCacheInternal();
482
                    break;
483
                case MESSAGE_FLUSH:
484
                    flushCacheInternal();
485
                    break;
486
                case MESSAGE_CLOSE:
487
                    closeCacheInternal();
488
                    break;
489
            }
490
            return null;
491
        }
492
    }
493
 
494
    protected void initDiskCacheInternal() {
495
        if (mImageCache != null) {
496
            mImageCache.initDiskCache();
497
        }
498
    }
499
 
500
    protected void clearCacheInternal() {
501
        if (mImageCache != null) {
502
            mImageCache.clearCache();
503
        }
504
    }
505
 
506
    protected void flushCacheInternal() {
507
        if (mImageCache != null) {
508
            mImageCache.flush();
509
        }
510
    }
511
 
512
    protected void closeCacheInternal() {
513
        if (mImageCache != null) {
514
            mImageCache.close();
515
            mImageCache = null;
516
        }
517
    }
518
 
519
    public void clearCache() {
520
        new CacheAsyncTask().execute(MESSAGE_CLEAR);
521
    }
522
 
523
    public void flushCache() {
524
        new CacheAsyncTask().execute(MESSAGE_FLUSH);
525
    }
526
 
527
    public void closeCache() {
528
        new CacheAsyncTask().execute(MESSAGE_CLOSE);
529
    }
530
}
This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

BERJAYA

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)