Android DiffUtil自定义视图场景下不一致异常排查求助
从你的代码和异常描述来看,问题的核心在于**getItemCount()方法中同时依赖了父类的计数和DiffCalculator的列表大小,导致RecyclerView在Diff更新过程中获取到不一致的itemCount值**,最终触发越界异常(position等于itemCount,而有效position范围应该是0到itemCount-1)。
问题分析
你的getItemCount()实现是取父类计数(包含加载视图、空列表视图等额外项)和DiffCalculator当前列表大小的最大值:
@Override public int getItemCount() { int superCount = super.getItemCount(); log("getItemCount() called with count=" + mDiffCalculator.getCurrentList().size()); return superCount > mDiffCalculator.getCurrentList().size() ? superCount : mDiffCalculator.getCurrentList().size(); }
这里的隐患在于:
- 当DiffUtil在后台计算差异时,
mDiffCalculator.getCurrentList()还是旧的数据源,但父类的superCount可能已经因为加载状态/空视图的变化而改变,导致返回的itemCount和实际的item数据不匹配。 - RecyclerView会依赖
getItemCount()来确定可滚动范围和item位置,一旦这个值在Diff更新过程中前后不一致,就会出现position >= getItemCount()的越界情况。
另外,你的setItemList()方法在Diff计算前就修改了加载状态,这也会提前改变superCount,加剧计数不一致的问题。
解决方案
1. 统一数据源,移除计数的最大值判断
你的DiffRecyclerViewAdapter已经重写了getList()返回DiffCalculator的当前列表,因此父类的getItemCount()应该基于这个统一的数据源来计算额外视图的数量。直接移除自定义的计数逻辑,让父类处理所有计数(包括数据项+额外视图):
修改DiffRecyclerViewAdapter的getItemCount():
@Override public int getItemCount() { int count = super.getItemCount(); log("getItemCount() called with count=" + count); return count; }
确保父类BaseRecyclerViewAdapter的getItemCount()是基于getList()来计算的,示例实现:
// BaseRecyclerViewAdapter中的实现示例 @Override public int getItemCount() { int dataCount = getList().size(); // 加上加载视图 if (isLoading()) { dataCount += 1; } // 空列表时显示空视图 if (dataCount == 0 && shouldShowEmptyView()) { dataCount = 1; } return dataCount; }
2. 延迟修改加载状态到Diff更新完成后
不要在setItemList()中直接修改加载状态,而是等到Diff结果分发完成后再更新,避免中途改变itemCount:
修改setItemList():
public void setItemList(List<T> list) { mDiffCalculator.calculateDifference(list); }
然后在UpdateCallback的onUpdateFinish()中处理加载状态:
@Override public void onUpdateFinish() { Log.w(getClass().getSimpleName(), "onUpdateFinish()"); adapter.setListUpdateInProgress(false); // 加载状态更新放在Diff完成后 if (adapter.isLoading()) { adapter.setDataIsLoading(false, true); } }
3. 验证DiffCallback的正确性
确保DiffComparable接口的实现没有问题:
isItemTheSame():必须基于唯一标识(比如ID)判断两个item是否为同一个,不能用内容比较。isContentTheSame():要准确比较item的所有显示内容,避免误判导致不必要的更新或遗漏更新。
4. 禁止直接修改原始列表
确保所有列表更新都通过setItemList()走DiffCalculator,不要直接修改BaseRecyclerViewAdapter中的原始itemList,否则会导致DiffCalculator的数据源和Adapter的数据源不一致。
总结
通过统一数据源、移除冲突的计数逻辑、延迟状态修改,就能解决DiffUtil更新过程中itemCount不一致的问题,避免触发RecyclerView的越界异常。同时要确保Diff相关的回调方法实现准确,保证Diff计算的结果符合预期。
内容的提问来源于stack exchange,提问作者Martin S

