| 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> |
| <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> |