Fresco: Slow loading image from cache during scrolling in recyclerview

Created on 2 Jun 2015  路  18Comments  路  Source: facebook/fresco

I have a gallery of images using recyclerview with different size of images. At first draw, it will load and fade-in image (as it should be) but when I scroll back to the top the image is displayed like first time and it takes a little time to be displayed and fade-in (I want it to be instantly there and not fade-in)

bug

All 18 comments

Are you doing any post-processing on the images?
How many images do you have to scroll back until you reach the top image? Maybe the images are big and our bitmap cache cannot fit them all, meaning that the oldest one will have to be evicted and then decoded again later (therefore, not being able to display immediately).
What are the dimensions of those images? If they are much bigger than the view, you should resize them first. See Resizing.

I think what @anthony1111 says is exactly the same as follows: https://androiddevx.wordpress.com/2014/12/05/recycler-view-pre-cache-views/.

Here am I have the same, too. I have tried the steps described in the above link but was no luck. But, it worked when it comes with Glide. Glide pre-renders the image correctly when on onAttach(), so there was no delay in showing the content. But, Fresco seems to start rendering the image when the actual view comes to on-screen, regardless of onAttach() calls.

If you need the demo, I can provide you one.

If the image is in bitmap cache, we don't do a fade-in animation. If it fades in, that means the image has been evicted from cache.

You can as we said reduce the footprint of the images in cache by resizing them. You can also experiment with increasing your cache size, although that may hurt the rest of your app.

I'm dealing with huge bitmaps, so it should be evicted from the memory cache to prevent OOM.
What I am trying to do is pre-load the bitmap to the DraweeView before it shows on the screen, doing it so can both reduce the memory usage and enhance the user experience. (The image re-loaded to the view, but user will not see it since the image will be loaded before user sees it.)

Is there any way to start load image when onAttach() called?

I think calling the prefetchToBitmapCache(ImageRequest, Object) method on ImagePipeline in your `onAttach()' would do what you ask for.

@feichngr Tried your suggestion but no luck. Still starts showing the image when the DraweeView comes to on-screen.

Following is the snippet of my RecyclerView adapter:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (holder instanceof FrescoImageHolder) {
        Log.e("BIND", "Binding holder " + holder);
        SimpleDraweeView view = (SimpleDraweeView) holder.itemView;

        view.setAspectRatio(items[position].getAspectRatio());

        DraweeController ctrl = Fresco.newDraweeControllerBuilder()
                .setUri(items[position].uri)
                .setOldController(view.getController())
                .setAutoPlayAnimations(true)
                .build();

        Fresco.getImagePipeline().prefetchToBitmapCache(
                ImageRequest.fromUri(items[position].getUri()), view.getContext());

        view.setController(ctrl);
    }
}

A prefetch at that point won't do any good. You need to prefetch an item 2-3 rows _ahead_ (or behind, depending on the scroll direction) of the item in the current row.

I don't think this is caused by the prefetching speed. It seems to okay prefetching the image before it is shown, in my case, as an amount of device screen height.

If the DraweeView starts loading the image just on onAttach() call, there will be no problem. (Glide loads and shows the image on onAttach() calls, so the image properly showed on the view before the user sees it)

But, DraweeView seems to start show the image when it comes to on-screen, regardless of onAttach() calls. This causes the problem.

The onAttach call occurs when the image comes on-screen, so the behaviour of Glide and Fresco is the same.

The question is whether the image is in (bitmap) cache when the image comes on-screen - if so, it will be shown immediately, if not, there will be a delay. As others have mentioned, if you need it to be available immediately you should prefetch to bitmap cache ahead of time.

@IanChilds Please give me an example on how to prefetch to bitmap cache ahead of time. & how to assign that prefetched bitmap to the SimpleDraweeView.

My recycler adapter:

`

public void onBindViewHolder(final Imgur_RV_Holder holder, int position) {

    Uri uri = Uri.parse(datumList.get(position).link);

    ImageRequest imageRequest = ImageRequestBuilder
            .newBuilderWithSource(uri)
            //.setResizeOptions(new ResizeOptions(parent.getWidth(),150))
            .setProgressiveRenderingEnabled(true)
            .setLocalThumbnailPreviewsEnabled(true)
            .build();

    Fresco.getImagePipeline().prefetchToBitmapCache(imageRequest, holder.draweeView.getContext());


    DraweeController controller = Fresco.newDraweeControllerBuilder()
            .setImageRequest(imageRequest)
            .setOldController(holder.draweeView.getController())
            .setAutoPlayAnimations(false)
            .build();


    holder.draweeView.setController(controller);


    holder.textView.setText(position+" : "+datumList.get(position).id+"."+datumList.get(position).type+"  size:"+datumList.get(position).size+" IS:"+inMemoryCache);}

`

There is no point in calling prefetchToBitmapCache in onBindViewHolder, since that is the point that the image comes onto the screen.

You should call prefetchToBitmapCache at some earlier point, e.g. when you set the adapter for your recycler view (as long as it is not too big). Apart from that, your code looks good.

@lanChilds Thank you for answering my question. I tried using prefetchToBitmapCache as you suggested. But its not working out. Please suggest me am way to implement it.

My recycler adapter.
I tried prefetchToBitmapCache in the constructor. but its not prefetching :-1:


public Imgur_RV_Adapter_2(List<Datum> list , Context context){
        this.context=context;
        datumList = list;
        i=0;
        imagePipeline = Fresco.getImagePipeline();
        for(Datum datum : list) {
            ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(datum.link)).build();
            Fresco.getImagePipeline().prefetchToBitmapCache(request, context);
            Fresco.getImagePipeline().prefetchToDiskCache(request, context);
            Log.d(TAG,""+i+datum.link);
            i++;
        }


    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        Log.d(TAG,"onAttachedToRecyclerView");

    }

    @Override
    public void onViewAttachedToWindow(Imgur_RV_Holder holder) {
        super.onViewAttachedToWindow(holder);
        Log.d(TAG,"onAttachedToRecyclerView Adapter"+holder.getAdapterPosition());
        Log.d(TAG,"onAttachedToRecyclerView Layout"+holder.getLayoutPosition());
        request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(datumList.get(holder.getAdapterPosition()).link)).build();
        Fresco.getImagePipeline().prefetchToBitmapCache(request, context);

    }



    @Override
    public Imgur_RV_Holder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.imgur_card_fresco,parent,false);
        this.parent = parent;
        return new Imgur_RV_Holder(view);

}



    @Override
    public void onBindViewHolder(final Imgur_RV_Holder holder, int position) {
        this.holder = holder;
        Log.d(TAG,"POSITION:"+position);

        Uri uri = Uri.parse(datumList.get(position).link);


        ImageRequest imageRequest = ImageRequestBuilder
                .newBuilderWithSource(uri)
                //.setResizeOptions(new ResizeOptions(parent.getWidth(),150))
                .setProgressiveRenderingEnabled(true)
                .setLocalThumbnailPreviewsEnabled(true)
                .setImageType(ImageRequest.ImageType.SMALL)
                .build();




        DraweeController controller = Fresco.newDraweeControllerBuilder()
                .setImageRequest(imageRequest)
                .setOldController(holder.draweeView.getController())
                .setAutoPlayAnimations(false)
                .build();


        boolean inMemoryCache = Fresco.getImagePipeline().isInBitmapMemoryCache(uri);

        holder.draweeView.setController(controller);



        holder.textView.setText(position+" : "+datumList.get(position).id+"."+datumList.get(position).type+"  size:"+datumList.get(position).size+" IS:"+inMemoryCache);


    }

So I tried in my Activity's observer before setting the recyclerview adapter :+1:

@Override
            public void onNext(List<Datum> data) {
                try {
                    Log.d(TAG,"Size:"+data.size());
                    for(Datum list : data) {
                        Log.d(TAG,list.id+":"+list.link);
                    }
                    for(Datum datum : data) {
                        Uri uri = Uri.parse(datum.link);
                        ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).build();
                        Fresco.getImagePipeline().prefetchToBitmapCache(request, getApplicationContext());
                        //Fresco.getImagePipeline().prefetchToDiskCache(request, getApplicationContext());
                        boolean inMemoryCache = Fresco.getImagePipeline().isInBitmapMemoryCache(uri);
                        Log.d(TAG,""+i+datum.link+" IS:"+inMemoryCache);
                        i++;
                    }

                    imgurRecyclerView.setAdapter(new Imgur_RV_Adapter(data,getApplicationContext()));
                }catch (Exception e){
                    Log.d(TAG,"Exception::"+e.getMessage());
                }


            }

Still its not prefetching. For now I'm trying to load a limited set of images to find how it works. But the final goal is to load a infinite number of images from imgur, as long as the user scrolls down, the images should keep be displayed.

At what point should I prefetch the next n number of images ? & how to set it in the SimpleDraweeView

.setLowestPermittedRequestLevel(ImageRequest.RequestLevel.DISK_CACHE) is enough right

i want to know how to cache the images and text form a online database. any links i can check to for me to learn this ?????????????????????

Fresco handles the caching for you. Take a look at http://frescolib.org/docs/caching.html to see how this works and how this can be configured.
If you want to prefetch images, check out http://frescolib.org/docs/using-image-pipeline.html

@oprisnik I read those docs, I feel its bit complicated to implement. A example code will be very helpful on how to prefetch in recyclerview & cache the same

Well, everything depends on when you do the caching. RecyclerView doesn't have an API for prefetching items. There's a good article here how this could look like and the pros / cons. Whenever you want to do the prefetching, just call prefetchTo... if you don't pre-create views (or if they are not attached to a window) or just set the URI if you're using a Drawee view.

Why prefetching of items setInitialPrefetchItemCount() is not supported/available in StaggeredGridLayoutManager API. You people have any solution for this Post

Was this page helpful?
0 / 5 - 0 ratings