You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复Jetpack Navigation+DataBinding场景下RecyclerView的IndexOutOfBoundsException

问题根因

  1. Jetpack Navigation 生命周期特性:跳转至B Fragment时,A Fragment仅销毁视图层,实例和绑定的ViewModel不会被销毁。从B返回A时,A会重新创建视图、初始化新的ItemsAdapter,但ViewModel会保留之前缓存的列表数据。
  2. 重复数据更新触发:返回A时,LiveData的粘性特性会先将缓存的旧列表数据推送给新的视图观察者,同时onViewCreated中重复调用getItems()会触发新的列表请求,短时间内两次更新列表容易触发状态冲突。
  3. Adapter更新逻辑错误:当前update方法先清空原有列表、再添加新数据,仅调用notifyItemRangeInserted通知插入,没有告知RecyclerView旧数据已被移除,导致RecyclerView内部维护的item数量和Adapter实际持有数据量不一致,直接触发IndexOutOfBoundsException异常。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 07:27:02