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}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 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<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 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<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 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 != 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 189 with a particular {@link android.widget.ImageView}:</p> 190 191 <pre> 192 private 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 205 BitmapWorkerTask}</a> so that it checks if the task is cancelled and if the current task matches the 206 one associated with the {@link android.widget.ImageView}:</p> 207 208 <a name="BitmapWorkerTaskUpdated"></a> 209 <pre> 210 class 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 232 android.widget.GridView} components as well as any other components that recycle their child 233 views. Simply call {@code loadBitmap} where you normally set an image to your {@link 234 android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this 235 would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p> 236