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.threadsample;
18 
19 import com.example.android.threadsample.PhotoDecodeRunnable.TaskRunnableDecodeMethods;
20 import com.example.android.threadsample.PhotoDownloadRunnable.TaskRunnableDownloadMethods;
21 
22 import android.graphics.Bitmap;
23 
24 import java.lang.ref.WeakReference;
25 import java.net.URL;
26 
27 /**
28  * This class manages PhotoDownloadRunnable and PhotoDownloadRunnable objects.  It does't perform
29  * the download or decode; instead, it manages persistent storage for the tasks that do the work.
30  * It does this by implementing the interfaces that the download and decode classes define, and
31  * then passing itself as an argument to the constructor of a download or decode object. In effect,
32  * this allows PhotoTask to start on a Thread, run a download in a delegate object, then
33  * run a decode, and then start over again. This class can be pooled and reused as necessary.
34  */
35 public class PhotoTask implements
36         TaskRunnableDownloadMethods, TaskRunnableDecodeMethods {
37 
38     /*
39      * Creates a weak reference to the ImageView that this Task will populate.
40      * The weak reference prevents memory leaks and crashes, because it
41      * automatically tracks the "state" of the variable it backs. If the
42      * reference becomes invalid, the weak reference is garbage- collected. This
43      * technique is important for referring to objects that are part of a
44      * component lifecycle. Using a hard reference may cause memory leaks as the
45      * value continues to change; even worse, it can cause crashes if the
46      * underlying component is destroyed. Using a weak reference to a View
47      * ensures that the reference is more transitory in nature.
48      */
49     private WeakReference<PhotoView> mImageWeakRef;
50 
51     // The image's URL
52     private URL mImageURL;
53 
54     // The width and height of the decoded image
55     private int mTargetHeight;
56     private int mTargetWidth;
57 
58     // Is the cache enabled for this transaction?
59     private boolean mCacheEnabled;
60 
61     /*
62      * Field containing the Thread this task is running on.
63      */
64     Thread mThreadThis;
65 
66     /*
67      * Fields containing references to the two runnable objects that handle downloading and
68      * decoding of the image.
69      */
70     private Runnable mDownloadRunnable;
71     private Runnable mDecodeRunnable;
72 
73     // A buffer for containing the bytes that make up the image
74     byte[] mImageBuffer;
75 
76     // The decoded image
77     private Bitmap mDecodedImage;
78 
79     // The Thread on which this task is currently running.
80     private Thread mCurrentThread;
81 
82     /*
83      * An object that contains the ThreadPool singleton.
84      */
85     private static PhotoManager sPhotoManager;
86 
87     /**
88      * Creates an PhotoTask containing a download object and a decoder object.
89      */
PhotoTask()90     PhotoTask() {
91         // Create the runnables
92         mDownloadRunnable = new PhotoDownloadRunnable(this);
93         mDecodeRunnable = new PhotoDecodeRunnable(this);
94         sPhotoManager = PhotoManager.getInstance();
95     }
96 
97     /**
98      * Initializes the Task
99      *
100      * @param photoManager A ThreadPool object
101      * @param photoView An ImageView instance that shows the downloaded image
102      * @param cacheFlag Whether caching is enabled
103      */
initializeDownloaderTask( PhotoManager photoManager, PhotoView photoView, boolean cacheFlag)104     void initializeDownloaderTask(
105             PhotoManager photoManager,
106             PhotoView photoView,
107             boolean cacheFlag)
108     {
109         // Sets this object's ThreadPool field to be the input argument
110         sPhotoManager = photoManager;
111 
112         // Gets the URL for the View
113         mImageURL = photoView.getLocation();
114 
115         // Instantiates the weak reference to the incoming view
116         mImageWeakRef = new WeakReference<PhotoView>(photoView);
117 
118         // Sets the cache flag to the input argument
119         mCacheEnabled = cacheFlag;
120 
121         // Gets the width and height of the provided ImageView
122         mTargetWidth = photoView.getWidth();
123         mTargetHeight = photoView.getHeight();
124 
125     }
126 
127     // Implements HTTPDownloaderRunnable.getByteBuffer
128     @Override
getByteBuffer()129     public byte[] getByteBuffer() {
130 
131         // Returns the global field
132         return mImageBuffer;
133     }
134 
135     /**
136      * Recycles an PhotoTask object before it's put back into the pool. One reason to do
137      * this is to avoid memory leaks.
138      */
recycle()139     void recycle() {
140 
141         // Deletes the weak reference to the imageView
142         if ( null != mImageWeakRef ) {
143             mImageWeakRef.clear();
144             mImageWeakRef = null;
145         }
146 
147         // Releases references to the byte buffer and the BitMap
148         mImageBuffer = null;
149         mDecodedImage = null;
150     }
151 
152     // Implements PhotoDownloadRunnable.getTargetWidth. Returns the global target width.
153     @Override
getTargetWidth()154     public int getTargetWidth() {
155         return mTargetWidth;
156     }
157 
158     // Implements PhotoDownloadRunnable.getTargetHeight. Returns the global target height.
159     @Override
getTargetHeight()160     public int getTargetHeight() {
161         return mTargetHeight;
162     }
163 
164     // Detects the state of caching
isCacheEnabled()165     boolean isCacheEnabled() {
166         return mCacheEnabled;
167     }
168 
169     // Implements PhotoDownloadRunnable.getImageURL. Returns the global Image URL.
170     @Override
getImageURL()171     public URL getImageURL() {
172         return mImageURL;
173     }
174 
175     // Implements PhotoDownloadRunnable.setByteBuffer. Sets the image buffer to a buffer object.
176     @Override
setByteBuffer(byte[] imageBuffer)177     public void setByteBuffer(byte[] imageBuffer) {
178         mImageBuffer = imageBuffer;
179     }
180 
181     // Delegates handling the current state of the task to the PhotoManager object
handleState(int state)182     void handleState(int state) {
183         sPhotoManager.handleState(this, state);
184     }
185 
186     // Returns the image that PhotoDecodeRunnable decoded.
getImage()187     Bitmap getImage() {
188         return mDecodedImage;
189     }
190 
191     // Returns the instance that downloaded the image
getHTTPDownloadRunnable()192     Runnable getHTTPDownloadRunnable() {
193         return mDownloadRunnable;
194     }
195 
196     // Returns the instance that decode the image
getPhotoDecodeRunnable()197     Runnable getPhotoDecodeRunnable() {
198         return mDecodeRunnable;
199     }
200 
201     // Returns the ImageView that's being constructed.
getPhotoView()202     public PhotoView getPhotoView() {
203         if ( null != mImageWeakRef ) {
204             return mImageWeakRef.get();
205         }
206         return null;
207     }
208 
209     /*
210      * Returns the Thread that this Task is running on. The method must first get a lock on a
211      * static field, in this case the ThreadPool singleton. The lock is needed because the
212      * Thread object reference is stored in the Thread object itself, and that object can be
213      * changed by processes outside of this app.
214      */
getCurrentThread()215     public Thread getCurrentThread() {
216         synchronized(sPhotoManager) {
217             return mCurrentThread;
218         }
219     }
220 
221     /*
222      * Sets the identifier for the current Thread. This must be a synchronized operation; see the
223      * notes for getCurrentThread()
224      */
setCurrentThread(Thread thread)225     public void setCurrentThread(Thread thread) {
226         synchronized(sPhotoManager) {
227             mCurrentThread = thread;
228         }
229     }
230 
231     // Implements ImageCoderRunnable.setImage(). Sets the Bitmap for the current image.
232     @Override
setImage(Bitmap decodedImage)233     public void setImage(Bitmap decodedImage) {
234         mDecodedImage = decodedImage;
235     }
236 
237     // Implements PhotoDownloadRunnable.setHTTPDownloadThread(). Calls setCurrentThread().
238     @Override
setDownloadThread(Thread currentThread)239     public void setDownloadThread(Thread currentThread) {
240         setCurrentThread(currentThread);
241     }
242 
243     /*
244      * Implements PhotoDownloadRunnable.handleHTTPState(). Passes the download state to the
245      * ThreadPool object.
246      */
247 
248     @Override
handleDownloadState(int state)249     public void handleDownloadState(int state) {
250         int outState;
251 
252         // Converts the download state to the overall state
253         switch(state) {
254             case PhotoDownloadRunnable.HTTP_STATE_COMPLETED:
255                 outState = PhotoManager.DOWNLOAD_COMPLETE;
256                 break;
257             case PhotoDownloadRunnable.HTTP_STATE_FAILED:
258                 outState = PhotoManager.DOWNLOAD_FAILED;
259                 break;
260             default:
261                 outState = PhotoManager.DOWNLOAD_STARTED;
262                 break;
263         }
264         // Passes the state to the ThreadPool object.
265         handleState(outState);
266     }
267 
268     // Implements PhotoDecodeRunnable.setImageDecodeThread(). Calls setCurrentThread().
269     @Override
setImageDecodeThread(Thread currentThread)270     public void setImageDecodeThread(Thread currentThread) {
271         setCurrentThread(currentThread);
272     }
273 
274     /*
275      * Implements PhotoDecodeRunnable.handleDecodeState(). Passes the decoding state to the
276      * ThreadPool object.
277      */
278     @Override
handleDecodeState(int state)279     public void handleDecodeState(int state) {
280         int outState;
281 
282         // Converts the decode state to the overall state.
283         switch(state) {
284             case PhotoDecodeRunnable.DECODE_STATE_COMPLETED:
285                 outState = PhotoManager.TASK_COMPLETE;
286                 break;
287             case PhotoDecodeRunnable.DECODE_STATE_FAILED:
288                 outState = PhotoManager.DOWNLOAD_FAILED;
289                 break;
290             default:
291                 outState = PhotoManager.DECODE_STARTED;
292                 break;
293         }
294 
295         // Passes the state to the ThreadPool object.
296         handleState(outState);
297     }
298 }
299