You need to enable JavaScript to run this app. Android分页加载更多,点击列表跳详情报IndexOutOfBoundsException
优惠活动
大模型
产品
解决方案
定价
更多

Android分页加载更多,点击列表跳详情报IndexOutOfBoundsException

异常根本原因

核心矛盾是两个独立的帖子列表数据源完全不同步,直接触发越界崩溃:

  • 给RecyclerView做渲染的数据源是MainActivity里的list(元素类型是用于列表展示的Model对象),分页逻辑是把每次接口返回的帖子追加累加,滚动加载后这个列表的长度会从初始10条增长到20条、30条
  • 详情页跳转时读取的数据源是MainActivity.mListPost(元素类型是包含完整帖子内容的WPPost对象),但在getRetrofit()方法里,每次网络请求返回后都是直接用新的接口返回值整体覆盖这个变量,根本没有做累加操作

实际触发崩溃的完整流程:

  1. 首次进入页面,第一次请求返回10条WPPost,此时mListPost.size() = 10,list里也同步追加10个对应的Model对象,点击前10条不会出问题
  2. 滚动到底部触发第二次分页请求,接口返回新的10条WPPost,此时代码直接执行mListPost = response.body()——mListPost.size()依然是10,但是list是把这10个新的Model追加进去,总长度变成20
  3. 点击索引为11的新增帖子项时,把position=11传给详情页,详情页用这个索引去查只有10个元素的mListPost,直接抛出IndexOutOfBoundsException: Index: 11, Size: 10

附带的逻辑问题会放大故障概率:

  • 分页加载的状态标志位loading只在触发加载时置为false,网络请求完成后从来没有重新置回true,会导致多次重复触发分页请求
  • 滚动到底部触发加载时,直接新建适配器实例设置给RecyclerView,完全没必要,只要更新原适配器的数据集再刷新即可
  • 适配器里点击事件获取position直接用onBindViewHolder传入的旧position值,在列表刷新、动画过程中会出现位置错位,应该用getAbsoluteAdapterPosition()获取实时位置
  • getItemViewType()方法直接返回position值,会给RecyclerView缓存机制造成不必要的ViewHolder创建开销
  • 适配器初始化时把total_types赋值为初始数据集大小,从来没有更新,属于冗余无用代码
  • 循环追加列表数据时,每加一个元素就调用一次notifyDataSetChanged(),会触发不必要的频繁布局重算
逐模块修复方案

1. 核心数据源同步修复

修改getRetrofit()里的WPPost赋值逻辑,不要直接覆盖mListPost,要做累加:

// 原来的错误赋值:直接覆盖
// mListPost = response.body();

// 修复后:初始化列表后追加数据
if (mListPost == null) {
    mListPost = new ArrayList<>();
}
// 把新返回的帖子追加入全局列表,不要覆盖
mListPost.addAll(response.body());

注意保证列表的Model顺序和mListPost的顺序完全一致,不要有插入、删除等打乱顺序的操作,确保索引一一对应。

2. 分页加载逻辑修复

  • 网络请求成功/失败后,把loading标志位置回true,允许下一次滚动触发加载
  • 不要在滚动监听里直接新建适配器,保留全局的adapter实例,加载完成后直接调用adapter.notifyDataSetChanged()刷新即可
  • 循环追加Model对象时,等所有元素追加完成后统一调用一次刷新方法,减少性能损耗

修改后的滚动监听代码示例:

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (dy > 0) {
            visibleItemCount = mLayoutManager.getChildCount();
            totalItemCount = mLayoutManager.getItemCount();
            pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();

            if (loading) {
                if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
                    loading = false;
                    getRetrofit();
                    // 不要新建适配器,直接复用已有实例刷新
                    adapter.notifyDataSetChanged();
                }
            }
        }
    }
});

在getRetrofit()的回调里,请求完成后补上状态重置:

@Override
public void onResponse(Call<List<WPPost>> call, Response<List<WPPost>> response) {
    // ... 原来的追加逻辑
    loading = true; // 请求完成,重置加载状态
}

@Override
public void onFailure(Call<List<WPPost>> call, Throwable t) {
    loading = true; // 失败也要重置状态,避免卡死
}

3. 适配器点击逻辑修复

把原来直接用onBindViewHolder传入的旧position的逻辑,替换成实时获取的绝对位置,避免位置错位:

View.OnClickListener clickListener = v -> {
    // 实时获取当前ViewHolder对应的位置,不要用闭包里捕获的旧position
    RecyclerView.ViewHolder viewHolder = (RecyclerView.ViewHolder) v.getTag();
    int currentPosition = viewHolder.getAbsoluteAdapterPosition();
    // 越界判断,避免位置无效时跳转崩溃
    if (currentPosition == RecyclerView.NO_POSITION) return;
    Intent intent = new Intent(mContext, PostDetails.class);
    intent.putExtra("itemPosition", currentPosition);
    mContext.startActivity(intent);
};

给每个可点击的控件设置tag,绑定到对应的ViewHolder:

@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
    final Model object = dataset.get(position);
    ImageTypeViewHolder viewHolder = (ImageTypeViewHolder) holder;

    viewHolder.title.setText(object.title);
    viewHolder.subtitle.setText(object.subtitle);
    Glide.with(mContext)
            .load(object.Image)
            .dontAnimate()
            .placeholder(R.drawable.icon)
            .into(viewHolder.imageView);

    // 给控件设置tag,关联当前ViewHolder
    viewHolder.title.setTag(holder);
    viewHolder.subtitle.setTag(holder);
    viewHolder.imageView.setTag(holder);
    // 给所有可点击控件设置统一的点击监听
    viewHolder.title.setOnClickListener(clickListener);
    viewHolder.subtitle.setOnClickListener(clickListener);
    viewHolder.imageView.setOnClickListener(clickListener);
}

同时删除冗余的total_types变量、修改错误的getItemViewType()实现:

// 原来错误的实现:return position; 会导致ViewHolder完全不缓存
@Override
public int getItemViewType(int position) {
    // 所有 item 类型一致,直接返回固定值即可
    return Model.IMAGE_TYPE;
}

4. 详情页增加防护逻辑

在详情页读取position之前,增加越界判断,避免极端情况下的崩溃:

int position = i.getExtras().getInt("itemPosition");
// 先判断列表是否为空、索引是否在合法范围内
if (MainActivity.mListPost == null || position < 0 || position >= MainActivity.mListPost.size()) {
    finish();
    return;
}
// 合法范围内再读取内容
Log.e("PostDetails ", "title is " + MainActivity.mListPost.get(position).getTitle().getRendered());

内容的提问来源于stack exchange,提问作者Punjabi Dharti

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.27 09:45:03