Android RecyclerView:如果不使用“setIsRecyclabe(false)”,则滚动期间内存使用量会增加

Posted

技术标签:

【中文标题】Android RecyclerView:如果不使用“setIsRecyclabe(false)”,则滚动期间内存使用量会增加【英文标题】:Android RecyclerView : Memory usage increases during scrolling if 'setIsRecyclabe(false)' is not used 【发布时间】:2020-06-16 06:52:56 【问题描述】:

在我的 android 应用程序 (Java) 中,我在 recyclerview 中显示了大约 1800 个联系人的列表。在进行内存配置文件时,发现滚动回收器视图时内存使用量迅速增加。所以我在这里找到了this 问题,它提到了同样的问题,并尝试了在 onBindViewHolder 中设置 setIsRecyclable(false) 的解决方案,它起作用了。分析结果如下。

案例 1:未使用 setIsRecyclable(False)

初始内存使用量:~ 40M [Java=5.9M Native=5M Graphics=20.3M Stack=0.3M Code=5.3M Others=0.8M]

内存使用峰值:~ 345M [ Java=187.5M 本机=39.1M 图形=101.5M 堆栈=0.4M 代码=11.6M 其他=6.5M ]

还发现峰值内存使用量随着列表中项目数量的增加而增加。连续滚动停止一段时间后,内存使用量确实下降了,但只有 162 MB 左右。

案例 2:在 onBindViewHolder 添加 setIsRecyclable(False) 后

初始内存使用量:~ 42M [Java=5.8M Native=5.5M Graphics=20.2M Stack=0.3M Code=9.4M Others=0.8M]

内存使用峰值:~ 100M [ Java=43.9M 本机=9.7M 图形=32.6M 堆栈=0.4M 代码=11.7M 其他=2.2M ]

此外,在这种情况下,内存使用不会因列表中项目数量的增加而受到显着影响。虽然峰值内存使用量约为 100MB,但在大多数情况下平均保持在 70MB 左右,这甚至更好。

包含recyclerView的片段源码

注意: * Adapter 类定义为 Fragment 类的内部类,ViewHolder 类定义为 Adapter 类的内部类。 * 'App.personList' 是一个包含联系人列表的静态数组列表,App 是 ViewModel 类。 * adapter1 是唯一感兴趣的适配器。请避免使用adapter2(处理另一个小列表)

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);
        
    




 

问题

所以 'setIsRecyclable(False)' 应该防止视图的回收,这应该会导致更多的内存使用。但相反,它表现出相反的行为。此外,我认为如果必须在不使用 setIsRecyclable(false) 的情况下处理更大的列表,该应用程序肯定会崩溃。为什么会这样?

【问题讨论】:

【参考方案1】:

getItemId(int)getItemViewType(int) 永远不应该返回 position 本身,你违反了 recyclerview 合同,因为它强迫它为每个位置创建新的视图而不是重复使用现有视图。

这是您的问题的原因 - 每个职位都有唯一的itemViewType,因此他们开始非常迅速地填补recycledViewPool,因为他们只是被插入而从未被移除。 setIsRecyclable(False) 通过不将它们放入 recyclerViewPool 来规避问题,但这并不能解决视图回收不足的问题。

只需删除 getItemIdgetItemViewType 覆盖,因为您没有正确使用它们。

【讨论】:

谢谢@Pawel ..刚刚删除了这2个方法,它工作了..

以上是关于Android RecyclerView:如果不使用“setIsRecyclabe(false)”,则滚动期间内存使用量会增加的主要内容,如果未能解决你的问题,请参考以下文章

Android RecyclerView:如果不使用“setIsRecyclabe(false)”,则滚动期间内存使用量会增加

Android:RecyclerView 不显示片段中的列表项

Android必知必会 - RecyclerView 恢复上次滚动位置

深入理解Android RecyclerView的缓存机制

Android TV RecyclerView重点互动

Android:RecyclerView 显示图像 2 次