1page.title=Processing Bitmaps Off the UI Thread 2parent.title=Displaying Bitmaps Efficiently 3parent.link=index.html 4 5trainingnavtop=true 6next.title=Caching Bitmaps 7next.link=cache-bitmap.html 8previous.title=Loading Large Bitmaps Efficiently 9previous.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 41android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) 42BitmapFactory.decode*} methods, discussed in the <a href="load-bitmap.html">Load Large Bitmaps 43Efficiently</a> lesson, should not be executed on the main UI thread if the source data is read from 44disk or a network location (or really any source other than memory). The time this data takes to 45load is unpredictable and depends on a variety of factors (speed of reading from disk or network, 46size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags 47your application as non-responsive and the user has the option of closing it (see <a 48href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a> for 49more 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 57thread and publish the results back on the UI thread. To use it, create a subclass and override the 58provided methods. Here’s an example of loading a large image into an {@link 59android.widget.ImageView} using {@link android.os.AsyncTask} and <a 60href="load-bitmap.html#decodeSampledBitmapFromResource">{@code 61decodeSampledBitmapFromResource()}</a>: </p> 62 63<a name="BitmapWorkerTask"></a> 64<pre> 65class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 66 private final WeakReference<ImageView> 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<ImageView>(imageView); 72 } 73 74 // Decode image in background. 75 @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 @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 96references from being garbage collected. There’s no guarantee the {@link android.widget.ImageView} 97is still around when the task finishes, so you must also check the reference in {@link 98android.os.AsyncTask#onPostExecute(Result) onPostExecute()}. The {@link android.widget.ImageView} 99may no longer exist, if for example, the user navigates away from the activity or if a 100configuration 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> 105public 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 114android.widget.GridView} introduce another issue when used in conjunction with the {@link 115android.os.AsyncTask} as demonstrated in the previous section. In order to be efficient with memory, 116these components recycle child views as the user scrolls. If each child view triggers an {@link 117android.os.AsyncTask}, there is no guarantee that when it completes, the associated view has not 118already been recycled for use in another child view. Furthermore, there is no guarantee that the 119order in which asynchronous tasks are started is the order that they complete.</p> 120 121<p>The blog post <a 122href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading 123for 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} 125which can later be checked when the task completes. Using a similar method, the {@link 126android.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 129back to the worker task. In this case, a {@link android.graphics.drawable.BitmapDrawable} is used so 130that a placeholder image can be displayed in the {@link android.widget.ImageView} while the task 131completes:</p> 132 133<a name="AsyncDrawable"></a> 134<pre> 135static class AsyncDrawable extends BitmapDrawable { 136 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; 137 138 public AsyncDrawable(Resources res, Bitmap bitmap, 139 BitmapWorkerTask bitmapWorkerTask) { 140 super(res, bitmap); 141 bitmapWorkerTaskReference = 142 new WeakReference<BitmapWorkerTask>(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 152href="#AsyncDrawable">{@code AsyncDrawable}</a> and bind it to the target {@link 153android.widget.ImageView}:</p> 154 155<pre> 156public 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 168running task is already associated with the {@link android.widget.ImageView}. If so, it attempts to 169cancel the previous task by calling {@link android.os.AsyncTask#cancel cancel()}. In a small number 170of cases, the new task data matches the existing task and nothing further needs to happen. Here is 171the implementation of {@code cancelPotentialWork}:</p> 172 173<pre> 174public 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 193with a particular {@link android.widget.ImageView}:</p> 194 195<pre> 196private 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 209BitmapWorkerTask}</a> so that it checks if the task is cancelled and if the current task matches the 210one associated with the {@link android.widget.ImageView}:</p> 211 212<a name="BitmapWorkerTaskUpdated"></a> 213<pre> 214class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 215 ... 216 217 @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 236android.widget.GridView} components as well as any other components that recycle their child 237views. Simply call {@code loadBitmap} where you normally set an image to your {@link 238android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this 239would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>