summaryrefslogtreecommitdiffstats
path: root/docs/html/training/displaying-bitmaps/process-bitmap.jd
diff options
context:
space:
mode:
Diffstat (limited to 'docs/html/training/displaying-bitmaps/process-bitmap.jd')
-rw-r--r--docs/html/training/displaying-bitmaps/process-bitmap.jd239
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.
+ &#64;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.
+ &#64;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> {
+ ...
+
+ &#64;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