diff options
Diffstat (limited to 'docs/html/training/displaying-bitmaps/process-bitmap.jd')
-rw-r--r-- | docs/html/training/displaying-bitmaps/process-bitmap.jd | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/docs/html/training/displaying-bitmaps/process-bitmap.jd b/docs/html/training/displaying-bitmaps/process-bitmap.jd new file mode 100644 index 0000000..c1450b4 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/process-bitmap.jd @@ -0,0 +1,239 @@ +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/design/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/design/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>
\ No newline at end of file |