刷新时滑动RecyclerView触发IndexOutOfBoundsException崩溃如何解决?
问题分析与修复方案
问题诱因
这个Inconsistency detected报错是RecyclerView的典型错误,本质是Adapter持有的数据源、Adapter内部记录的条目位置,和你触发的更新通知三者不匹配,仅在滑动时崩溃是因为滑动过程中RecyclerView会频繁计算布局、预加载条目,会触发一致性校验,静止时不会执行校验所以不会报错。具体到你的代码有以下几个直接诱因:
- 通知更新的位置完全错误:你传入的
id是分页的页码,不是RecyclerView的条目位置。你按每页10条存储,第id个分页对应RecyclerView里的第id*10到id*10+9个条目,直接调用notifyItemChanged(id)相当于通知完全错误的位置更新,自然会出现位置不匹配的问题。 - 数据更新和通知时机不同步:你调用
_articles.postValue()是异步将数据同步到主线程,但是紧接着就同步调用了Adapter的notify方法,此时Adapter可能还没拿到最新的数据源,用旧数据匹配新的更新通知就会出现不一致。 - 直接修改LiveData持有的列表引用:你直接修改
_articles.value对应的ArrayList实例,没有生成新的列表,滑动过程中如果Adapter正在遍历数据源,同时你在后台修改列表内容,就会出现并发修改导致的索引异常。 - ViewModel持有Adapter引用违反分层原则:ViewModel生命周期长于View层,持有Adapter会导致内存泄漏,也会破坏数据更新和UI通知的时序一致性。
- 下拉刷新清空旧数据时没有同步通知Adapter:如果你触发重载时清空了旧的分页数据,但是没有调用对应的删除通知,Adapter还以为条目总数是旧的数值,加载新数据时自然会出现索引越界。
修复方案
- 调整通知逻辑,匹配正确的条目位置
如果你是将所有分页的文章平铺展示,更新第id个分页后,应该通知对应范围的条目更新:
// 仅在View层拿到新数据后调用,不要写在ViewModel里 recyclerAdapter.notifyItemRangeChanged(id * 10, 10)
如果你是分页作为分组展示,也要计算分页对应的实际条目位置再触发通知。
2. 移除ViewModel中的Adapter引用,遵循MVVM分层逻辑
ViewModel只负责更新LiveData数据,不要持有任何View层的引用:
// 修改后的ViewModel方法 fun setArticlePage(id: Int, content: ThomsLineWordpressArticlePage) { if (_articles.value == null || id >= _articles.value!!.size || (id < _articles.value!!.size && !_articles.value!![id].equals(content))) { // 生成新的列表实例,避免并发修改 val newList = _articles.value?.toMutableList() ?: mutableListOf() if (newList.size <= id) { newList.add(content) } else { newList[id] = content } // 主线程更新用setValue,后台线程更新用postValue _articles.postValue(newList) } }
然后在View层(Activity/Fragment)观察LiveData变化,更新Adapter数据源后再触发通知:
viewModel.articles.observe(this) { newPageList -> adapter.updateData(newPageList) // 先更新Adapter持有的数据源 // 再触发对应范围的更新通知,如果你做了差量计算可以只更新变化的分页 adapter.notifyDataSetChanged() // 前期可以先用全量通知保证稳定,后续再优化为局部更新 }
- 重载时同步清空数据源和通知
触发下拉刷新时,先清空LiveData的旧数据,同时通知Adapter条目删除:
// ViewModel里添加重置方法 fun resetArticles() { _articles.postValue(mutableListOf()) } // View层监听重载事件时调用 swipeRefreshLayout.setOnRefreshListener { viewModel.resetArticles() // 再触发分页重新加载 viewModel.loadFirstPage() }
- 兜底方案
如果临时要快速定位问题,可以先把局部通知替换为notifyDataSetChanged(),确认崩溃消失后再逐步优化为局部更新,缩小问题范围。
内容的提问来源于stack exchange,提问作者isi_ko
相关产品推荐
相关产品推荐

