0

In my android application(Java) I am displaying a list of around 1800 contacts in a recyclerview. While doing a memory profile it was found that when scrolling the recycler view the memory usage was increasing rapidly. So I found this question here which was mentioning the same problem and tried out the solution which was to setIsRecyclable(false) in onBindViewHolder and it worked. The profiling results are given below.


Case 1 : setIsRecyclable(False) not used

Initial memory usage : ~ 40M [ Java=5.9M Native=5M Graphics=20.3M Stack=0.3M Code=5.3M Others =0.8M ]

Peak memory usage : ~ 345M [ Java=187.5M Native=39.1M Graphics=101.5M Stack=0.4M Code=11.6M Others =6.5M ]

enter image description here

Also the peak memory usage was found to increase with increase in number of items in the list. After the continuous scrolling is stopped for a while the memory usage does come down but only to around 162 MB.


Case 2 : after adding setIsRecyclable(False) to onBindViewHolder

Initial memory usage : ~ 42M [ Java=5.8M Native=5.5M Graphics=20.2M Stack=0.3M Code=9.4M Others =0.8M ]

Peak memory usage : ~ 100M [ Java=43.9M Native=9.7M Graphics=32.6M Stack=0.4M Code=11.7M Others =2.2M ]

enter image description here

Also, in this case, memory usage was not affected significantly by increasing number of items in list. Although peak memory usage is about 100MB the average stays at around 70 MB for most of the time which is even better.


Source Code of fragment containing recyclerView

Note :
* Adapter class is defined as an inner class of Fragment class and ViewHolder class is defined as an inner class of Adapter class.
* 'App.personList' is a static arrayList holding the list of contacts and App is the ViewModel class.
* adapter1 is the only adapter of interest. Please avoid adapter2(handles another small list)

public class FragmentAllContacts extends Fragment 
{

public static MainActivity main;
public RecyclerView contactsView, tagsView;
LinearLayoutManager llm, lln;
Button filterCloseButton;
CardView filterView;
Adapter_ContactListView adapter1;
Adapter_TagListView adapter2;

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
    setHasOptionsMenu(true);
    main = (MainActivity) getActivity();
    return inflater.inflate(R.layout.fragment_all_contacts, container, false);
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState)
{
    super.onViewCreated(view, savedInstanceState);
    adapter1 = new Adapter_ContactListView(App.personList,getContext());
    adapter2 = new Adapter_TagListView(App.tagList,getContext());
    filterView = getView().findViewById(R.id.cardView7);
    FloatingActionButton fab = getView().findViewById(R.id.create_contact_fab);


    contactsView = getView().findViewById(R.id.allContacts_recyclerView);
    contactsView.setAdapter(adapter1);
    llm = new LinearLayoutManager(main.getBaseContext());
    contactsView.setLayoutManager(llm);
    contactsView.scrollToPosition(App.AllConnections.scrollPosition);

    tagsView = getView().findViewById(R.id.allTags_recyclerView);
    tagsView.setAdapter(adapter2);
    lln = new LinearLayoutManager(main.getBaseContext(), LinearLayoutManager.HORIZONTAL, false);
    tagsView.setLayoutManager(lln);

}







class Adapter_ContactListView extends RecyclerView.Adapter<Adapter_ContactListView.ViewHolder> implements Filterable {

    List<Person_PersistentData> contactsFiltered;
    Context context;


    public Adapter_ContactListView(List<Person_PersistentData> list, Context context)
    {
        this.contactsFiltered = list;
        this.context = context;
    }

    @Override
    public Adapter_ContactListView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_view_list, parent, false);
        Adapter_ContactListView.ViewHolder pane = new Adapter_ContactListView.ViewHolder(v);
        return pane;
    }

    @Override
    public void onBindViewHolder(Adapter_ContactListView.ViewHolder pane, int position) 
    {
        pane.setIsRecyclable(false);
        final Person_PersistentData rec = contactsFiltered.get(position);
        pane.nameView.setText(rec.personName + " (" + rec.personID + ")");
        Uri imageUri = App.FSManager.getProfilePic(rec.personID);
        if (imageUri != null) {pane.imageView.setImageURI(imageUri);} 
        else {pane.imageView.setImageResource(R.drawable.ico_60px);}
        if (App.AllConnections.personSelectionStack.contains(rec.personID)) {pane.cv.setBackgroundColor(context.getResources().getColor(R.color.rgb_000_070_100));}
        else
        {pane.cv.setBackgroundColor(context.getResources().getColor(R.color.rgb_020_020_020));}

        pane.cv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view)
            {
                if(App.AllConnections.selectionMode)
                {
                    App.Person_SelectionInput(rec.personID);
                    Adapter_ContactListView.this.notifyDataSetChanged();
                }
                else
                {
                    App.PersonInfo.id = rec.personID;
                    main.startTask(T.personInfo);
                }
            }
        });


        pane.cv.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view)
            {
                App.Person_SelectionInput(rec.personID);
                Adapter_ContactListView.this.notifyDataSetChanged();
                return false;
            }
        });
        //animate(holder);

    }

    @Override
    public int getItemCount() {
        //returns the number of elements the RecyclerView will display
        return contactsFiltered.size();
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);

    }

    @Override
    public Filter getFilter() {  ...  }

    @Override
    public long getItemId(int position)
    {
        return position;
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    class ViewHolder extends RecyclerView.ViewHolder {

        CardView cv;
        TextView nameView;
        ImageView imageView;

        public ViewHolder(@NonNull View itemView)
        {
            super(itemView);
            cv = itemView.findViewById(R.id.cardView);
            nameView = itemView.findViewById(R.id.name);
            imageView = itemView.findViewById(R.id.imageViewZ);
        }
    }
}



} 


Question

So 'setIsRecyclable(False)' is supposed to prevent recycling of views and this should be causing more memory usage. But instead it is showing the opposite behavior. Also i think the app will surely crash if it has to handle an even larger list without using setIsRecyclable(false). Why is this happening ?

JPK
  • 95
  • 1
  • 8

1 Answers1

1

getItemId(int) and getItemViewType(int) should NEVER return position itself, you're violating recyclerview contract by forcing it to create new viewholders for every single position intead of re-using existing views.

This is the cause of your issue - every position has unique itemViewType so they start to fill up recycledViewPool very rapidly since they're only being inserted and never being taken out of it. setIsRecyclable(False) circumvents the issue by not putting them in recyclerViewPool but that doesn't fix the problem of lack of view recycling.

Just delete getItemId and getItemViewType overrides because you're not using them properly.

Pawel
  • 15,548
  • 3
  • 36
  • 36