• 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
6next.title=Caching Bitmaps
7next.link=cache-bitmap.html
8previous.title=Loading Large Bitmaps Efficiently
9previous.link=load-bitmap.html
10
11@jd:body
12
13<div id="tb-wrapper">
14<div id="tb">
15
16<h2>This lesson teaches you to</h2>
17<ol>
18  <li><a href="#async-task">Use an AsyncTask</a></li>
19  <li><a href="#concurrency">Handle Concurrency</a></li>
20</ol>
21
22<h2>You should also read</h2>
23<ul>
24  <li><a href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a></li>
25  <li><a
26  href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading
27  for Performance</a></li>
28</ul>
29
30<h2>Try it out</h2>
31
32<div class="download-box">
33  <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a>
34  <p class="filename">BitmapFun.zip</p>
35</div>
36
37</div>
38</div>
39
40<p>The {@link
41android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options)
42BitmapFactory.decode*} methods, discussed in the <a href="load-bitmap.html">Load Large Bitmaps
43Efficiently</a> lesson, should not be executed on the main UI thread if the source data is read from
44disk or a network location (or really any source other than memory). The time this data takes to
45load is unpredictable and depends on a variety of factors (speed of reading from disk or network,
46size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags
47your application as non-responsive and the user has the option of closing it (see <a
48href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a> for
49more information).</p>
50
51<p>This lesson walks you through processing bitmaps in a background thread using
52{@link android.os.AsyncTask} and shows you how to handle concurrency issues.</p>
53
54<h2 id="async-task">Use an AsyncTask</h2>
55
56<p>The {@link android.os.AsyncTask} class provides an easy way to execute some work in a background
57thread and publish the results back on the UI thread. To use it, create a subclass and override the
58provided methods. Here’s an example of loading a large image into an {@link
59android.widget.ImageView} using {@link android.os.AsyncTask} and <a
60href="load-bitmap.html#decodeSampledBitmapFromResource">{@code
61decodeSampledBitmapFromResource()}</a>: </p>
62
63<a name="BitmapWorkerTask"></a>
64<pre>
65class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
66    private final WeakReference<ImageView> imageViewReference;
67    private int data = 0;
68
69    public BitmapWorkerTask(ImageView imageView) {
70        // Use a WeakReference to ensure the ImageView can be garbage collected
71        imageViewReference = new WeakReference<ImageView>(imageView);
72    }
73
74    // Decode image in background.
75    &#64;Override
76    protected Bitmap doInBackground(Integer... params) {
77        data = params[0];
78        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
79    }
80
81    // Once complete, see if ImageView is still around and set bitmap.
82    &#64;Override
83    protected void onPostExecute(Bitmap bitmap) {
84        if (imageViewReference != null && bitmap != null) {
85            final ImageView imageView = imageViewReference.get();
86            if (imageView != null) {
87                imageView.setImageBitmap(bitmap);
88            }
89        }
90    }
91}
92</pre>
93
94<p>The {@link java.lang.ref.WeakReference} to the {@link android.widget.ImageView} ensures that the
95{@link android.os.AsyncTask} does not prevent the {@link android.widget.ImageView} and anything it
96references from being garbage collected. There’s no guarantee the {@link android.widget.ImageView}
97is still around when the task finishes, so you must also check the reference in {@link
98android.os.AsyncTask#onPostExecute(Result) onPostExecute()}. The {@link android.widget.ImageView}
99may no longer exist, if for example, the user navigates away from the activity or if a
100configuration change happens before the task finishes.</p>
101
102<p>To start loading the bitmap asynchronously, simply create a new task and execute it:</p>
103
104<pre>
105public void loadBitmap(int resId, ImageView imageView) {
106    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
107    task.execute(resId);
108}
109</pre>
110
111<h2 id="concurrency">Handle Concurrency</h2>
112
113<p>Common view components such as {@link android.widget.ListView} and {@link
114android.widget.GridView} introduce another issue when used in conjunction with the {@link
115android.os.AsyncTask} as demonstrated in the previous section. In order to be efficient with memory,
116these components recycle child views as the user scrolls. If each child view triggers an {@link
117android.os.AsyncTask}, there is no guarantee that when it completes, the associated view has not
118already been recycled for use in another child view. Furthermore, there is no guarantee that the
119order in which asynchronous tasks are started is the order that they complete.</p>
120
121<p>The blog post <a
122href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading
123for Performance</a> further discusses dealing with concurrency, and offers a solution where the
124{@link android.widget.ImageView} stores a reference to the most recent {@link android.os.AsyncTask}
125which can later be checked when the task completes. Using a similar method, the {@link
126android.os.AsyncTask} from the previous section can be extended to follow a similar pattern.</p>
127
128<p>Create a dedicated {@link android.graphics.drawable.Drawable} subclass to store a reference
129back to the worker task. In this case, a {@link android.graphics.drawable.BitmapDrawable} is used so
130that a placeholder image can be displayed in the {@link android.widget.ImageView} while the task
131completes:</p>
132
133<a name="AsyncDrawable"></a>
134<pre>
135static class AsyncDrawable extends BitmapDrawable {
136    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
137
138    public AsyncDrawable(Resources res, Bitmap bitmap,
139            BitmapWorkerTask bitmapWorkerTask) {
140        super(res, bitmap);
141        bitmapWorkerTaskReference =
142            new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
143    }
144
145    public BitmapWorkerTask getBitmapWorkerTask() {
146        return bitmapWorkerTaskReference.get();
147    }
148}
149</pre>
150
151<p>Before executing the <a href="#BitmapWorkerTask">{@code BitmapWorkerTask}</a>, you create an <a
152href="#AsyncDrawable">{@code AsyncDrawable}</a> and bind it to the target {@link
153android.widget.ImageView}:</p>
154
155<pre>
156public void loadBitmap(int resId, ImageView imageView) {
157    if (cancelPotentialWork(resId, imageView)) {
158        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
159        final AsyncDrawable asyncDrawable =
160                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
161        imageView.setImageDrawable(asyncDrawable);
162        task.execute(resId);
163    }
164}
165</pre>
166
167<p>The {@code cancelPotentialWork} method referenced in the code sample above checks if another
168running task is already associated with the {@link android.widget.ImageView}. If so, it attempts to
169cancel the previous task by calling {@link android.os.AsyncTask#cancel cancel()}. In a small number
170of cases, the new task data matches the existing task and nothing further needs to happen. Here is
171the implementation of {@code cancelPotentialWork}:</p>
172
173<pre>
174public static boolean cancelPotentialWork(int data, ImageView imageView) {
175    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
176
177    if (bitmapWorkerTask != null) {
178        final int bitmapData = bitmapWorkerTask.data;
179        if (bitmapData != data) {
180            // Cancel previous task
181            bitmapWorkerTask.cancel(true);
182        } else {
183            // The same work is already in progress
184            return false;
185        }
186    }
187    // No task associated with the ImageView, or an existing task was cancelled
188    return true;
189}
190</pre>
191
192<p>A helper method, {@code getBitmapWorkerTask()}, is used above to retrieve the task associated
193with a particular {@link android.widget.ImageView}:</p>
194
195<pre>
196private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
197   if (imageView != null) {
198       final Drawable drawable = imageView.getDrawable();
199       if (drawable instanceof AsyncDrawable) {
200           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
201           return asyncDrawable.getBitmapWorkerTask();
202       }
203    }
204    return null;
205}
206</pre>
207
208<p>The last step is updating {@code onPostExecute()} in <a href="#BitmapWorkerTask">{@code
209BitmapWorkerTask}</a> so that it checks if the task is cancelled and if the current task matches the
210one associated with the {@link android.widget.ImageView}:</p>
211
212<a name="BitmapWorkerTaskUpdated"></a>
213<pre>
214class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
215    ...
216
217    &#64;Override
218    protected void onPostExecute(Bitmap bitmap) {
219        <strong>if (isCancelled()) {
220            bitmap = null;
221        }</strong>
222
223        if (imageViewReference != null && bitmap != null) {
224            final ImageView imageView = imageViewReference.get();
225            <strong>final BitmapWorkerTask bitmapWorkerTask =
226                    getBitmapWorkerTask(imageView);</strong>
227            if (<strong>this == bitmapWorkerTask &&</strong> imageView != null) {
228                imageView.setImageBitmap(bitmap);
229            }
230        }
231    }
232}
233</pre>
234
235<p>This implementation is now suitable for use in {@link android.widget.ListView} and {@link
236android.widget.GridView} components as well as any other components that recycle their child
237views. Simply call {@code loadBitmap} where you normally set an image to your {@link
238android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this
239would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>