2012-04-04 17:45:24 -07:00
|
|
|
|
page.title=Processing Bitmaps Off the UI Thread
|
|
|
|
|
parent.title=Displaying Bitmaps Efficiently
|
|
|
|
|
parent.link=index.html
|
|
|
|
|
|
|
|
|
|
trainingnavtop=true
|
|
|
|
|
next.title=Caching Bitmaps
|
|
|
|
|
next.link=cache-bitmap.html
|
|
|
|
|
previous.title=Loading Large Bitmaps Efficiently
|
|
|
|
|
previous.link=load-bitmap.html
|
|
|
|
|
|
|
|
|
|
@jd:body
|
|
|
|
|
|
|
|
|
|
<div id="tb-wrapper">
|
|
|
|
|
<div id="tb">
|
|
|
|
|
|
|
|
|
|
<h2>This lesson teaches you to</h2>
|
|
|
|
|
<ol>
|
|
|
|
|
<li><a href="#async-task">Use an AsyncTask</a></li>
|
|
|
|
|
<li><a href="#concurrency">Handle Concurrency</a></li>
|
|
|
|
|
</ol>
|
|
|
|
|
|
|
|
|
|
<h2>You should also read</h2>
|
|
|
|
|
<ul>
|
2012-06-21 17:14:39 -07:00
|
|
|
|
<li><a href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a></li>
|
2012-04-04 17:45:24 -07:00
|
|
|
|
<li><a
|
|
|
|
|
href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading
|
|
|
|
|
for Performance</a></li>
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
<h2>Try it out</h2>
|
|
|
|
|
|
|
|
|
|
<div class="download-box">
|
|
|
|
|
<a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a>
|
|
|
|
|
<p class="filename">BitmapFun.zip</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<p>The {@link
|
|
|
|
|
android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options)
|
|
|
|
|
BitmapFactory.decode*} methods, discussed in the <a href="load-bitmap.html">Load Large Bitmaps
|
|
|
|
|
Efficiently</a> lesson, should not be executed on the main UI thread if the source data is read from
|
|
|
|
|
disk or a network location (or really any source other than memory). The time this data takes to
|
|
|
|
|
load is unpredictable and depends on a variety of factors (speed of reading from disk or network,
|
|
|
|
|
size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags
|
|
|
|
|
your application as non-responsive and the user has the option of closing it (see <a
|
2012-06-21 17:14:39 -07:00
|
|
|
|
href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a> for
|
2012-04-04 17:45:24 -07:00
|
|
|
|
more information).</p>
|
|
|
|
|
|
|
|
|
|
<p>This lesson walks you through processing bitmaps in a background thread using
|
|
|
|
|
{@link android.os.AsyncTask} and shows you how to handle concurrency issues.</p>
|
|
|
|
|
|
|
|
|
|
<h2 id="async-task">Use an AsyncTask</h2>
|
|
|
|
|
|
|
|
|
|
<p>The {@link android.os.AsyncTask} class provides an easy way to execute some work in a background
|
|
|
|
|
thread and publish the results back on the UI thread. To use it, create a subclass and override the
|
|
|
|
|
provided methods. Here’s an example of loading a large image into an {@link
|
|
|
|
|
android.widget.ImageView} using {@link android.os.AsyncTask} and <a
|
|
|
|
|
href="load-bitmap.html#decodeSampledBitmapFromResource">{@code
|
|
|
|
|
decodeSampledBitmapFromResource()}</a>: </p>
|
|
|
|
|
|
|
|
|
|
<a name="BitmapWorkerTask"></a>
|
|
|
|
|
<pre>
|
2012-08-14 14:53:42 -04:00
|
|
|
|
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
|
|
|
|
|
private final WeakReference<ImageView> imageViewReference;
|
2012-04-04 17:45:24 -07:00
|
|
|
|
private int data = 0;
|
|
|
|
|
|
|
|
|
|
public BitmapWorkerTask(ImageView imageView) {
|
|
|
|
|
// Use a WeakReference to ensure the ImageView can be garbage collected
|
2012-08-14 14:53:42 -04:00
|
|
|
|
imageViewReference = new WeakReference<ImageView>(imageView);
|
2012-04-04 17:45:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Decode image in background.
|
|
|
|
|
@Override
|
|
|
|
|
protected Bitmap doInBackground(Integer... params) {
|
|
|
|
|
data = params[0];
|
|
|
|
|
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Once complete, see if ImageView is still around and set bitmap.
|
|
|
|
|
@Override
|
|
|
|
|
protected void onPostExecute(Bitmap bitmap) {
|
|
|
|
|
if (imageViewReference != null && bitmap != null) {
|
|
|
|
|
final ImageView imageView = imageViewReference.get();
|
|
|
|
|
if (imageView != null) {
|
|
|
|
|
imageView.setImageBitmap(bitmap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
<p>The {@link java.lang.ref.WeakReference} to the {@link android.widget.ImageView} ensures that the
|
|
|
|
|
{@link android.os.AsyncTask} does not prevent the {@link android.widget.ImageView} and anything it
|
|
|
|
|
references from being garbage collected. There’s no guarantee the {@link android.widget.ImageView}
|
|
|
|
|
is still around when the task finishes, so you must also check the reference in {@link
|
|
|
|
|
android.os.AsyncTask#onPostExecute(Result) onPostExecute()}. The {@link android.widget.ImageView}
|
|
|
|
|
may no longer exist, if for example, the user navigates away from the activity or if a
|
|
|
|
|
configuration change happens before the task finishes.</p>
|
|
|
|
|
|
|
|
|
|
<p>To start loading the bitmap asynchronously, simply create a new task and execute it:</p>
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
public void loadBitmap(int resId, ImageView imageView) {
|
|
|
|
|
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
|
|
|
|
|
task.execute(resId);
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
<h2 id="concurrency">Handle Concurrency</h2>
|
|
|
|
|
|
|
|
|
|
<p>Common view components such as {@link android.widget.ListView} and {@link
|
|
|
|
|
android.widget.GridView} introduce another issue when used in conjunction with the {@link
|
|
|
|
|
android.os.AsyncTask} as demonstrated in the previous section. In order to be efficient with memory,
|
|
|
|
|
these components recycle child views as the user scrolls. If each child view triggers an {@link
|
|
|
|
|
android.os.AsyncTask}, there is no guarantee that when it completes, the associated view has not
|
|
|
|
|
already been recycled for use in another child view. Furthermore, there is no guarantee that the
|
|
|
|
|
order in which asynchronous tasks are started is the order that they complete.</p>
|
|
|
|
|
|
|
|
|
|
<p>The blog post <a
|
|
|
|
|
href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading
|
|
|
|
|
for Performance</a> further discusses dealing with concurrency, and offers a solution where the
|
|
|
|
|
{@link android.widget.ImageView} stores a reference to the most recent {@link android.os.AsyncTask}
|
|
|
|
|
which can later be checked when the task completes. Using a similar method, the {@link
|
|
|
|
|
android.os.AsyncTask} from the previous section can be extended to follow a similar pattern.</p>
|
|
|
|
|
|
|
|
|
|
<p>Create a dedicated {@link android.graphics.drawable.Drawable} subclass to store a reference
|
|
|
|
|
back to the worker task. In this case, a {@link android.graphics.drawable.BitmapDrawable} is used so
|
|
|
|
|
that a placeholder image can be displayed in the {@link android.widget.ImageView} while the task
|
|
|
|
|
completes:</p>
|
|
|
|
|
|
|
|
|
|
<a name="AsyncDrawable"></a>
|
|
|
|
|
<pre>
|
|
|
|
|
static class AsyncDrawable extends BitmapDrawable {
|
2012-08-14 14:53:42 -04:00
|
|
|
|
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
|
2012-04-04 17:45:24 -07:00
|
|
|
|
|
|
|
|
|
public AsyncDrawable(Resources res, Bitmap bitmap,
|
|
|
|
|
BitmapWorkerTask bitmapWorkerTask) {
|
|
|
|
|
super(res, bitmap);
|
|
|
|
|
bitmapWorkerTaskReference =
|
2012-08-14 14:53:42 -04:00
|
|
|
|
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
|
2012-04-04 17:45:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public BitmapWorkerTask getBitmapWorkerTask() {
|
|
|
|
|
return bitmapWorkerTaskReference.get();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
<p>Before executing the <a href="#BitmapWorkerTask">{@code BitmapWorkerTask}</a>, you create an <a
|
|
|
|
|
href="#AsyncDrawable">{@code AsyncDrawable}</a> and bind it to the target {@link
|
|
|
|
|
android.widget.ImageView}:</p>
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
public void loadBitmap(int resId, ImageView imageView) {
|
|
|
|
|
if (cancelPotentialWork(resId, imageView)) {
|
|
|
|
|
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
|
|
|
|
|
final AsyncDrawable asyncDrawable =
|
|
|
|
|
new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
|
|
|
|
|
imageView.setImageDrawable(asyncDrawable);
|
|
|
|
|
task.execute(resId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
<p>The {@code cancelPotentialWork} method referenced in the code sample above checks if another
|
|
|
|
|
running task is already associated with the {@link android.widget.ImageView}. If so, it attempts to
|
|
|
|
|
cancel the previous task by calling {@link android.os.AsyncTask#cancel cancel()}. In a small number
|
|
|
|
|
of cases, the new task data matches the existing task and nothing further needs to happen. Here is
|
|
|
|
|
the implementation of {@code cancelPotentialWork}:</p>
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
public static boolean cancelPotentialWork(int data, ImageView imageView) {
|
|
|
|
|
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
|
|
|
|
|
|
|
|
|
|
if (bitmapWorkerTask != null) {
|
|
|
|
|
final int bitmapData = bitmapWorkerTask.data;
|
|
|
|
|
if (bitmapData != data) {
|
|
|
|
|
// Cancel previous task
|
|
|
|
|
bitmapWorkerTask.cancel(true);
|
|
|
|
|
} else {
|
|
|
|
|
// The same work is already in progress
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// No task associated with the ImageView, or an existing task was cancelled
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
<p>A helper method, {@code getBitmapWorkerTask()}, is used above to retrieve the task associated
|
|
|
|
|
with a particular {@link android.widget.ImageView}:</p>
|
|
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
|
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
|
|
|
|
|
if (imageView != null) {
|
|
|
|
|
final Drawable drawable = imageView.getDrawable();
|
|
|
|
|
if (drawable instanceof AsyncDrawable) {
|
|
|
|
|
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
|
|
|
|
|
return asyncDrawable.getBitmapWorkerTask();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
<p>The last step is updating {@code onPostExecute()} in <a href="#BitmapWorkerTask">{@code
|
|
|
|
|
BitmapWorkerTask}</a> so that it checks if the task is cancelled and if the current task matches the
|
|
|
|
|
one associated with the {@link android.widget.ImageView}:</p>
|
|
|
|
|
|
|
|
|
|
<a name="BitmapWorkerTaskUpdated"></a>
|
|
|
|
|
<pre>
|
2012-08-14 14:53:42 -04:00
|
|
|
|
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
|
2012-04-04 17:45:24 -07:00
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onPostExecute(Bitmap bitmap) {
|
|
|
|
|
<strong>if (isCancelled()) {
|
|
|
|
|
bitmap = null;
|
|
|
|
|
}</strong>
|
|
|
|
|
|
|
|
|
|
if (imageViewReference != null && bitmap != null) {
|
|
|
|
|
final ImageView imageView = imageViewReference.get();
|
|
|
|
|
<strong>final BitmapWorkerTask bitmapWorkerTask =
|
|
|
|
|
getBitmapWorkerTask(imageView);</strong>
|
|
|
|
|
if (<strong>this == bitmapWorkerTask &&</strong> imageView != null) {
|
|
|
|
|
imageView.setImageBitmap(bitmap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
<p>This implementation is now suitable for use in {@link android.widget.ListView} and {@link
|
|
|
|
|
android.widget.GridView} components as well as any other components that recycle their child
|
|
|
|
|
views. Simply call {@code loadBitmap} where you normally set an image to your {@link
|
|
|
|
|
android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this
|
2012-08-14 14:53:42 -04:00
|
|
|
|
would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>
|