• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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&lt;Integer, Void, Bitmap&gt; {
62    private final WeakReference&lt;ImageView&gt; 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&lt;ImageView&gt;(imageView);
68    }
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. 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&lt;BitmapWorkerTask&gt; bitmapWorkerTaskReference;
133
134    public AsyncDrawable(Resources res, Bitmap bitmap,
135            BitmapWorkerTask bitmapWorkerTask) {
136        super(res, bitmap);
137        bitmapWorkerTaskReference =
138            new WeakReference&lt;BitmapWorkerTask&gt;(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&lt;Integer, Void, Bitmap&gt; {
211    ...
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
235would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>
236