Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 1 | page.title=Processing Bitmaps Off the UI Thread |
| 2 | parent.title=Displaying Bitmaps Efficiently |
| 3 | parent.link=index.html |
| 4 | |
| 5 | trainingnavtop=true |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 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> |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 20 | <li><a href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a></li> |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 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 |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 44 | href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a> for |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 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. Here’s 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> |
Adam Koch | 9977ddd | 2012-08-14 14:53:42 -0400 | [diff] [blame] | 61 | class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { |
| 62 | private final WeakReference<ImageView> imageViewReference; |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 63 | private int data = 0; |
| 64 | |
| 65 | public BitmapWorkerTask(ImageView imageView) { |
| 66 | // Use a WeakReference to ensure the ImageView can be garbage collected |
Adam Koch | 9977ddd | 2012-08-14 14:53:42 -0400 | [diff] [blame] | 67 | imageViewReference = new WeakReference<ImageView>(imageView); |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 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. There’s 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 { |
Adam Koch | 9977ddd | 2012-08-14 14:53:42 -0400 | [diff] [blame] | 132 | private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 133 | |
| 134 | public AsyncDrawable(Resources res, Bitmap bitmap, |
| 135 | BitmapWorkerTask bitmapWorkerTask) { |
| 136 | super(res, bitmap); |
| 137 | bitmapWorkerTaskReference = |
Adam Koch | 9977ddd | 2012-08-14 14:53:42 -0400 | [diff] [blame] | 138 | new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 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> |
Adam Koch | 9977ddd | 2012-08-14 14:53:42 -0400 | [diff] [blame] | 210 | class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { |
Scott Main | 153f8fe | 2012-04-04 17:45:24 -0700 | [diff] [blame] | 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 |
Adam Koch | 9977ddd | 2012-08-14 14:53:42 -0400 | [diff] [blame] | 235 | would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p> |