Home | History | Annotate | Download | only in threadsample
      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