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 android.graphics.Bitmap;
20 import android.graphics.BitmapFactory;
21 import android.util.Log;
22 
23 /**
24  * This runnable decodes a byte array containing an image.
25  *
26  * Objects of this class are instantiated and managed by instances of PhotoTask, which
27  * implements the methods {@link TaskRunnableDecodeMethods}. PhotoTask objects call
28  * {@link #PhotoDecodeRunnable(TaskRunnableDecodeMethods) PhotoDecodeRunnable()} with
29  * themselves as the argument. In effect, an PhotoTask object and a
30  * PhotoDecodeRunnable object communicate through the fields of the PhotoTask.
31  *
32  */
33 class PhotoDecodeRunnable implements Runnable {
34 
35     // Limits the number of times the decoder tries to process an image
36     private static final int NUMBER_OF_DECODE_TRIES = 2;
37 
38     // Tells the Runnable to pause for a certain number of milliseconds
39     private static final long SLEEP_TIME_MILLISECONDS = 250;
40 
41     // Sets the log tag
42     private static final String LOG_TAG = "PhotoDecodeRunnable";
43 
44     // Constants for indicating the state of the decode
45     static final int DECODE_STATE_FAILED = -1;
46     static final int DECODE_STATE_STARTED = 0;
47     static final int DECODE_STATE_COMPLETED = 1;
48 
49     // Defines a field that contains the calling object of type PhotoTask.
50     final TaskRunnableDecodeMethods mPhotoTask;
51 
52     /**
53     *
54     * An interface that defines methods that PhotoTask implements. An instance of
55     * PhotoTask passes itself to an PhotoDecodeRunnable instance through the
56     * PhotoDecodeRunnable constructor, after which the two instances can access each other's
57     * variables.
58     */
59     interface TaskRunnableDecodeMethods {
60 
61         /**
62          * Sets the Thread that this instance is running on
63          * @param currentThread the current Thread
64          */
setImageDecodeThread(Thread currentThread)65         void setImageDecodeThread(Thread currentThread);
66 
67         /**
68          * Returns the current contents of the download buffer
69          * @return The byte array downloaded from the URL in the last read
70          */
getByteBuffer()71         byte[] getByteBuffer();
72 
73         /**
74          * Sets the actions for each state of the PhotoTask instance.
75          * @param state The state being handled.
76          */
handleDecodeState(int state)77         void handleDecodeState(int state);
78 
79         /**
80          * Returns the desired width of the image, based on the ImageView being created.
81          * @return The target width
82          */
getTargetWidth()83         int getTargetWidth();
84 
85         /**
86          * Returns the desired height of the image, based on the ImageView being created.
87          * @return The target height.
88          */
getTargetHeight()89         int getTargetHeight();
90 
91         /**
92          * Sets the Bitmap for the ImageView being displayed.
93          * @param image
94          */
setImage(Bitmap image)95         void setImage(Bitmap image);
96     }
97 
98     /**
99      * This constructor creates an instance of PhotoDownloadRunnable and stores in it a reference
100      * to the PhotoTask instance that instantiated it.
101      *
102      * @param downloadTask The PhotoTask, which implements ImageDecoderRunnableCallback
103      */
PhotoDecodeRunnable(TaskRunnableDecodeMethods downloadTask)104     PhotoDecodeRunnable(TaskRunnableDecodeMethods downloadTask) {
105         mPhotoTask = downloadTask;
106     }
107 
108     /*
109      * Defines this object's task, which is a set of instructions designed to be run on a Thread.
110      */
111     @Override
run()112     public void run() {
113 
114         /*
115          * Stores the current Thread in the the PhotoTask instance, so that the instance
116          * can interrupt the Thread.
117          */
118         mPhotoTask.setImageDecodeThread(Thread.currentThread());
119 
120         /*
121          * Gets the image cache buffer object from the PhotoTask instance. This makes the
122          * to both PhotoDownloadRunnable and PhotoTask.
123          */
124         byte[] imageBuffer = mPhotoTask.getByteBuffer();
125 
126         // Defines the Bitmap object that this thread will create
127         Bitmap returnBitmap = null;
128 
129         /*
130          * A try block that decodes a downloaded image.
131          *
132          */
133         try {
134 
135             /*
136              * Calls the PhotoTask implementation of {@link #handleDecodeState} to
137              * set the state of the download
138              */
139             mPhotoTask.handleDecodeState(DECODE_STATE_STARTED);
140 
141             // Sets up options for creating a Bitmap object from the
142             // downloaded image.
143             BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
144 
145             /*
146              * Sets the desired image height and width based on the
147              * ImageView being created.
148              */
149             int targetWidth = mPhotoTask.getTargetWidth();
150             int targetHeight = mPhotoTask.getTargetHeight();
151 
152             // Before continuing, checks to see that the Thread hasn't
153             // been interrupted
154             if (Thread.interrupted()) {
155 
156                 return;
157             }
158 
159             /*
160              * Even if the decoder doesn't set a Bitmap, this flag tells
161              * the decoder to return the calculated bounds.
162              */
163             bitmapOptions.inJustDecodeBounds = true;
164 
165             /*
166              * First pass of decoding to get scaling and sampling
167              * parameters from the image
168              */
169             BitmapFactory
170                     .decodeByteArray(imageBuffer, 0, imageBuffer.length, bitmapOptions);
171 
172             /*
173              * Sets horizontal and vertical scaling factors so that the
174              * image is expanded or compressed from its actual size to
175              * the size of the target ImageView
176              */
177             int hScale = bitmapOptions.outHeight / targetHeight;
178             int wScale = bitmapOptions.outWidth / targetWidth;
179 
180             /*
181              * Sets the sample size to be larger of the horizontal or
182              * vertical scale factor
183              */
184             //
185             int sampleSize = Math.max(hScale, wScale);
186 
187             /*
188              * If either of the scaling factors is > 1, the image's
189              * actual dimension is larger that the available dimension.
190              * This means that the BitmapFactory must compress the image
191              * by the larger of the scaling factors. Setting
192              * inSampleSize accomplishes this.
193              */
194             if (sampleSize > 1) {
195                 bitmapOptions.inSampleSize = sampleSize;
196             }
197 
198             if (Thread.interrupted()) {
199                 return;
200             }
201 
202             // Second pass of decoding. If no bitmap is created, nothing
203             // is set in the object.
204             bitmapOptions.inJustDecodeBounds = false;
205 
206             /*
207              * This does the actual decoding of the buffer. If the
208              * decode encounters an an out-of-memory error, it may throw
209              * an Exception or an Error, both of which need to be
210              * handled. Once the problem is handled, the decode is
211              * re-tried.
212              */
213             for (int i = 0; i < NUMBER_OF_DECODE_TRIES; i++) {
214                 try {
215                     // Tries to decode the image buffer
216                     returnBitmap = BitmapFactory.decodeByteArray(
217                             imageBuffer,
218                             0,
219                             imageBuffer.length,
220                             bitmapOptions
221                             );
222                     /*
223                      * If the decode works, no Exception or Error has occurred.
224                     break;
225 
226                     /*
227                      * If the decode fails, this block tries to get more memory.
228                      */
229                 } catch (Throwable e) {
230 
231                     // Logs an error
232                     Log.e(LOG_TAG, "Out of memory in decode stage. Throttling.");
233 
234                     /*
235                      * Tells the system that garbage collection is
236                      * necessary. Notice that collection may or may not
237                      * occur.
238                      */
239                     java.lang.System.gc();
240 
241                     if (Thread.interrupted()) {
242                         return;
243 
244                     }
245                     /*
246                      * Tries to pause the thread for 250 milliseconds,
247                      * and catches an Exception if something tries to
248                      * activate the thread before it wakes up.
249                      */
250                     try {
251                         Thread.sleep(SLEEP_TIME_MILLISECONDS);
252                     } catch (java.lang.InterruptedException interruptException) {
253                         return;
254                     }
255                 }
256             }
257 
258             // Catches exceptions if something tries to activate the
259             // Thread incorrectly.
260         } finally {
261             // If the decode failed, there's no bitmap.
262             if (null == returnBitmap) {
263 
264                 // Sends a failure status to the PhotoTask
265                 mPhotoTask.handleDecodeState(DECODE_STATE_FAILED);
266 
267                 // Logs the error
268                 Log.e(LOG_TAG, "Download failed in PhotoDecodeRunnable");
269 
270             } else {
271 
272                 // Sets the ImageView Bitmap
273                 mPhotoTask.setImage(returnBitmap);
274 
275                 // Reports a status of "completed"
276                 mPhotoTask.handleDecodeState(DECODE_STATE_COMPLETED);
277             }
278 
279             // Sets the current Thread to null, releasing its storage
280             mPhotoTask.setImageDecodeThread(null);
281 
282             // Clears the Thread's interrupt flag
283             Thread.interrupted();
284 
285         }
286 
287     }
288 }
289