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

Android RecyclerView快速滚动触发IndexOutOfBoundsException异常解决方法

快速滚动RecyclerView时触发IndexOutOfBoundsException,缓慢滚动无异常

快速滚动RecyclerView时出现如下崩溃,缓慢滚动则不会触发:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.freshop.android.adhoc.consumer, PID: 12225
    java.lang.IndexOutOfBoundsException: Index: 43, Size: 36
        at java.util.ArrayList.get(ArrayList.java:437)
        at com.freshop.android.consumer.adapter.DepartmentsAdapter$CustomViewHolder$1.onScrollStateChanged(DepartmentsAdapter.java:526)
        at androidx.recyclerview.widget.RecyclerView.dispatchOnScrollStateChanged(RecyclerView.java:5210)
        at androidx.recyclerview.widget.RecyclerView.setScrollState(RecyclerView.java:1550)
        at androidx.recyclerview.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:5397)

相关代码片段

DepartmentsAdapter的CustomViewHolder实现

public class CustomViewHolder extends RecyclerView.ViewHolder {
        private final TextView departmentTitle;
        private final TextView moreProducts;
        private final RecyclerView mHorizontalRecyclerView;
        private ProductShowcaseAdapter showcaseAdapter;
        private final ProgressBar progress;
        private final FrameLayout item_framelayout;
        private final LinearLayout pr_row;

        public CustomViewHolder(View itemView) {
            super(itemView);
            pr_row = itemView.findViewById(R.id.pr_row);
            departmentTitle = itemView.findViewById(R.id.department);
            moreProducts = itemView.findViewById(R.id.moreProducts);
            progress = itemView.findViewById(R.id.progress);
            item_framelayout = itemView.findViewById(R.id.item);
            mHorizontalRecyclerView = itemView.findViewById(R.id.products_collection_row);
        }

        public void bind(final CustomViewHolder holder,
                         final Department department,
                         final int position, final OnItemClickListener listener) {
            Log.d(TAG,"bind");
            if (department == null) { return; }
            if (department.getDepartmentProducts() != null && department.getDepartmentProducts().size() > 0) {
                holder.departmentTitle.setText(department.getName());
                if (!department.getHasViewMore()) {
                    holder.moreProducts.setVisibility(View.GONE);
                } else {
                    holder.moreProducts.setVisibility(View.VISIBLE);
                    holder.moreProducts.setText(R.string.lbl_more);
                    holder.moreProducts.setContentDescription(department.getName() + context.get().getString(R.string.lbl_more));
                    holder.moreProducts.setTextColor(Theme.primaryColor);
                    holder.moreProducts.setOnClickListener(v -> {
                        if(fragment != null) {
                            (((ProductShowcaseFragment)fragment)).updateDataScrollToTopListener();
                        }
                        listener.onItemClick(null, department, AppConstants.PRODUCTBTNTYPEMORE, null, null, 0, 0);
                    });
                }

                List<Product> products1 = DataHelper.updateProductsWithFavAndListItems(department.getDepartmentProducts(), shoppingListItems);

                if (showcaseAdapter == null && context != null) {
                    showcaseAdapter = new ProductShowcaseAdapter(context.get(), imageConfig, products1, shoppingListItems,
                            (data, product, holder1, type, pos) -> listener.onItemClick(product, department, type, holder1, data, position, pos),
                            DataHelper::trackUrlOnLoad);

                    mHorizontalRecyclerView.setNestedScrollingEnabled(false);
                    ((SimpleItemAnimator) Objects.requireNonNull(mHorizontalRecyclerView.getItemAnimator())).setSupportsChangeAnimations(false);

                    if (context != null) {
                        LinearLayoutManager llm = new LinearLayoutManager(context.get());
                        llm.setOrientation(LinearLayoutManager.HORIZONTAL);
                        mHorizontalRecyclerView.setLayoutManager(llm);
                    }
                    mHorizontalRecyclerView.setAdapter(holder.showcaseAdapter);
                } else {
                    if (showcaseAdapter != null)
                        showcaseAdapter.updateDataSet(products1, shoppingListItems);
                }

                holder.item_framelayout.setVisibility(View.VISIBLE);
                holder.progress.setVisibility(View.GONE);

                if (products1 != null && products1.size() >= Integer.parseInt(department.getLimit())) {
                    mHorizontalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                        @Override
                        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                            super.onScrollStateChanged(recyclerView, newState);

                            if (!recyclerView.canScrollHorizontally(1) && !mScrolled) {
                                if (scrollListener != null) {
                                    scrollListener.onScrollListener(true, department, position);
                                }
                            }
                            if (recyclerView.getLayoutManager() != null &&
                                    department.getDepartmentProducts().size() > 0 &&
                                    ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition() >= 0) {
                                if (department.getDepartmentProducts().contains(department.getDepartmentProducts()
    // 堆栈追踪中的第526行指向此处
    .get(((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition()))) {
DataHelper.trackUrlOnView(department.getDepartmentProducts().get(((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition()));
                                }
                            }
                        }
                    });
                }
            }
        }
    }

DataHelper.trackUrlOnView方法

public static void trackUrlOnView(Product product) {
        Log.d(TAG,"trackUrlOnView");
        if (product == null) return;

        if (DataHelper.isNullOrEmpty(product.getOnViewProductUrl())) return;

        if (product.getIsAd() && !product.isHasViewedAd()
                && !DataHelper.isNullOrEmpty(product.getOnViewProductUrl())) {
            final Request request = new Request.Builder()
                    .url(product.getOnViewProductUrl())
                    .build();

            new Thread(() -> {
                try {
                    httpClient.newCall(request).execute();
                    product.setHasViewedAd(true);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

问题原因

  1. 重复绑定滚动监听器:每次bind方法执行时都会给mHorizontalRecyclerView添加新的滚动监听器,导致多个监听器同时生效,快速滚动时重复触发逻辑,加剧索引越界风险。
  2. 索引未做合法性校验:直接使用findFirstCompletelyVisibleItemPosition()返回的位置去获取department.getDepartmentProducts()的元素,未校验该索引是否在列表有效范围内。快速滚动时,RecyclerView视图复用,旧监听器持有的department可能已更新,列表大小变化导致索引超出范围。
  3. 数据引用过期:滚动监听器持有bind方法传入的旧department引用,而ViewHolder被复用时,数据已更新,监听器仍在使用旧数据进行操作。

解决方案

1. 避免重复绑定滚动监听器

在添加新的滚动监听器前,先清除旧的监听器,防止重复触发:

if (products1 != null && products1.size() >= Integer.parseInt(department.getLimit())) {
    // 先清除已有监听器
    mHorizontalRecyclerView.clearOnScrollListeners();
    mHorizontalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        // ... 原有逻辑
    });
}

2. 严格校验索引合法性

在获取产品元素前,检查索引是否在列表的有效范围内:

@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
    super.onScrollStateChanged(recyclerView, newState);

    if (!recyclerView.canScrollHorizontally(1) && !mScrolled) {
        if (scrollListener != null) {
            scrollListener.onScrollListener(true, department, position);
        }
    }

    LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
    if (layoutManager == null) return;

    int visiblePos = layoutManager.findFirstCompletelyVisibleItemPosition();
    List<Product> products = department.getDepartmentProducts();

    // 校验列表不为空且索引在有效范围内
    if (products != null && products.size() > 0 && visiblePos >= 0 && visiblePos < products.size()) {
        Product product = products.get(visiblePos);
        DataHelper.trackUrlOnView(product);
    }
}

3. 复用监听器并获取最新数据

将滚动监听器的创建移到ViewHolder构造方法中,通过getAdapterPosition()获取最新位置,再从数据源中获取对应最新数据,避免持有旧引用:

public CustomViewHolder(View itemView) {
    super(itemView);
    // ... 其他视图初始化
    mHorizontalRecyclerView = itemView.findViewById(R.id.products_collection_row);
    
    // 在构造方法中绑定一次监听器
    mHorizontalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            
            int adapterPos = getAdapterPosition();
            // 校验位置合法性
            if (adapterPos < 0 || adapterPos >= mDepartments.size()) return;
            
            Department currentDept = mDepartments.get(adapterPos);
            List<Product> products = currentDept.getDepartmentProducts();
            
            LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
            if (layoutManager == null || products == null || products.size() == 0) return;
            
            int visiblePos = layoutManager.findFirstCompletelyVisibleItemPosition();
            if (visiblePos >= 0 && visiblePos < products.size()) {
                Product product = products.get(visiblePos);
                DataHelper.trackUrlOnView(product);
            }
            
            // 加载更多逻辑
            if (!recyclerView.canScrollHorizontally(1) && !mScrolled) {
                if (scrollListener != null) {
                    scrollListener.onScrollListener(true, currentDept, adapterPos);
                }
            }
        }
    });
}

4. 优化加载更多标记

mScrolled变量如果是全局变量,会导致多个ViewHolder互相干扰,建议将mScrolled改为ViewHolder的成员变量,避免跨视图复用冲突:

public class CustomViewHolder extends RecyclerView.ViewHolder {
    private boolean mScrolled = false;
    // ... 其他成员变量
    
    // 在滚动监听器中使用
    if (!recyclerView.canScrollHorizontally(1) && !mScrolled) {
        mScrolled = true; // 标记已触发加载
        if (scrollListener != null) {
            scrollListener.onScrollListener(true, currentDept, adapterPos);
        }
    }
}

内容的提问来源于stack exchange,提问作者Hemal Adani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 22:20:35