Permalink
Browse files

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/
  • Loading branch information...
murraycu committed Nov 21, 2014
1 parent 9edc930 commit fdef5fdba83d6ef4dd767e728a08378e294bc631
@@ -78,6 +78,54 @@ public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
return new ViewHolder(v, this);
}

class ShowViewHolderImageFromContentProviderTask extends UiUtils.ShowImageFromContentProviderTask {
final WeakReference<ViewHolder> 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 {
@@ -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<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private final WeakReference<ItemFragment> 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);
}
}
@@ -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<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private final WeakReference<Context> 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.

0 comments on commit fdef5fd

Please sign in to comment.