androidandroid-gridviewbaseadapterandroid-lru-cache

Android - Image GridView gallery with LruCache - images keep changing


I wrote this adapter class based on examples of LruCache usage. The problem I encountered is the following:

If I do a fast swipe and the GridView scrolls really far down or up, (where the bitmaps are not yet cached) the images are drawn with the wrong bitmaps instead of staying empty. The images keep changing while the GridView is still (not scrolling), until finaly the correct ones are loaded and then then everything is as it should be. I cant see anything wrong with my code...Any thoughts on how to avoid this?

public class ImageGalleryAdapter extends BaseAdapter {
    @SuppressLint("NewApi")

    private MyLruCache mMemoryCache;
    int size;
    BaseAdapter adapter;
    LayoutInflater mInflater;
    ArrayList<ImageItem> photos;
    Context con;
    ContentResolver cr;


    @SuppressLint("NewApi")
    public ImageGalleryAdapter(ArrayList<ImageItem> photoList,Context con, int size, GridView gridView) { 
        this.photos=photoList;
        this.size=size;
        this.con=con;
        adapter=this;

        mInflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        cr = con.getContentResolver();
        final int memClass = ((ActivityManager)con.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();

        // Use 1/8th of the available memory for this memory cache.
        final int cacheSize = 1024 * 1024 * memClass / 4;

        mMemoryCache = new MyLruCache(cacheSize) {

            @SuppressLint("NewApi")
            protected int sizeOf(String key, Bitmap bitmap) {
                // The cache size will be measured in bytes rather than number of items.
                return bitmap.getByteCount();   
            }

        };
    } 

    public int getCount() {   return photos.size();   } 
    public Object getItem(int position) {   return photos.get(position); } 
    public long getItemId(int position) {   return position;   } 

    @SuppressLint("InflateParams")
    public View getView( final int position, View convertView, ViewGroup parent) { 
        ImageHolder holder;
        if (convertView == null) { 
            holder = new ImageHolder();
            convertView = mInflater.inflate(R.layout.image_gallery_item, null);
            holder.imageview = (ImageView) convertView.findViewById(R.id.thumbImage);
            holder.checkbox = (CheckBox) convertView.findViewById(R.id.itemCheckBox);
            convertView.setTag(holder);
        } else { holder = (ImageHolder) convertView.getTag(); }

        FrameLayout.LayoutParams params=(FrameLayout.LayoutParams)holder.imageview.getLayoutParams();
        params.width=size; params.height=size;
        holder.imageview.setLayoutParams(params);


        final Bitmap bm = mMemoryCache.getBitmapFromCache(photos.get(position).filename);
        if (bm == null){
            mMemoryCache.getNewBitmap(holder.imageview, position);

        }

         if(bm!=null && !bm.isRecycled()){
            holder.imageview.setImageBitmap(bm);
            holder.imageview.setScaleType(ScaleType.FIT_XY);

        }
        else{
            holder.imageview.setImageResource(R.drawable.loading);
        }

        return convertView; 
    } 

    class ImageHolder {
        ImageView imageview;
        CheckBox checkbox;
    }


    class MyLruCache extends LruCache<String, Bitmap>{

        public MyLruCache(int maxSize) {
            super(maxSize);
        }

        @SuppressLint("NewApi")
        public void addBitmapToCache(String key, Bitmap bitmap) {
            if (getBitmapFromCache(key) == null) {
                put(key, bitmap);   
            }   
        }

        @SuppressLint("NewApi")
        public Bitmap getBitmapFromCache(String key) {
            return (Bitmap) get(key);   
        }


        public void getNewBitmap(ImageView imageview, int position) {
            BitmapWorkerTask task = new BitmapWorkerTask(imageview);
            task.execute(photos.get(position));
        }

        class BitmapWorkerTask extends AsyncTask<ImageItem, Void, Bitmap>{

            private final WeakReference<ImageView> imageViewReference;
            ImageItem item;
            public BitmapWorkerTask(ImageView imageView) {
                // Use a WeakReference to ensure the ImageView can be garbage collected
                imageViewReference = new WeakReference<ImageView>(imageView);
            }

            @Override
            protected Bitmap doInBackground(ImageItem... params) {
                ImageItem item = params[0];
                this.item=item;
                String filename = item.filename;
                final Bitmap bitmap = getBitmapForImageItem(item);
                mMemoryCache.addBitmapToCache(String.valueOf(filename), bitmap);
                return bitmap;
            }

            @Override
            protected void onPostExecute(Bitmap bitmap) {
                if (imageViewReference != null && bitmap != null) {
                    final ImageView imageView = (ImageView)imageViewReference.get();
                    if (imageView != null) {
                        imageView.setScaleType(ScaleType.FIT_XY);
                        imageView.setImageBitmap(bitmap);
                    }
                }
            }
        } 
    }

    public Bitmap getBitmapForImageItem(ImageItem item){
        long imageID=item.imageId;
        Bitmap bm = null;
        if(!item.isVideo){
            bm=MediaStore.Images.Thumbnails.getThumbnail( cr, imageID, MediaStore.Images.Thumbnails.MICRO_KIND, null);
        }
        else{
            bm=MediaStore.Video.Thumbnails.getThumbnail( cr, imageID, MediaStore.Video.Thumbnails.MICRO_KIND, null);
        }

         if(bm==null){
            try{
                Bitmap newbitmap=F.bitmapInScreenSizeFromPath(item.filename, con, 70, 70);
                bm=F.cropAndScaleBitmap(newbitmap, 70, 70);
                newbitmap.recycle(); 
            }catch(Exception e){}
        }
        return bm; 
    }


}

Solution

  • The problem is in your BitmapWorkerTask onPostExecute() that always set the Bitmap to the ImageView and in a GridView the views are reused, so the changing images you are seeing are the updates of images when you scrolled and the same ImageView was used for various images because of the long scroll.

    My suggestion is to add a position in your ImageHolder and update the value on getView(), then on BitmapWorkerTask onPostExecute() you get the current position of the ImageView via the ImageHolder and if it's not the same position as the Bitmap you don't need to update it since the image has already been scrolled out of screen.