3

I have a simple gridview that shows bitmaps. The view is very jenky due to the garbage collection. I have come experience with garbage collection in listviews and have a fairly heavy weight one running smoothly. However I cannot understand what is happening here. The UI thread is blocked so much when I fling the view. The stack traces looks like so

12-20 13:22:46.187: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 106ms 12-20 13:22:46.187: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 106ms 12-20 13:22:46.187: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 106ms 12-20 13:22:46.187: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 106ms 12-20 13:22:46.187: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 107ms 12-20 13:22:46.187: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 107ms 12-20 13:22:46.191: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 108ms 12-20 13:22:46.191: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 108ms 12-20 13:22:46.191: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 109ms 12-20 13:22:46.191: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 108ms 12-20 13:22:46.191: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 108ms 12-20 13:22:46.191: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 109ms 12-20 13:22:46.191: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 109ms 12-20 13:22:46.191: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 110ms 12-20 13:22:46.195: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 111ms 12-20 13:22:46.195: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 110ms 12-20 13:22:46.195: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 111ms 12-20 13:22:46.195: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 110ms 12-20 13:22:46.195: D/dalvikvm(17979): WAIT_FOR_CONCURRENT_GC blocked 114ms

The adapter I am using is like this....

public class PhotoSelectorAdapter extends BaseAdapter {

private Context context;
private String[] paths;
private final Executor executor = Executors.newFixedThreadPool(20);

public PhotoSelectorAdapter(Context context, String[] paths) {
    this.context = context;
    this.paths = paths;
}

public int getCount() {
    return paths.length;
}

public Object getItem(int position) {
    return null;
}

public long getItemId(int position) {
    return 0;
}

public View getView(int position, View convertView, ViewGroup parent) {
    ImageView imageView;
    ImageGetter imageGetter;
    if (convertView == null) {
        imageView = new ImageView(context);
        imageView.setLayoutParams(new GridView.LayoutParams(150, 150));
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imageView.setPadding(8, 8, 8, 8);
    } else {
        imageView = (ImageView) convertView;
        imageView.setImageBitmap(null);
        imageGetter = (ImageGetter) imageView.getTag();
       if (imageGetter != null) {
            imageGetter.cancel(true);
        }
    }
    imageGetter = new ImageGetter(context, paths[position], imageView);
    executor.execute(imageGetter.future());
    imageView.setTag(imageGetter);
    return imageView;
}

class ImageGetter extends RoboAsyncTask<Bitmap> {

    private WeakReference<ImageView> imageViewReference;
    private String path;
    BitmapFactory.Options options = new BitmapFactory.Options();

    protected ImageGetter(Context context, String path, ImageView imageView) {
        super(context);
        this.path = path;
        this.imageViewReference = new WeakReference<ImageView>(imageView);
    }

    @Override
    public Bitmap call() throws Exception {
        options.inSampleSize = 10;
        return BitmapFactory.decodeFile(path, options);
    }

    @Override
    protected void onSuccess(Bitmap bitmap) throws Exception {
        super.onSuccess(bitmap);
        if (future.isCancelled()) {
            bitmap = null;
        } else {
            if (imageViewReference != null) {
                ImageView imageView = imageViewReference.get();
                if (imageView != null) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    }
}

The steps that I have taken to avoid garbage collection is

  • taking a sample size of 10 for the bitmap
  • canceling a task if it is not needed anymore in the hope of not creating unnecessary bitmaps

What is going on?

jiduvah
  • 5,108
  • 6
  • 39
  • 55

1 Answers1

0

How big are your images? Decoding too many of them at the same time might be too much, even when done in the background. The trashing might be lessened if you created an in-memory thumbnail cache. You can also save the thumbnails to disk and have your cache load them from there once the memory cache is full.

dmon
  • 30,048
  • 8
  • 87
  • 96
  • It is pulling the images from the Mediastore so they are varying sizes. It's also getting all the images on the device so it would be too much to process all of the images and make thumbnails from them. – jiduvah Dec 20 '12 at 14:30
  • Hmmmm the MediaStore pre-generates the thumbnails, maybe you can just use those: `MediaStore.Images.Thumbnails.getThumbnail(contentResolver, mediaId, MediaStore.Images.Thumbnails.MICRO_KIND, null);` There's also "MINI_KIND" – dmon Dec 20 '12 at 14:39
  • http://stackoverflow.com/questions/3680357/how-to-query-android-mediastore-content-provider-avoiding-orphaned-images this post suggest thumbnails are not created on every device – jiduvah Dec 20 '12 at 15:54
  • I actually read you suggestion incorrectly. I change the query on the media store to return thumbnails. I realised that you was suggesting the Mediastore loads the thumbnail for me. That did work. There is still quiet a lot of GC but it is now under 0.020 seconds so, it's very smooth. – jiduvah Dec 20 '12 at 16:45
  • Huzzah! Yeah it will probably still GC but it will be through the media store, and I'm guessing they have a cache, etc etc. – dmon Dec 20 '12 at 17:44
  • I think they just store all thumbnails whenever they are requested. So not quiet a cache but something that acts in a similar way. Certainly no memory cache – jiduvah Dec 20 '12 at 17:59