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 */ 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 */ 71 byte[] getByteBuffer(); 72 73 /** 74 * Sets the actions for each state of the PhotoTask instance. 75 * @param state The state being handled. 76 */ 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 */ 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 */ 89 int getTargetHeight(); 90 91 /** 92 * Sets the Bitmap for the ImageView being displayed. 93 * @param image 94 */ 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 */ 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 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