blob: 272b8bcb8f1596123917e806d92d76ba91e60678 [file] [log] [blame]
Scott Main153f8fe2012-04-04 17:45:24 -07001page.title=Processing Bitmaps Off the UI Thread
2parent.title=Displaying Bitmaps Efficiently
3parent.link=index.html
4
5trainingnavtop=true
Scott Main153f8fe2012-04-04 17:45:24 -07006
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 Main50e990c2012-06-21 17:14:39 -070020 <li><a href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a></li>
Scott Main153f8fe2012-04-04 17:45:24 -070021 <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
Scott Main50e990c2012-06-21 17:14:39 -070044href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a> for
Scott Main153f8fe2012-04-04 17:45:24 -070045more 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. Heres 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>
Adam Koch9977ddd2012-08-14 14:53:42 -040061class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
62 private final WeakReference&lt;ImageView&gt; imageViewReference;
Scott Main153f8fe2012-04-04 17:45:24 -070063 private int data = 0;
64
65 public BitmapWorkerTask(ImageView imageView) {
66 // Use a WeakReference to ensure the ImageView can be garbage collected
Adam Koch9977ddd2012-08-14 14:53:42 -040067 imageViewReference = new WeakReference&lt;ImageView&gt;(imageView);
Scott Main153f8fe2012-04-04 17:45:24 -070068 }
69
70 // Decode image in background.
71 &#64;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 &#64;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. Theres 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 {
Adam Koch9977ddd2012-08-14 14:53:42 -0400132 private final WeakReference&lt;BitmapWorkerTask&gt; bitmapWorkerTaskReference;
Scott Main153f8fe2012-04-04 17:45:24 -0700133
134 public AsyncDrawable(Resources res, Bitmap bitmap,
135 BitmapWorkerTask bitmapWorkerTask) {
136 super(res, bitmap);
137 bitmapWorkerTaskReference =
Adam Koch9977ddd2012-08-14 14:53:42 -0400138 new WeakReference&lt;BitmapWorkerTask&gt;(bitmapWorkerTask);
Scott Main153f8fe2012-04-04 17:45:24 -0700139 }
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>
Adam Koch9977ddd2012-08-14 14:53:42 -0400210class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
Scott Main153f8fe2012-04-04 17:45:24 -0700211 ...
212
213 &#64;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
Adam Koch9977ddd2012-08-14 14:53:42 -0400235would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>