RecyclerView滚动时出现IndexOutOfBoundsException问题求助
这种滚动加载时的数组越界问题我碰到过好几次,大概率是加载流程里的数据源同步、触发逻辑或者Adapter更新方式出了问题,咱们一步步拆解可能的原因:
1. 重复触发加载导致数据源混乱
最常见的原因是没有加加载状态锁,当第一次加载的异步请求还没完成时,快速滚动又触发了第二次加载。这时候previouslength还是第一次加载前的数值,两次请求的结果先后插入数据源,就会导致索引计算错误,直接触发越界。
解决办法是加一个isLoading标志位,在加载过程中禁止再次触发:
private boolean isLoading = false; // 滚动监听里的触发逻辑 @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int lastVisibleItem = layoutManager.findLastVisibleItemPosition(); int totalItemCount = layoutManager.getItemCount(); // 只有不在加载状态,且滚动到最后一个可见条目时才触发加载 if (!isLoading && lastVisibleItem == totalItemCount - 1) { isLoading = true; previouslength = arrayList.size(); // 记录当前数据源长度 loadMoreData(); // 触发异步加载 } } // 加载完成后的回调 private void onLoadMoreSuccess(List newData) { arrayList.addAll(newData); adapter.notifyItemRangeInserted(previouslength, newData.size()); isLoading = false; // 重置加载状态 }
2. 数据源修改的线程安全问题
加载更多的请求一般是异步的,如果在子线程里直接修改arrayList,同时主线程的RecyclerView正在绑定数据(调用onBindViewHolder),就会出现数据源长度不一致的情况——主线程拿到的长度和子线程修改后的长度不匹配,调用arrayList.get(position)时就会越界。
解决:所有修改数据源和更新Adapter的操作必须放在主线程,异步请求回来后通过getActivity().runOnUiThread()或者recyclerView.post()切换到主线程执行:
// 异步请求回调示例 new AsyncTask<Void, Void, List>() { @Override protected List doInBackground(Void... voids) { // 网络请求获取数据 return fetchNewData(); } @Override protected void onPostExecute(List newData) { super.onPostExecute(newData); // 主线程更新数据源和Adapter arrayList.addAll(newData); adapter.notifyItemRangeInserted(previouslength, newData.size()); isLoading = false; } }.execute();
3. Adapter更新方法使用错误
如果图省事用notifyDataSetChanged()全局刷新,而不是用notifyItemRangeInserted()局部更新,会导致RecyclerView的布局状态混乱,尤其是快速滚动时,ViewHolder复用可能拿到错误的位置索引,引发越界。
一定要用局部更新的方法:notifyItemRangeInserted(起始位置, 新增条目数量),这样RecyclerView只会更新新增的部分,避免全局刷新带来的状态不一致问题。
4. previouslength变量维护不当
你代码里用到了previouslength来记录加载前的数据源长度,如果这个变量没有在每次触发加载前正确赋值,或者被其他逻辑意外修改,就会导致插入位置计算错误。比如第一次加载后没更新previouslength,第二次加载时还是用旧值,插入位置就会偏移,触发越界。
排查要点:确保每次触发加载前,都把previouslength设置为当前arrayList.size(),不要在其他地方随意修改这个变量。
5. ViewHolder绑定数据时的边界判断
快速滚动时,RecyclerView的ViewHolder复用机制可能会让onBindViewHolder拿到的position暂时超出当前数据源的长度(比如异步加载还没完成,但滚动已经触发了视图绑定)。这时候直接调用arrayList.get(position)就会越界。
解决:在onBindViewHolder里先做边界判断:
@Override public void onBindViewHolder(ViewHolder holder, int position) { // 先判断position是否在数据源有效范围内 if (position < 0 || position >= arrayList.size()) { // 可以设置默认占位,或者直接返回 holder.textView.setText(""); return; } // 正常绑定数据 DataModel data = arrayList.get(position); holder.textView.setText(data.getContent()); }
你可以先从加isLoading标志位开始排查,这是最容易忽略也最常见的问题,然后再依次检查线程安全和Adapter更新逻辑,应该能很快定位到问题。
内容的提问来源于stack exchange,提问作者jobin

