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