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