1page.title=Processing Bitmaps Off the UI Thread 2parent.title=Displaying Bitmaps Efficiently 3parent.link=index.html 4 5trainingnavtop=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}shareables/training/BitmapFun.zip" class="button">Download the sample</a> 30 <p class="filename">BitmapFun.zip</p> 31</div> 32 33</div> 34</div> 35 36<p>The {@link 37android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) 38BitmapFactory.decode*} methods, discussed in the <a href="load-bitmap.html">Load Large Bitmaps 39Efficiently</a> lesson, should not be executed on the main UI thread if the source data is read from 40disk or a network location (or really any source other than memory). The time this data takes to 41load is unpredictable and depends on a variety of factors (speed of reading from disk or network, 42size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags 43your application as non-responsive and the user has the option of closing it (see <a 44href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a> for 45more 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 53thread and publish the results back on the UI thread. To use it, create a subclass and override the 54provided methods. Here’s an example of loading a large image into an {@link 55android.widget.ImageView} using {@link android.os.AsyncTask} and <a 56href="load-bitmap.html#decodeSampledBitmapFromResource">{@code 57decodeSampledBitmapFromResource()}</a>: </p> 58 59<a name="BitmapWorkerTask"></a> 60<pre> 61class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 62 private final WeakReference<ImageView> 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<ImageView>(imageView); 68 } 69 70 // Decode image in background. 71 @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 @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 92references from being garbage collected. There’s no guarantee the {@link android.widget.ImageView} 93is still around when the task finishes, so you must also check the reference in {@link 94android.os.AsyncTask#onPostExecute(Result) onPostExecute()}. The {@link android.widget.ImageView} 95may no longer exist, if for example, the user navigates away from the activity or if a 96configuration 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> 101public 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 110android.widget.GridView} introduce another issue when used in conjunction with the {@link 111android.os.AsyncTask} as demonstrated in the previous section. In order to be efficient with memory, 112these components recycle child views as the user scrolls. If each child view triggers an {@link 113android.os.AsyncTask}, there is no guarantee that when it completes, the associated view has not 114already been recycled for use in another child view. Furthermore, there is no guarantee that the 115order in which asynchronous tasks are started is the order that they complete.</p> 116 117<p>The blog post <a 118href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading 119for 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} 121which can later be checked when the task completes. Using a similar method, the {@link 122android.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 125back to the worker task. In this case, a {@link android.graphics.drawable.BitmapDrawable} is used so 126that a placeholder image can be displayed in the {@link android.widget.ImageView} while the task 127completes:</p> 128 129<a name="AsyncDrawable"></a> 130<pre> 131static class AsyncDrawable extends BitmapDrawable { 132 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; 133 134 public AsyncDrawable(Resources res, Bitmap bitmap, 135 BitmapWorkerTask bitmapWorkerTask) { 136 super(res, bitmap); 137 bitmapWorkerTaskReference = 138 new WeakReference<BitmapWorkerTask>(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 148href="#AsyncDrawable">{@code AsyncDrawable}</a> and bind it to the target {@link 149android.widget.ImageView}:</p> 150 151<pre> 152public 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 164running task is already associated with the {@link android.widget.ImageView}. If so, it attempts to 165cancel the previous task by calling {@link android.os.AsyncTask#cancel cancel()}. In a small number 166of cases, the new task data matches the existing task and nothing further needs to happen. Here is 167the implementation of {@code cancelPotentialWork}:</p> 168 169<pre> 170public 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 != data) { 176 // Cancel previous task 177 bitmapWorkerTask.cancel(true); 178 } else { 179 // The same work is already in progress 180 return false; 181 } 182 } 183 // No task associated with the ImageView, or an existing task was cancelled 184 return true; 185} 186</pre> 187 188<p>A helper method, {@code getBitmapWorkerTask()}, is used above to retrieve the task associated 189with a particular {@link android.widget.ImageView}:</p> 190 191<pre> 192private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { 193 if (imageView != null) { 194 final Drawable drawable = imageView.getDrawable(); 195 if (drawable instanceof AsyncDrawable) { 196 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; 197 return asyncDrawable.getBitmapWorkerTask(); 198 } 199 } 200 return null; 201} 202</pre> 203 204<p>The last step is updating {@code onPostExecute()} in <a href="#BitmapWorkerTask">{@code 205BitmapWorkerTask}</a> so that it checks if the task is cancelled and if the current task matches the 206one associated with the {@link android.widget.ImageView}:</p> 207 208<a name="BitmapWorkerTaskUpdated"></a> 209<pre> 210class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 211 ... 212 213 @Override 214 protected void onPostExecute(Bitmap bitmap) { 215 <strong>if (isCancelled()) { 216 bitmap = null; 217 }</strong> 218 219 if (imageViewReference != null && bitmap != null) { 220 final ImageView imageView = imageViewReference.get(); 221 <strong>final BitmapWorkerTask bitmapWorkerTask = 222 getBitmapWorkerTask(imageView);</strong> 223 if (<strong>this == bitmapWorkerTask &&</strong> imageView != null) { 224 imageView.setImageBitmap(bitmap); 225 } 226 } 227 } 228} 229</pre> 230 231<p>This implementation is now suitable for use in {@link android.widget.ListView} and {@link 232android.widget.GridView} components as well as any other components that recycle their child 233views. Simply call {@code loadBitmap} where you normally set an image to your {@link 234android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this 235would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p> 236