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 an