Skip to content

Commit

Permalink
ListCursorAdapter: Avoid calling BitmapFactory.decodeStream() in main…
Browse files Browse the repository at this point in the history
… 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 fdef5fd
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 74 deletions.
65 changes: 52 additions & 13 deletions app/src/main/java/com/murrayc/galaxyzoo/app/ListCursorAdapter.java
Expand Up @@ -78,6 +78,54 @@ public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
return new ViewHolder(v, this); 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 @Override
public void onBindViewHolder(final ViewHolder viewHolder, int i) { public void onBindViewHolder(final ViewHolder viewHolder, int i) {
mCursor.moveToPosition(i); mCursor.moveToPosition(i);
Expand All @@ -97,24 +145,15 @@ public void onBindViewHolder(final ViewHolder viewHolder, int i) {
*/ */


if (!TextUtils.isEmpty(imageUriStr)) { if (!TextUtils.isEmpty(imageUriStr)) {
boolean imageShown = false;
if (thumbnailDownloaded) { if (thumbnailDownloaded) {
final Bitmap bMap = UiUtils.getBitmapFromContentUri(mContext, imageUriStr); //viewHolder.imageView.setImageDrawable(null);
if (bMap != null) { final ShowViewHolderImageFromContentProviderTask task = new ShowViewHolderImageFromContentProviderTask(mContext, viewHolder, viewHolder.getPosition(), itemId);
viewHolder.imageView.setImageBitmap(bMap); task.execute(imageUriStr);
} else {
Utils.abandonItem(mContext, itemId);
}
}

if (imageShown) {
viewHolder.progressBar.setVisibility(View.GONE);
} else { } else {
//We are still waiting for it to download:
viewHolder.progressBar.setVisibility(View.VISIBLE); viewHolder.progressBar.setVisibility(View.VISIBLE);
//TODO: Make sure it gets updated later?
} }



if (!favorite && !done && !uploaded) { if (!favorite && !done && !uploaded) {
viewHolder.iconsPanel.setVisibility(View.GONE); viewHolder.iconsPanel.setVisibility(View.GONE);
} else { } else {
Expand Down
75 changes: 14 additions & 61 deletions app/src/main/java/com/murrayc/galaxyzoo/app/SubjectFragment.java
Expand Up @@ -20,6 +20,7 @@
package com.murrayc.galaxyzoo.app; package com.murrayc.galaxyzoo.app;


import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
Expand Down Expand Up @@ -211,66 +212,6 @@ private void updateFromCursor() {
showImage(); 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() { private void showImage() {
final Activity activity = getActivity(); final Activity activity = getActivity();
if (activity == null) if (activity == null)
Expand Down Expand Up @@ -301,7 +242,19 @@ private void showImage() {
} }


if (!TextUtils.isEmpty(imageUriStr)) { 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); task.execute(imageUriStr);
} }
} }
Expand Down
54 changes: 54 additions & 0 deletions app/src/main/java/com/murrayc/galaxyzoo/app/UiUtils.java
Expand Up @@ -29,22 +29,76 @@
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.Toast; import android.widget.Toast;


import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.ref.WeakReference;


/** /**
* Created by murrayc on 5/21/14. * Created by murrayc on 5/21/14.
*/ */
class UiUtils { 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. /** Get a URI for an image in the ItemsContentProvider.
* *
* Don't call this from a main (UI) thread. * Don't call this from a main (UI) thread.
Expand Down

0 comments on commit fdef5fd

Please sign in to comment.