Home | History | Annotate | Download | only in displaying-bitmaps
      1 page.title=Processing Bitmaps Off the UI Thread
      2 parent.title=Displaying Bitmaps Efficiently
      3 parent.link=index.html
      4 
      5 trainingnavtop=true
      6 
      7 @jd:body
      8 
      9 <div id="tb-wrapper">
     10 <div id="tb">
     11 
     12 <h2>This lesson teaches you to</h2>
     13 <ol>
     14   <li><a href="#async-task">Use an AsyncTask</a></li>
     15   <li><a href="#concurrency">Handle Concurrency</a></li>
     16 </ol>
     17 
     18 <h2>You should also read</h2>
     19 <ul>
     20   <li><a href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a></li>
     21   <li><a
     22   href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading
     23   for Performance</a></li>
     24 </ul>
     25 
     26 <h2>Try it out</h2>
     27 
     28 <div class="download-box">
     29   <a href="{@docRoot}downloads/samples/DisplayingBitmaps.zip" class="button">Download the sample</a>
     30   <p class="filename">DisplayingBitmaps.zip</p>
     31 </div>
     32 
     33 </div>
     34 </div>
     35 
     36 <p>The {@link
     37 android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options)
     38 BitmapFactory.decode*} methods, discussed in the <a href="load-bitmap.html">Load Large Bitmaps
     39 Efficiently</a> lesson, should not be executed on the main UI thread if the source data is read from
     40 disk or a network location (or really any source other than memory). The time this data takes to
     41 load is unpredictable and depends on a variety of factors (speed of reading from disk or network,
     42 size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags
     43 your application as non-responsive and the user has the option of closing it (see <a
     44 href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a> for
     45 more information).</p>
     46 
     47 <p>This lesson walks you through processing bitmaps in a background thread using
     48 {@link android.os.AsyncTask} and shows you how to handle concurrency issues.</p>
     49 
     50 <h2 id="async-task">Use an AsyncTask</h2>
     51 
     52 <p>The {@link android.os.AsyncTask} class provides an easy way to execute some work in a background
     53 thread and publish the results back on the UI thread. To use it, create a subclass and override the
     54 provided methods. Heres an example of loading a large image into an {@link
     55 android.widget.ImageView} using {@link android.os.AsyncTask} and <a
     56 href="load-bitmap.html#decodeSampledBitmapFromResource">{@code
     57 decodeSampledBitmapFromResource()}</a>: </p>
     58 
     59 <a name="BitmapWorkerTask"></a>
     60 <pre>
     61 class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
     62     private final WeakReference&lt;ImageView&gt; imageViewReference;
     63     private int data = 0;
     64 
     65     public BitmapWorkerTask(ImageView imageView) {
     66         // Use a WeakReference to ensure the ImageView can be garbage collected
     67         imageViewReference = new WeakReference&lt;ImageView&gt;(imageView);
     68     }
     69 
     70     // Decode image in background.
     71     &#64;Override
     72     protected Bitmap doInBackground(Integer... params) {
     73         data = params[0];
     74         return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
     75     }
     76 
     77     // Once complete, see if ImageView is still around and set bitmap.
     78     &#64;Override
     79     protected void onPostExecute(Bitmap bitmap) {
     80         if (imageViewReference != null && bitmap != null) {
     81             final ImageView imageView = imageViewReference.get();
     82             if (imageView != null) {
     83                 imageView.setImageBitmap(bitmap);
     84             }
     85         }
     86     }
     87 }
     88 </pre>
     89 
     90 <p>The {@link java.lang.ref.WeakReference} to the {@link android.widget.ImageView} ensures that the
     91 {@link android.os.AsyncTask} does not prevent the {@link android.widget.ImageView} and anything it
     92 references from being garbage collected. Theres no guarantee the {@link android.widget.ImageView}
     93 is still around when the task finishes, so you must also check the reference in {@link
     94 android.os.AsyncTask#onPostExecute(Result) onPostExecute()}. The {@link android.widget.ImageView}
     95 may no longer exist, if for example, the user navigates away from the activity or if a
     96 configuration change happens before the task finishes.</p>
     97 
     98 <p>To start loading the bitmap asynchronously, simply create a new task and execute it:</p>
     99 
    100 <pre>
    101 public void loadBitmap(int resId, ImageView imageView) {
    102     BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    103     task.execute(resId);
    104 }
    105 </pre>
    106 
    107 <h2 id="concurrency">Handle Concurrency</h2>
    108 
    109 <p>Common view components such as {@link android.widget.ListView} and {@link
    110 android.widget.GridView} introduce another issue when used in conjunction with the {@link
    111 android.os.AsyncTask} as demonstrated in the previous section. In order to be efficient with memory,
    112 these components recycle child views as the user scrolls. If each child view triggers an {@link
    113 android.os.AsyncTask}, there is no guarantee that when it completes, the associated view has not
    114 already been recycled for use in another child view. Furthermore, there is no guarantee that the
    115 order in which asynchronous tasks are started is the order that they complete.</p>
    116 
    117 <p>The blog post <a
    118 href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading
    119 for Performance</a> further discusses dealing with concurrency, and offers a solution where the
    120 {@link android.widget.ImageView} stores a reference to the most recent {@link android.os.AsyncTask}
    121 which can later be checked when the task completes. Using a similar method, the {@link
    122 android.os.AsyncTask} from the previous section can be extended to follow a similar pattern.</p>
    123 
    124 <p>Create a dedicated {@link android.graphics.drawable.Drawable} subclass to store a reference
    125 back to the worker task. In this case, a {@link android.graphics.drawable.BitmapDrawable} is used so
    126 that a placeholder image can be displayed in the {@link android.widget.ImageView} while the task
    127 completes:</p>
    128 
    129 <a name="AsyncDrawable"></a>
    130 <pre>
    131 static class AsyncDrawable extends BitmapDrawable {
    132     private final WeakReference&lt;BitmapWorkerTask&gt; bitmapWorkerTaskReference;
    133 
    134     public AsyncDrawable(Resources res, Bitmap bitmap,
    135             BitmapWorkerTask bitmapWorkerTask) {
    136         super(res, bitmap);
    137         bitmapWorkerTaskReference =
    138             new WeakReference&lt;BitmapWorkerTask&gt;(bitmapWorkerTask);
    139     }
    140 
    141     public BitmapWorkerTask getBitmapWorkerTask() {
    142         return bitmapWorkerTaskReference.get();
    143     }
    144 }
    145 </pre>
    146 
    147 <p>Before executing the <a href="#BitmapWorkerTask">{@code BitmapWorkerTask}</a>, you create an <a
    148 href="#AsyncDrawable">{@code AsyncDrawable}</a> and bind it to the target {@link
    149 android.widget.ImageView}:</p>
    150 
    151 <pre>
    152 public void loadBitmap(int resId, ImageView imageView) {
    153     if (cancelPotentialWork(resId, imageView)) {
    154         final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    155         final AsyncDrawable asyncDrawable =
    156                 new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
    157         imageView.setImageDrawable(asyncDrawable);
    158         task.execute(resId);
    159     }
    160 }
    161 </pre>
    162 
    163 <p>The {@code cancelPotentialWork} method referenced in the code sample above checks if another
    164 running task is already associated with the {@link android.widget.ImageView}. If so, it attempts to
    165 cancel the previous task by calling {@link android.os.AsyncTask#cancel cancel()}. In a small number
    166 of cases, the new task data matches the existing task and nothing further needs to happen. Here is
    167 the implementation of {@code cancelPotentialWork}:</p>
    168 
    169 <pre>
    170 public static boolean cancelPotentialWork(int data, ImageView imageView) {
    171     final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
    172 
    173     if (bitmapWorkerTask != null) {
    174         final int bitmapData = bitmapWorkerTask.data;
    175         // If bitmapData is not yet set or it differs from the new data
    176         if (bitmapData == 0 || bitmapData != data) {
    177             // Cancel previous task
    178             bitmapWorkerTask.cancel(true);
    179         } else {
    180             // The same work is already in progress
    181             return false;
    182         }
    183     }
    184     // No task associated with the ImageView, or an existing task was cancelled
    185     return true;
    186 }
    187 </pre>
    188 
    189 <p>A helper method, {@code getBitmapWorkerTask()}, is used above to retrieve the task associated
    190 with a particular {@link android.widget.ImageView}:</p>
    191 
    192 <pre>
    193 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
    194    if (imageView != null) {
    195        final Drawable drawable = imageView.getDrawable();
    196        if (drawable instanceof AsyncDrawable) {
    197            final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
    198            return asyncDrawable.getBitmapWorkerTask();
    199        }
    200     }
    201     return null;
    202 }
    203 </pre>
    204 
    205 <p>The last step is updating {@code onPostExecute()} in <a href="#BitmapWorkerTask">{@code
    206 BitmapWorkerTask}</a> so that it checks if the task is cancelled and if the current task matches the
    207 one associated with the {@link android.widget.ImageView}:</p>
    208 
    209 <a name="BitmapWorkerTaskUpdated"></a>
    210 <pre>
    211 class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
    212     ...
    213 
    214     &#64;Override
    215     protected void onPostExecute(Bitmap bitmap) {
    216         <strong>if (isCancelled()) {
    217             bitmap = null;
    218         }</strong>
    219 
    220         if (imageViewReference != null && bitmap != null) {
    221             final ImageView imageView = imageViewReference.get();
    222             <strong>final BitmapWorkerTask bitmapWorkerTask =
    223                     getBitmapWorkerTask(imageView);</strong>
    224             if (<strong>this == bitmapWorkerTask &&</strong> imageView != null) {
    225                 imageView.setImageBitmap(bitmap);
    226             }
    227         }
    228     }
    229 }
    230 </pre>
    231 
    232 <p>This implementation is now suitable for use in {@link android.widget.ListView} and {@link
    233 android.widget.GridView} components as well as any other components that recycle their child
    234 views. Simply call {@code loadBitmap} where you normally set an image to your {@link
    235 android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this
    236 would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>
    237