1 /* 2 * Copyright (C) ${year} 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 21 import java.io.EOFException; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.net.HttpURLConnection; 25 import java.net.URL; 26 27 /** 28 * This task downloads bytes from a resource addressed by a URL. When the task 29 * has finished, it calls handleState to report its results. 30 * 31 * Objects of this class are instantiated and managed by instances of PhotoTask, which 32 * implements the methods of {@link TaskRunnableDecodeMethods}. PhotoTask objects call 33 * {@link #PhotoDownloadRunnable(TaskRunnableDownloadMethods) PhotoDownloadRunnable()} with 34 * themselves as the argument. In effect, an PhotoTask object and a 35 * PhotoDownloadRunnable object communicate through the fields of the PhotoTask. 36 */ 37 class PhotoDownloadRunnable implements Runnable { 38 // Sets the size for each read action (bytes) 39 private static final int READ_SIZE = 1024 * 2; 40 41 // Sets a tag for this class 42 @SuppressWarnings("unused") 43 private static final String LOG_TAG = "PhotoDownloadRunnable"; 44 45 // Constants for indicating the state of the download 46 static final int HTTP_STATE_FAILED = -1; 47 static final int HTTP_STATE_STARTED = 0; 48 static final int HTTP_STATE_COMPLETED = 1; 49 50 // Defines a field that contains the calling object of type PhotoTask. 51 final TaskRunnableDownloadMethods mPhotoTask; 52 53 /** 54 * 55 * An interface that defines methods that PhotoTask implements. An instance of 56 * PhotoTask passes itself to an PhotoDownloadRunnable instance through the 57 * PhotoDownloadRunnable constructor, after which the two instances can access each other's 58 * variables. 59 */ 60 interface TaskRunnableDownloadMethods { 61 62 /** 63 * Sets the Thread that this instance is running on 64 * @param currentThread the current Thread 65 */ 66 void setDownloadThread(Thread currentThread); 67 68 /** 69 * Returns the current contents of the download buffer 70 * @return The byte array downloaded from the URL in the last read 71 */ 72 byte[] getByteBuffer(); 73 74 /** 75 * Sets the current contents of the download buffer 76 * @param buffer The bytes that were just read 77 */ 78 void setByteBuffer(byte[] buffer); 79 80 /** 81 * Defines the actions for each state of the PhotoTask instance. 82 * @param state The current state of the task 83 */ 84 void handleDownloadState(int state); 85 86 /** 87 * Gets the URL for the image being downloaded 88 * @return The image URL 89 */ 90 URL getImageURL(); 91 } 92 93 /** 94 * This constructor creates an instance of PhotoDownloadRunnable and stores in it a reference 95 * to the PhotoTask instance that instantiated it. 96 * 97 * @param photoTask The PhotoTask, which implements TaskRunnableDecodeMethods 98 */ 99 PhotoDownloadRunnable(TaskRunnableDownloadMethods photoTask) { 100 mPhotoTask = photoTask; 101 } 102 103 /* 104 * Defines this object's task, which is a set of instructions designed to be run on a Thread. 105 */ 106 @SuppressWarnings("resource") 107 @Override 108 public void run() { 109 110 /* 111 * Stores the current Thread in the the PhotoTask instance, so that the instance 112 * can interrupt the Thread. 113 */ 114 mPhotoTask.setDownloadThread(Thread.currentThread()); 115 116 // Moves the current Thread into the background 117 android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); 118 119 /* 120 * Gets the image cache buffer object from the PhotoTask instance. This makes the 121 * to both PhotoDownloadRunnable and PhotoTask. 122 */ 123 byte[] byteBuffer = mPhotoTask.getByteBuffer(); 124 125 /* 126 * A try block that downloads a Picasa image from a URL. The URL value is in the field 127 * PhotoTask.mImageURL 128 */ 129 // Tries to download the picture from Picasa 130 try { 131 // Before continuing, checks to see that the Thread hasn't been 132 // interrupted 133 if (Thread.interrupted()) { 134 135 throw new InterruptedException(); 136 } 137 138 // If there's no cache buffer for this image 139 if (null == byteBuffer) { 140 141 /* 142 * Calls the PhotoTask implementation of {@link #handleDownloadState} to 143 * set the state of the download 144 */ 145 mPhotoTask.handleDownloadState(HTTP_STATE_STARTED); 146 147 // Defines a handle for the byte download stream 148 InputStream byteStream = null; 149 150 // Downloads the image and catches IO errors 151 try { 152 153 // Opens an HTTP connection to the image's URL 154 HttpURLConnection httpConn = 155 (HttpURLConnection) mPhotoTask.getImageURL().openConnection(); 156 157 // Sets the user agent to report to the server 158 httpConn.setRequestProperty("User-Agent", Constants.USER_AGENT); 159 160 // Before continuing, checks to see that the Thread 161 // hasn't been interrupted 162 if (Thread.interrupted()) { 163 164 throw new InterruptedException(); 165 } 166 // Gets the input stream containing the image 167 byteStream = httpConn.getInputStream(); 168 169 if (Thread.interrupted()) { 170 171 throw new InterruptedException(); 172 } 173 /* 174 * Gets the size of the file being downloaded. This 175 * may or may not be returned. 176 */ 177 int contentSize = httpConn.getContentLength(); 178 179 /* 180 * If the size of the image isn't available 181 */ 182 if (-1 == contentSize) { 183 184 // Allocates a temporary buffer 185 byte[] tempBuffer = new byte[READ_SIZE]; 186 187 // Records the initial amount of available space 188 int bufferLeft = tempBuffer.length; 189 190 /* 191 * Defines the initial offset of the next available 192 * byte in the buffer, and the initial result of 193 * reading the binary 194 */ 195 int bufferOffset = 0; 196 int readResult = 0; 197 198 /* 199 * The "outer" loop continues until all the bytes 200 * have been downloaded. The inner loop continues 201 * until the temporary buffer is full, and then 202 * allocates more buffer space. 203 */ 204 outer: do { 205 while (bufferLeft > 0) { 206 207 /* 208 * Reads from the URL location into 209 * the temporary buffer, starting at the 210 * next available free byte and reading as 211 * many bytes as are available in the 212 * buffer. 213 */ 214 readResult = byteStream.read(tempBuffer, bufferOffset, 215 bufferLeft); 216 217 /* 218 * InputStream.read() returns zero when the 219 * file has been completely read. 220 */ 221 if (readResult < 0) { 222 // The read is finished, so this breaks 223 // the to "outer" loop 224 break outer; 225 } 226 227 /* 228 * The read isn't finished. This sets the 229 * next available open position in the 230 * buffer (the buffer index is 0-based). 231 */ 232 bufferOffset += readResult; 233 234 // Subtracts the number of bytes read from 235 // the amount of buffer left 236 bufferLeft -= readResult; 237 238 if (Thread.interrupted()) { 239 240 throw new InterruptedException(); 241 } 242 } 243 /* 244 * The temporary buffer is full, so the 245 * following code creates a new buffer that can 246 * contain the existing contents plus the next 247 * read cycle. 248 */ 249 250 // Resets the amount of buffer left to be the 251 // max buffer size 252 bufferLeft = READ_SIZE; 253 254 /* 255 * Sets a new size that can contain the existing 256 * buffer's contents plus space for the next 257 * read cycle. 258 */ 259 int newSize = tempBuffer.length + READ_SIZE; 260 261 /* 262 * Creates a new temporary buffer, moves the 263 * contents of the old temporary buffer into it, 264 * and then points the temporary buffer variable 265 * to the new buffer. 266 */ 267 byte[] expandedBuffer = new byte[newSize]; 268 System.arraycopy(tempBuffer, 0, expandedBuffer, 0, 269 tempBuffer.length); 270 tempBuffer = expandedBuffer; 271 } while (true); 272 273 /* 274 * When the entire image has been read, this creates 275 * a permanent byte buffer with the same size as 276 * the number of used bytes in the temporary buffer 277 * (equal to the next open byte, because tempBuffer 278 * is 0=based). 279 */ 280 byteBuffer = new byte[bufferOffset]; 281 282 // Copies the temporary buffer to the image buffer 283 System.arraycopy(tempBuffer, 0, byteBuffer, 0, bufferOffset); 284 285 /* 286 * The download size is available, so this creates a 287 * permanent buffer of that length. 288 */ 289 } else { 290 byteBuffer = new byte[contentSize]; 291 292 // How much of the buffer still remains empty 293 int remainingLength = contentSize; 294 295 // The next open space in the buffer 296 int bufferOffset = 0; 297 298 /* 299 * Reads into the buffer until the number of bytes 300 * equal to the length of the buffer (the size of 301 * the image) have been read. 302 */ 303 while (remainingLength > 0) { 304 int readResult = byteStream.read( 305 byteBuffer, 306 bufferOffset, 307 remainingLength); 308 /* 309 * EOF should not occur, because the loop should 310 * read the exact # of bytes in the image 311 */ 312 if (readResult < 0) { 313 314 // Throws an EOF Exception 315 throw new EOFException(); 316 } 317 318 // Moves the buffer offset to the next open byte 319 bufferOffset += readResult; 320 321 // Subtracts the # of bytes read from the 322 // remaining length 323 remainingLength -= readResult; 324 325 if (Thread.interrupted()) { 326 327 throw new InterruptedException(); 328 } 329 } 330 } 331 332 if (Thread.interrupted()) { 333 334 throw new InterruptedException(); 335 } 336 337 // If an IO error occurs, returns immediately 338 } catch (IOException e) { 339 e.printStackTrace(); 340 return; 341 342 /* 343 * If the input stream is still open, close it 344 */ 345 } finally { 346 if (null != byteStream) { 347 try { 348 byteStream.close(); 349 } catch (Exception e) { 350 351 } 352 } 353 } 354 } 355 356 /* 357 * Stores the downloaded bytes in the byte buffer in the PhotoTask instance. 358 */ 359 mPhotoTask.setByteBuffer(byteBuffer); 360 361 /* 362 * Sets the status message in the PhotoTask instance. This sets the 363 * ImageView background to indicate that the image is being 364 * decoded. 365 */ 366 mPhotoTask.handleDownloadState(HTTP_STATE_COMPLETED); 367 368 // Catches exceptions thrown in response to a queued interrupt 369 } catch (InterruptedException e1) { 370 371 // Does nothing 372 373 // In all cases, handle the results 374 } finally { 375 376 // If the byteBuffer is null, reports that the download failed. 377 if (null == byteBuffer) { 378 mPhotoTask.handleDownloadState(HTTP_STATE_FAILED); 379 } 380 381 /* 382 * The implementation of setHTTPDownloadThread() in PhotoTask calls 383 * PhotoTask.setCurrentThread(), which then locks on the static ThreadPool 384 * object and returns the current thread. Locking keeps all references to Thread 385 * objects the same until the reference to the current Thread is deleted. 386 */ 387 388 // Sets the reference to the current Thread to null, releasing its storage 389 mPhotoTask.setDownloadThread(null); 390 391 // Clears the Thread's interrupt flag 392 Thread.interrupted(); 393 } 394 } 395 } 396 397