Android RecyclerView:如果不使用 'setIsRecyclabe(false)',则滚动期间内存使用量会增加
Android RecyclerView : Memory usage increases during scrolling if 'setIsRecyclabe(false)' is not used
在我的 android 应用程序 (Java) 中,我在 recyclerview 中显示了大约 1800 个联系人的列表。在做内存配置文件时发现,当滚动回收器视图时,内存使用量正在迅速增加。所以我在这里找到了 问题,它提到了同样的问题,并尝试了在 onBindViewHolder 中设置 IsRecyclable(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 Native=39.1M Graphics=101.5M Stack=0.4M Code=11.6M Others =6.5M ]
还发现峰值内存使用量随着列表中项目数量的增加而增加。连续滚动停止一段时间后,内存使用率确实下降了,但仅下降到 162 MB 左右。
情况 2:将 setIsRecyclable(False) 添加到 onBindViewHolder 之后
初始内存使用量:~ 42M
[ Java=5.8M Native=5.5M Graphics=20.2M Stack=0.3M Code=9.4M Others =0.8M ]
内存使用峰值:~ 100M
[ Java=43.9M Native=9.7M Graphics=32.6M Stack=0.4M Code=11.7M Others =2.2M ]
此外,在这种情况下,内存使用量不会因列表中项目数量的增加而受到显着影响。虽然峰值内存使用量约为 100MB,但大部分时间平均保持在 70MB 左右,这甚至更好。
包含 recyclerView 的片段的源代码
注意:
* Adapter class 被定义为 Fragment class 的内部 class 并且 ViewHolder class 被定义为 Adapter [=] 的内部 class 69=]。
* 'App.personList' 是一个包含联系人列表的静态数组列表,App 是 ViewModel class。
* 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) 的情况下处理更大的列表,它肯定会崩溃。为什么会这样?
getItemId(int)
和 getItemViewType(int)
永远不应该 return position
本身,你违反了 recyclerview 合同,强迫它为每个位置而不是重新创建新的查看器- 使用现有视图。
这就是您遇到问题的原因 - 每个位置都有唯一性 itemViewType
,因此它们开始很快填满 recycledViewPool
,因为它们只是被插入而从未被移除。 setIsRecyclable(False)
通过不将它们放入 recyclerViewPool
来规避问题,但这并不能解决缺少视图回收的问题。
只需删除 getItemId
和 getItemViewType
覆盖,因为您没有正确使用它们。
在我的 android 应用程序 (Java) 中,我在 recyclerview 中显示了大约 1800 个联系人的列表。在做内存配置文件时发现,当滚动回收器视图时,内存使用量正在迅速增加。所以我在这里找到了
案例 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 Native=39.1M Graphics=101.5M Stack=0.4M Code=11.6M Others =6.5M ]
还发现峰值内存使用量随着列表中项目数量的增加而增加。连续滚动停止一段时间后,内存使用率确实下降了,但仅下降到 162 MB 左右。
情况 2:将 setIsRecyclable(False) 添加到 onBindViewHolder 之后
初始内存使用量:~ 42M [ Java=5.8M Native=5.5M Graphics=20.2M Stack=0.3M Code=9.4M Others =0.8M ]
内存使用峰值:~ 100M [ Java=43.9M Native=9.7M Graphics=32.6M Stack=0.4M Code=11.7M Others =2.2M ]
此外,在这种情况下,内存使用量不会因列表中项目数量的增加而受到显着影响。虽然峰值内存使用量约为 100MB,但大部分时间平均保持在 70MB 左右,这甚至更好。
包含 recyclerView 的片段的源代码
注意:
* Adapter class 被定义为 Fragment class 的内部 class 并且 ViewHolder class 被定义为 Adapter [=] 的内部 class 69=]。
* 'App.personList' 是一个包含联系人列表的静态数组列表,App 是 ViewModel class。
* 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) 的情况下处理更大的列表,它肯定会崩溃。为什么会这样?
getItemId(int)
和 getItemViewType(int)
永远不应该 return position
本身,你违反了 recyclerview 合同,强迫它为每个位置而不是重新创建新的查看器- 使用现有视图。
这就是您遇到问题的原因 - 每个位置都有唯一性 itemViewType
,因此它们开始很快填满 recycledViewPool
,因为它们只是被插入而从未被移除。 setIsRecyclable(False)
通过不将它们放入 recyclerViewPool
来规避问题,但这并不能解决缺少视图回收的问题。
只需删除 getItemId
和 getItemViewType
覆盖,因为您没有正确使用它们。