如何修复Jetpack Navigation+DataBinding场景下RecyclerView的IndexOutOfBoundsException
问题根因
- Jetpack Navigation 生命周期特性:跳转至B Fragment时,A Fragment仅销毁视图层,实例和绑定的ViewModel不会被销毁。从B返回A时,A会重新创建视图、初始化新的
ItemsAdapter,但ViewModel会保留之前缓存的列表数据。 - 重复数据更新触发:返回A时,LiveData的粘性特性会先将缓存的旧列表数据推送给新的视图观察者,同时
onViewCreated中重复调用getItems()会触发新的列表请求,短时间内两次更新列表容易触发状态冲突。 - Adapter更新逻辑错误:当前
update方法先清空原有列表、再添加新数据,仅调用notifyItemRangeInserted通知插入,没有告知RecyclerView旧数据已被移除,导致RecyclerView内部维护的item数量和Adapter实际持有数据量不一致,直接触发IndexOutOfBoundsException异常。 - BindingAdapter边界处理缺失:当前逻辑仅在列表非空时更新Adapter,若新拉取的列表为空,Adapter会一直持有旧数据,也可能导致状态不一致。
修复方案
1. 修复Adapter更新逻辑
优先使用DiffUtil做差量更新,既能保证状态一致,也能保留列表更新动画:
fun update(newItems: List<Item>) { val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize() = items.size override fun getNewListSize() = newItems.size // 替换为你的Item唯一标识字段判断 override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = items[oldItemPosition].id == newItems[newItemPosition].id override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = items[oldItemPosition] == newItems[newItemPosition] }) items.clear() items.addAll(newItems) diffResult.dispatchUpdatesTo(this) }
如果列表数据量小不需要动画,也可以简化为全量通知:
fun update(newItems: List<Item>) { items.clear() items.addAll(newItems) notifyDataSetChanged() }
2. 修复BindingAdapter边界处理
空列表也要同步到Adapter,避免状态残留:
@BindingAdapter("bind_items") fun setItems(view: RecyclerView, list: List<Item>?) { (view.adapter as? ItemsAdapter)?.update(list ?: emptyList()) }
3. 优化数据请求逻辑
避免每次回到A Fragment都重复请求数据,将请求逻辑移到ViewModel初始化时执行,删除onViewCreated中的viewModel.getItems()调用:
@HiltViewModel class AViewModel @Inject constructor( private val itemRepo: ItemRepo, private val disposable: CompositeDisposable ) : ViewModel() { private var _items: MutableLiveData<List<Item>> = MutableLiveData() val items: LiveData<List<Item>> get() = _items init { getItems() } override fun onCleared() { super.onCleared() disposable.clear() } private fun getItems() { itemRepo.getAll() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ _items.postValue(it) }, { t -> // 补充异常处理逻辑 }) .addTo(disposable) } }
内容的提问来源于stack exchange,提问作者dev.farmer
相关产品推荐
相关产品推荐

