Android RecyclerView删除后notify更新列表偶发失效问题求助
问题:RecyclerView删除功能时好时坏,notify更新列表失效
删除功能时好时坏,耗时3天排查bug仍未定位根因,初步怀疑问题出在notifyItemRemoved方法调用或是数组元素移除逻辑上,尝试过绝大多数用于更新RecyclerView的notify系列方法,均无法让列表正常更新。
对应适配器类代码
public class ChecklistItems extends AppCompatActivity { Toolbar toolbar; RecyclerView recyclerView; String content; String status; String[] contentSplit; String[] statusSplit; private ChecklistAdapter checklistAdapter; DbHelper dbHelper; List<ChecklistHelper> checks; int id; ChecklistHelper checklistHelper; StringBuilder stringBuilderContent; StringBuilder stringBuilderStatus; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_checklist_items); toolbar = findViewById(R.id.tool_bar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); recyclerView = findViewById(R.id.recycler_view_child); id = getIntent().getIntExtra("id", 0); content = getIntent().getStringExtra("content"); status = getIntent().getStringExtra("status"); contentSplit = content.split("\n"); statusSplit = status.split("\n"); checklistHelper = new ChecklistHelper(); dbHelper = new DbHelper(this); checks = dbHelper.getChecklists(); recyclerView.setLayoutManager(new LinearLayoutManager(this)); checklistHelper = dbHelper.getChecklists(id); String status = checklistHelper.getStatus(); String[] statusSplit = status.split("\n"); ArrayList<String> newStatusList = new ArrayList<>(Arrays.asList(statusSplit)); ArrayList<String> newContentList = new ArrayList<>(Arrays.asList(statusSplit)); checklistAdapter = new ChecklistAdapter(newStatusList, newContentList); recyclerView.setAdapter(checklistAdapter); } public class ChecklistAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { ArrayList<String> newStatusList; ArrayList<String> newContentList; public ChecklistAdapter( ArrayList<String> newStatusList, ArrayList<String> newContentList) { this.newStatusList = newStatusList; this.newContentList = newContentList; } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); View view = layoutInflater.inflate(R.layout.checklist_layout_child, parent, false); return new ChecklistChildViewHolder(view); } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, @SuppressLint("RecyclerView") int position) { ChecklistChildViewHolder checklistChildViewHolder = (ChecklistChildViewHolder) holder; checklistChildViewHolder.checkBox.setText(contentSplit[position]); boolean state; if (statusSplit[position].equals("1")) { state = true; checklistChildViewHolder.checkBox.setText(lineThrough(checklistChildViewHolder.checkBox.getText().toString())); } else { state = false; checklistChildViewHolder.checkBox.setText(checklistChildViewHolder.checkBox.getText().toString()); } checklistChildViewHolder.checkBox.setChecked(state); checklistChildViewHolder.delete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { doDelete(position); } }); } @Override public int getItemCount() { return newStatusList.size(); } public class ChecklistChildViewHolder extends RecyclerView.ViewHolder { CheckBox checkBox; ImageView delete, edit; ChecklistChildViewHolder(@NonNull View itemView) { super(itemView); checkBox = itemView.findViewById(R.id.checkbox_child); delete = itemView.findViewById(R.id.delete_child); edit = itemView.findViewById(R.id.edit_child); delete.setVisibility(View.INVISIBLE); edit.setVisibility(View.INVISIBLE); } public void updateVisibility(boolean isEnableDelete){ if (isEnableDelete) { delete.setVisibility(View.VISIBLE); edit.setVisibility(View.VISIBLE); } else { delete.setVisibility(View.INVISIBLE); edit.setVisibility(View.INVISIBLE); } } } public int doDelete(int position) { int idd = getIntent().getIntExtra("id", 0); checklistHelper = dbHelper.getChecklists(idd); String status = checklistHelper.getStatus(); String content = checklistHelper.getContent(); String[] statusSplit = status.split("\n"); String[] contentSplit = content.split("\n"); newStatusList = new ArrayList<>(Arrays.asList(statusSplit)); newContentList = new ArrayList<>(Arrays.asList(contentSplit)); newStatusList.remove(position); newContentList.remove(position); stringBuilderStatus = new StringBuilder(); stringBuilderContent = new StringBuilder(); for(String singleStatus : newStatusList) { stringBuilderStatus.append(singleStatus); stringBuilderStatus.append("\n"); } for(String singleContent: newContentList){ stringBuilderContent.append(singleContent); stringBuilderContent.append("\n"); } String allStatus = stringBuilderStatus.toString(); String allContent = stringBuilderContent.toString(); dbHelper.updateChecklist(id,allStatus , allContent, DateTime.date(), DateTime.time(), System.currentTimeMillis()); if(allContent.length()==0) { dbHelper.deleteChecklist(id); finish(); } notifyItemRemoved(position); notifyItemChanged(position); return newStatusList.size(); } } }
onCreate生命周期方法实现代码
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_checklist_items); toolbar = findViewById(R.id.tool_bar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); recyclerView = findViewById(R.id.recycler_view_child); id = getIntent().getIntExtra("id", 0); content = getIntent().getStringExtra("content"); status = getIntent().getStringExtra("status"); contentSplit = content.split("\n"); statusSplit = status.split("\n"); checklistHelper = new ChecklistHelper(); dbHelper = new DbHelper(this); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(checklistAdapter); checklistHelper = dbHelper.getChecklists(id); String status = checklistHelper.getStatus(); String[] statusSplit = status.split("\n"); ArrayList<String> newStatusList = new ArrayList<>(Arrays.asList(statusSplit)); ArrayList<String> newContentList = new ArrayList<>(Arrays.asList(statusSplit)); checklistAdapter = new ChecklistAdapter(newStatusList, newContentList); recyclerView.setAdapter(checklistAdapter); }
问题根因&修复方案
代码里存在4个直接导致删除功能异常的问题:
- 数据源引用错乱
onBindViewHolder里加载列表内容用的是Activity级别的全局数组contentSplit、statusSplit,这两个数组只在onCreate初始化时从Intent取了一次值,删除操作后从来没有更新过。删除后列表重绑item时还是读的旧数组数据,自然显示不对。另外初始化newContentList的时候错误传了statusSplit,内容列表和状态列表用了同一份数据,从初始化阶段内容显示就可能出错。 - 删除逻辑重复创建新列表,替换了适配器持有的原列表引用
doDelete方法里每次都重新从数据库读数据、new新的ArrayList赋值给newStatusList和newContentList,相当于把适配器原来持有的列表引用直接替换了,但notify系列方法是基于适配器持有的原数据集计算位置差异的,直接换引用会导致适配器内部的位置缓存和实际数据集不匹配,出现位置错乱、删错项、列表不刷新的问题。 - 点击事件持有过期position
onBindViewHolder里给删除按钮设点击监听的时候直接用了方法参数里的position,当列表有item被删除后,没有重新绑定的holder持有的还是旧的position值,触发点击时会操作错误位置。 - notify调用逻辑错误、onCreate存在重复初始化
调用notifyItemRemoved(position)之后不需要再调用notifyItemChanged(position),正确做法是删除后调用notifyItemRangeChanged(position, newStatusList.size() - position)刷新被删位置之后所有item的位置,避免位置缓存错乱。另外onCreate里先给recyclerView设了一次未初始化的空adapter,之后又重新new adapter再set一次,属于无意义的重复操作,也可能引发状态异常。
核心修复代码参考
首先修正onCreate里的初始化逻辑,去掉重复setAdapter的代码,初始化newContentList时传入contentSplit而非statusSplit;再修改绑定和删除逻辑:
// 修正后的onBindViewHolder内容加载与点击绑定逻辑 @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { ChecklistChildViewHolder checklistChildViewHolder = (ChecklistChildViewHolder) holder; // 直接读取适配器持有的列表数据,不要用Activity全局的旧数组 checklistChildViewHolder.checkBox.setText(newContentList.get(position)); boolean state = "1".equals(newStatusList.get(position)); if (state) { checklistChildViewHolder.checkBox.setText(lineThrough(checklistChildViewHolder.checkBox.getText().toString())); } else { checklistChildViewHolder.checkBox.setText(checklistChildViewHolder.checkBox.getText().toString()); } checklistChildViewHolder.checkBox.setChecked(state); checklistChildViewHolder.delete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 获取实时位置,不要用绑定时传入的固定position int currentPos = checklistChildViewHolder.getAdapterPosition(); if(currentPos != RecyclerView.NO_POSITION){ doDelete(currentPos); } } }); } // 修正后的doDelete核心逻辑 public int doDelete(int position) { // 直接操作适配器当前持有的列表,不要重新new列表替换引用 newStatusList.remove(position); newContentList.remove(position); // 拼接更新数据库的逻辑保持不变 stringBuilderStatus = new StringBuilder(); stringBuilderContent = new StringBuilder(); for(String singleStatus : newStatusList) { stringBuilderStatus.append(singleStatus).append("\n"); } for(String singleContent: newContentList){ stringBuilderContent.append(singleContent).append("\n"); } String allStatus = stringBuilderStatus.toString(); String allContent = stringBuilderContent.toString(); dbHelper.updateChecklist(id, allStatus, allContent, DateTime.date(), DateTime.time(), System.currentTimeMillis()); if(allContent.length()==0) { dbHelper.deleteChecklist(id); finish(); return 0; } // 正确调用notify方法 notifyItemRemoved(position); // 刷新被删位置之后的所有item,修正位置缓存 notifyItemRangeChanged(position, newStatusList.size() - position); return newStatusList.size(); }
内容的提问来源于stack exchange,提问作者nano tech
相关产品推荐
相关产品推荐

