From fdef5fdba83d6ef4dd767e728a08378e294bc631 Mon Sep 17 00:00:00 2001 From: Murray Cumming Date: Fri, 21 Nov 2014 12:49:29 +0100 Subject: [PATCH] ListCursorAdapter: Avoid calling BitmapFactory.decodeStream() in main thread. By using an AsyncTask in onBindViewHolder(). I don't like that much, but it seems to be OK with a position check, as suggested here (for ListView, not RecyclerView): http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/ --- .../galaxyzoo/app/ListCursorAdapter.java | 65 ++++++++++++---- .../galaxyzoo/app/SubjectFragment.java | 75 ++++--------------- .../com/murrayc/galaxyzoo/app/UiUtils.java | 54 +++++++++++++ 3 files changed, 120 insertions(+), 74 deletions(-) diff --git a/app/src/main/java/com/murrayc/galaxyzoo/app/ListCursorAdapter.java b/app/src/main/java/com/murrayc/galaxyzoo/app/ListCursorAdapter.java index 53e91532..c8b960c7 100644 --- a/app/src/main/java/com/murrayc/galaxyzoo/app/ListCursorAdapter.java +++ b/app/src/main/java/com/murrayc/galaxyzoo/app/ListCursorAdapter.java @@ -78,6 +78,54 @@ public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { return new ViewHolder(v, this); } + class ShowViewHolderImageFromContentProviderTask extends UiUtils.ShowImageFromContentProviderTask { + final WeakReference viewHolderReference; + final int position; + final String itemId; + + public ShowViewHolderImageFromContentProviderTask(final Context fragment, final ViewHolder viewHolder, int position, final String itemId) { + super(viewHolder.imageView, fragment); + + this.viewHolderReference = new WeakReference<>(viewHolder); + this.position = position; + this.itemId = itemId; + } + + @Override + protected void onPostExecute(final Bitmap bitmap) { + + if (viewHolderReference == null) { + return; + } + + final ViewHolder viewHolder = viewHolderReference.get(); + if (viewHolder == null) { + return; + } + + //Check that we are still dealing with the same position, + //because the ImageView might be recycled for use with a different position. + if (viewHolder.getPosition() != position) { + return; + } + + super.onPostExecute(bitmap); + + if (bitmap != null) { + //Hide the progress indicator now that we are showing the image. + viewHolder.progressBar.setVisibility(View.GONE); + } else { + //Show the progress indicator because we have no image: + viewHolder.progressBar.setVisibility(View.VISIBLE); + + //Something was wrong with the (cached) image, + //so just abandon this whole item. + //That seems safer and simpler than trying to recover just one of the 3 images. + Utils.abandonItem(mContext, itemId); + } + } + } + @Override public void onBindViewHolder(final ViewHolder viewHolder, int i) { mCursor.moveToPosition(i); @@ -97,24 +145,15 @@ public void onBindViewHolder(final ViewHolder viewHolder, int i) { */ if (!TextUtils.isEmpty(imageUriStr)) { - boolean imageShown = false; if (thumbnailDownloaded) { - final Bitmap bMap = UiUtils.getBitmapFromContentUri(mContext, imageUriStr); - if (bMap != null) { - viewHolder.imageView.setImageBitmap(bMap); - } else { - Utils.abandonItem(mContext, itemId); - } - } - - if (imageShown) { - viewHolder.progressBar.setVisibility(View.GONE); + //viewHolder.imageView.setImageDrawable(null); + final ShowViewHolderImageFromContentProviderTask task = new ShowViewHolderImageFromContentProviderTask(mContext, viewHolder, viewHolder.getPosition(), itemId); + task.execute(imageUriStr); } else { + //We are still waiting for it to download: viewHolder.progressBar.setVisibility(View.VISIBLE); - //TODO: Make sure it gets updated later? } - if (!favorite && !done && !uploaded) { viewHolder.iconsPanel.setVisibility(View.GONE); } else { diff --git a/app/src/main/java/com/murrayc/galaxyzoo/app/SubjectFragment.java b/app/src/main/java/com/murrayc/galaxyzoo/app/SubjectFragment.java index ad8c343b..0d93ee5e 100644 --- a/app/src/main/java/com/murrayc/galaxyzoo/app/SubjectFragment.java +++ b/app/src/main/java/com/murrayc/galaxyzoo/app/SubjectFragment.java @@ -20,6 +20,7 @@ package com.murrayc.galaxyzoo.app; import android.app.Activity; +import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; @@ -211,66 +212,6 @@ private void updateFromCursor() { showImage(); } - //See http://developer.android.com/training/displaying-bitmaps/process-bitmap.html - private static class ShowImageFromContentProviderTask extends AsyncTask { - private final WeakReference imageViewReference; - private final WeakReference fragmentReference; - - private String strUri = null; - - public ShowImageFromContentProviderTask(final ImageView imageView, final ItemFragment fragment) { - // Use a WeakReference to ensure the ImageView can be garbage collected - imageViewReference = new WeakReference<>(imageView); - - // Use a WeakReference to ensure the ImageView can be garbage collected - fragmentReference = new WeakReference<>(fragment); - } - - // Decode image in background. - @Override - protected Bitmap doInBackground(String... params) { - strUri = params[0]; - - if (fragmentReference != null) { - final ItemFragment fragment = fragmentReference.get(); - if (fragment != null) { - return UiUtils.getBitmapFromContentUri(fragment.getActivity(), strUri); - } - } - - return null; - } - - // Once complete, see if ImageView is still around and set bitmap. - // This avoids calling the ImageView methods in the non-main thread. - @Override - protected void onPostExecute(final Bitmap bitmap) { - if (bitmap == null) { - //Something was wrong with the (cached) image, - //so just abandon this whole item. - //That seems safer and simpler than trying to recover just one of the 3 images. - if (fragmentReference != null) { - final ItemFragment fragment = fragmentReference.get(); - if (fragment != null) { - fragment.abandonItem(); - } - } - } - - if (imageViewReference != null && bitmap != null) { - final ImageView imageView = imageViewReference.get(); - if (imageView != null) { - if (bitmap != null) { - imageView.setImageBitmap(bitmap); - } else { - - imageView.setImageResource(android.R.color.transparent); - } - } - } - } - } - private void showImage() { final Activity activity = getActivity(); if (activity == null) @@ -301,7 +242,19 @@ private void showImage() { } if (!TextUtils.isEmpty(imageUriStr)) { - final ShowImageFromContentProviderTask task = new ShowImageFromContentProviderTask(mImageView, this); + final UiUtils.ShowImageFromContentProviderTask task = new UiUtils.ShowImageFromContentProviderTask(mImageView, activity) { + @Override + protected void onPostExecute(final Bitmap bitmap) { + super.onPostExecute(bitmap); + + if (bitmap == null) { + //Something was wrong with the (cached) image, + //so just abandon this whole item. + //That seems safer and simpler than trying to recover just one of the 3 images. + SubjectFragment.this.abandonItem(); + } + } + }; task.execute(imageUriStr); } } diff --git a/app/src/main/java/com/murrayc/galaxyzoo/app/UiUtils.java b/app/src/main/java/com/murrayc/galaxyzoo/app/UiUtils.java index 77c15895..d5c29498 100644 --- a/app/src/main/java/com/murrayc/galaxyzoo/app/UiUtils.java +++ b/app/src/main/java/com/murrayc/galaxyzoo/app/UiUtils.java @@ -29,22 +29,76 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.ActivityOptionsCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.Toolbar; import android.view.View; +import android.widget.ImageView; import android.widget.Toast; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.WeakReference; /** * Created by murrayc on 5/21/14. */ class UiUtils { + //See http://developer.android.com/training/displaying-bitmaps/process-bitmap.html + static class ShowImageFromContentProviderTask extends AsyncTask { + private final WeakReference imageViewReference; + private final WeakReference contextReference; + + private String strUri = null; + + public ShowImageFromContentProviderTask(final ImageView imageView, final Context fragment) { + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference<>(imageView); + + // Use a WeakReference to ensure the ImageView can be garbage collected + contextReference = new WeakReference<>(fragment); + } + + // Decode image in background. + @Override + protected Bitmap doInBackground(String... params) { + strUri = params[0]; + + if (contextReference != null) { + final Context context = contextReference.get(); + if (context != null) { + return UiUtils.getBitmapFromContentUri(context, strUri); + } + } + + return null; + } + + // Once complete, see if ImageView is still around and set bitmap. + // This avoids calling the ImageView methods in the non-main thread. + @Override + protected void onPostExecute(final Bitmap bitmap) { + //Callers should maybe use a derived class that overrides this + //to call abandonItem() when the bitmap is null. + + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + if (bitmap != null) { + imageView.setImageBitmap(bitmap); + } else { + + imageView.setImageResource(android.R.color.transparent); + } + } + } + } + } + /** Get a URI for an image in the ItemsContentProvider. * * Don't call this from a main (UI) thread.