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