7571542c9d
Change-Id: If780ad8bfabd66de21842bdb465d86ab24b2940c
236 lines
9.6 KiB
Plaintext
236 lines
9.6 KiB
Plaintext
page.title=Processing Bitmaps Off the UI Thread
|
||
parent.title=Displaying Bitmaps Efficiently
|
||
parent.link=index.html
|
||
|
||
trainingnavtop=true
|
||
|
||
@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>
|
||
<li><a href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a></li>
|
||
<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
|
||
href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a> for
|
||
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>
|
||
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
|
||
private final WeakReference<ImageView> imageViewReference;
|
||
private int data = 0;
|
||
|
||
public BitmapWorkerTask(ImageView imageView) {
|
||
// Use a WeakReference to ensure the ImageView can be garbage collected
|
||
imageViewReference = new WeakReference<ImageView>(imageView);
|
||
}
|
||
|
||
// 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 {
|
||
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
|
||
|
||
public AsyncDrawable(Resources res, Bitmap bitmap,
|
||
BitmapWorkerTask bitmapWorkerTask) {
|
||
super(res, bitmap);
|
||
bitmapWorkerTaskReference =
|
||
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
|
||
}
|
||
|
||
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>
|
||
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
|
||
...
|
||
|
||
@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
|
||
would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>
|