异步操作下RecyclerView不显示数据并抛出ConcurrentModificationException
分析RecyclerView适配器异步更新时的ConcurrentModificationException及数据显示问题
首先看你遇到的两个核心问题:ConcurrentModificationException异常和异步更新后RecyclerView为空,我们逐个拆解原因和解决办法:
一、ConcurrentModificationException的直接原因
你在AsyncTaskRunner.doInBackground()里犯了一个典型的ArrayList迭代错误:
Iterator<ProfileSearchResult> iter = profileList.iterator(); while (iter.hasNext()) { ProfileSearchResult result = iter.next(); if(false == profileListNew.contains(result)){ profileList.remove(result); // 这里直接调用集合的remove方法! } }
ArrayList的迭代器是**快速失败(fail-fast)**的:当迭代器正在遍历集合时,如果集合本身被调用add/remove等结构性修改方法,迭代器会立刻抛出ConcurrentModificationException。正确的做法是使用迭代器自身的remove()方法来删除元素:
iter.remove(); // 替换掉profileList.remove(result)
二、RecyclerView为空的深层原因
即使修复了异常,你的异步更新逻辑还有几个逻辑漏洞,会导致数据没有正确更新到profileList中:
- equals方法未重写:
profileListNew.contains(result)依赖ProfileSearchResult的equals()方法判断对象是否相同。如果你的ProfileSearchResult类没有重写equals()和hashCode(),默认会比较对象引用,导致原本应该匹配的对象被判定为不相等,最终profileList的增删逻辑完全失效,列表始终为空。 - AsyncTask参数与成员变量线程安全问题:你定义的
AsyncTask<List<ProfileSearchResult>, Void, Void>没有传入参数(taskRunner.execute()是空参),但在doInBackground里直接操作成员变量profileList和profileListNew——如果主线程在异步任务执行过程中修改了这些成员变量,会导致数据不一致。 - 列表更新逻辑低效且易出错:手动遍历两个列表对比增删的方式很容易遗漏边界情况,比如修改了某个元素的属性但对象引用没变时,你的逻辑无法识别这种更新。
三、推荐的修复方案
方案1:修复现有AsyncTask逻辑(快速临时修复)
- 用迭代器的
remove()替代集合的remove() - 给
ProfileSearchResult重写equals()和hashCode()(比如根据用户ID等唯一标识来实现) - 确保
profileListNew是每次更新的完整数据集,或者在异步任务开始前创建副本,避免主线程修改:
public void notifyChanges(List<ProfileSearchResult> changes){ // 创建副本,避免主线程后续修改影响异步任务 profileListNew = new ArrayList<>(changes); new AsyncTaskRunner().execute(); } // 修正doInBackground的迭代逻辑 @Override protected Void doInBackground(Void... voids) { // 先移除原列表中不在新列表的项 Iterator<ProfileSearchResult> iter = profileList.iterator(); while (iter.hasNext()) { ProfileSearchResult result = iter.next(); if(!profileListNew.contains(result)){ iter.remove(); // 使用迭代器的remove方法 } } // 添加新列表中不在原列表的项 for(ProfileSearchResult result : profileListNew){ if(!profileList.contains(result)){ profileList.add(result); } } return null; }
方案2:使用DiffUtil(官方推荐,更高效可靠)
手动处理列表对比容易出错,Android官方推荐用DiffUtil来计算新旧列表的差异,它可以在后台线程计算,然后在主线程更新RecyclerView,既避免线程安全问题,又能实现局部刷新(比notifyDataSetChanged()更高效):
- 创建DiffUtil.Callback类:
public class ProfileDiffCallback extends DiffUtil.Callback { private List<ProfileSearchResult> oldList; private List<ProfileSearchResult> newList; public ProfileDiffCallback(List<ProfileSearchResult> oldList, List<ProfileSearchResult> newList) { this.oldList = oldList; this.newList = newList; } @Override public int getOldListSize() { return oldList.size(); } @Override public int getNewListSize() { return newList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { // 根据唯一标识判断是否是同一个item(比如用户ID) return oldList.get(oldItemPosition).getId().equals(newList.get(newItemPosition).getId()); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { // 判断item内容是否相同,这里要比较所有需要显示的字段 ProfileSearchResult oldItem = oldList.get(oldItemPosition); ProfileSearchResult newItem = newList.get(newItemPosition); return oldItem.getName().equals(newItem.getName()) && oldItem.getTagline().equals(newItem.getTagline()) && oldItem.getProfilePictureUrl().equals(newItem.getProfilePictureUrl()); } }
- 修改适配器的notifyChanges方法:
public void notifyChanges(List<ProfileSearchResult> newList){ // 在后台线程计算差异 new AsyncTask<Void, Void, DiffUtil.DiffResult>() { @Override protected DiffUtil.DiffResult doInBackground(Void... voids) { return DiffUtil.calculateDiff(new ProfileDiffCallback(profileList, newList)); } @Override protected void onPostExecute(DiffUtil.DiffResult diffResult) { // 更新原列表,然后通知适配器刷新 profileList.clear(); profileList.addAll(newList); diffResult.dispatchUpdatesTo(ProfileSearchRecyclerAdapter.this); } }.execute(); }
这样既避免了ConcurrentModificationException,又能正确更新数据,还能实现局部刷新提升性能。
额外注意点
- 永远不要在非主线程调用RecyclerView的notify系列方法,
notifyDataSetChanged()必须在主线程执行(你的onPostExecute是主线程,这部分是对的)。 - 尽量避免使用AsyncTask,Android现在推荐用
Coroutines或者Executor来处理后台任务,AsyncTask已经被标记为废弃。
内容的提问来源于stack exchange,提问作者Sachin Titus
相关产品推荐
相关产品推荐

