2

I have a ListView with around 5-6 rows, every row is a RecycleView displaying horizontal Fragments. Cards (like Netflix, HBO apps for instance)

Considerations:

  • Every RecycleView has around 30-50 items.
  • Every ViewHolder has a "big" picture as background (around 300x260dp depending on the device screen size, the width is around 90% of the device screen)
  • The images are loaded by using Glide (code below)

I created a cache (SparseArray) for the RecycleViews in the ListView Adapter to keep the state of every RecycleView, otherwise every time it gets out of the screen it restarted to position 0.

By doing this, just scrolling every RecycleView to the end, makes some of them get a freeze.

Tried several workarounds:

  • Reviewed MAT and have refactored some code for avoiding leak because of the static context
  • MAT obviously display a massive heap of memory because of the images
  • Reduced the size of the images
  • Hide the images
  • Create a cache of ViewHolders (didn't work)
  • Create a memory cache for the Images once Glide download it
  • Making the ViewHolders setIsRecyclable to false

But, none of this options has worked, the only way to guarantee the app is not getting a freeze, is avoiding the ListView cache (And losing the last position you visited, if you moves the View out of the screen)

Glide Code (I tried several options here: avoiding Bitmap, avoiding memory cache, updating the disk Strategy to ALL)

Glide.with(context).load(url)
                .asBitmap()
                .skipMemoryCache(true)
                .thumbnail(0.5f)
                .centerCrop()
                .error(placeHolderId)
                .diskCacheStrategy(DiskCacheStrategy.RESULT)
                //.priority(Priority.NORMAL)
                .dontAnimate()
                .into(new SimpleTarget<Bitmap>() {
                    @Override
                    public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
                        if (bitmap != null) {
                            imgView.setImageBitmap(bitmap);
                            //cache.put(url, bitmap);
                        } else {
                            imgView.setBackground(null);
                        }
                    }
                });

Any advice/suggestions? Maybe there is another option to keep the state of the View without the cache?

ListView Adapter Code:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
            case TYPE_VENUE_FILTER: {
            View v;
            // Create a new view into the list.
            LayoutInflater inflater = (LayoutInflater) context
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            if (inflater != null) {
                v = inflater.inflate(R.layout.fragment_venue_filter, parent, false);
            }

            // We need to get the exact filter to be displayed
            int index = position - FIRST_VENUE_FILTER_ROW;
            if (index < this.filters.size()) {
                VenueFilter filter = this.filters.get(index);

                // Recalculate the distance order
                filter.sortVenuesForLocation(this.lastLocation);

                // Set data into the view.
                VenueFilterFragment.loadFragment(this.context, v, filter, this.fm);
            }

            return v;
        }
}

UPDATE 11/04

I finally figure out.

  • Reused the converView on the Adapter as suggested
  • Included a ScrollListener in the RecycleView and storing in the bean the last scroll position, for recover it when is revisited.

This code is in the Adapter, for inflating the specific row (getView)

    // We should reset or recover the scroll state
    LinearLayoutManager llm = (LinearLayoutManager) filterContent.getLayoutManager();
    if (filter.offset > 0) {
        if (llm != null) llm.scrollToPosition(filter.offset + 1);
    } else {
        if (llm != null) llm.scrollToPosition(0);
    }

    // We need to clear every time. Because we are reusing the view
    recycleView.clearOnScrollListeners();

    // We need to create a new specific scroll listener
    recycleView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            // Keeping the offset for a future load
            LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
            if (llm != null) filter.offset = llm.findFirstVisibleItemPosition();
        }
    });
ColdFire
  • 6,764
  • 6
  • 35
  • 51
Iván Peralta
  • 851
  • 1
  • 9
  • 25
  • please put code of your Adapter – Cao Minh Vu Apr 10 '18 at 10:26
  • added the ListView adapter code @CaoMinhVu, thanks for looking into – Iván Peralta Apr 10 '18 at 12:16
  • There are two things that look very suspicious to me: 1) you are not using `convertView` at all, i.e. you never reuse the views from adapter and not taking advantage of the recycling logic which will give you a slowdown in performance. In this case you have to inflate views for each item which is a costly operation. 2) Referring to java naming convention, I assume `VenueFilterFragment` is a class name and thus `loadFragment` is a static method which is strange.. what's the purpose of this? – Gennadii Saprykin Apr 10 '18 at 12:28
  • I'm using Fragments for the different kinds of cells in the ListView, this fragments can be used in another parts of the app. the static method is just for set the content over the given view. (But is now performing as a fragment, because I lost all the fragment lifecycle). Related to the point 1, you are right, looking into it now. – Iván Peralta Apr 10 '18 at 13:24
  • This indeed sounds strange to me. If you want to set content for the given fragment, it should be a non-static method on a fragment object. Static method is setting "so-called" state on a fragment class which means it's some kind of shared data. I see that you are passing a filter object to this shared state on every `getView` call which honestly doesn't make much sense to me. I can't think of any scenario where that would be correct. Please rethink this logic too, you most likely don't want a static call there. – Gennadii Saprykin Apr 10 '18 at 14:15

1 Answers1

0

This is a very broad and complex question. Your layout is complex and thus it will take some effort to make it efficient. Things that you can do to improve the performance:

  • All RecyclerViews should use view holders where possible. Define multiple view types if needed. The parent RecyclerView will probably have only one view type which is an item with a child RecyclerView. Child RecyclerViews might also share view holders by having a RecycledViewPool. More info here: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.RecycledViewPool.html
  • Make sure all layouts are as flat as possible. The more view types you have the more important this becomes.
  • Glide is a good choice for image loading. Make sure you use caching where possible. Cache images once they are downloaded to reduce the impact on network. Use placeholders or progress bars when downloading an image. Reset each ImageView state when the view is reused to avoid flickering.
  • Cancel image loading for items that you no longer need to load. This should also help you to reduce the networking activity.
  • You can restore the scrolling position too, there shouldn't be any problem here. This is a separate topic though, you can refer this thread for more details: How to save RecyclerView's scroll position using RecyclerView.State?
  • And keep an eye on MAT, yes. It's important to make sure you don't use too many resources which is quite easy in this case.

I hope that helps. Good luck!

Gennadii Saprykin
  • 4,505
  • 8
  • 31
  • 41
  • Thanks! I just added my ListView adapter Code, maybe because the way I'm loading the Fragments that includes the RecycleView the approach suggested in your last link is not working (So, the onSaveState ... is not called) Will look into the rest, or maybe find another approach for manage the Fragment Lifecycle. Best! – Iván Peralta Apr 10 '18 at 12:19