【问题标题】:ArrayIndexOutOfBoundsException when populating RecyclerView填充 RecyclerView 时出现 ArrayIndexOutOfBoundsException
【发布时间】:2014-12-27 01:21:25
【问题描述】:

当我尝试填充我的 RecyclerView 时,我经常遇到错误,但错误似乎发生在 StaggeredGridLayoutManager 内部。

我从我的数据库中填充 RecyclerView,然后使用它将图像添加到我的适配器

List<WImage> images;

public void addImages(LinkedHashMap<Integer,WImage> newImages){
    for(Map.Entry<Integer,WImage> entry : newImages.entrySet()){
        addImage(entry.getValue(),entry.getKey());
    }
}

public void addImage(WImage image, int position){
    images.add(position,image);
    notifyItemRangeInserted(position, 1);

}

奇怪的是,当应用程序第一次加载时它很好并且可以正确显示所有内容,但是如果我根据数据库查询将当前在 RecyclerView 中的内容替换为不同的图像集,那么我会再次返回初始图像这个错误。

java.lang.ArrayIndexOutOfBoundsException: src.length=16 srcPos=0 dst.length=0 dstPos=0 length=16
            at java.lang.System.arraycopy(Native Method)
            at android.support.v7.widget.StaggeredGridLayoutManager$LazySpanLookup.ensureSize(StaggeredGridLayoutManager.java:2277)
            at android.support.v7.widget.StaggeredGridLayoutManager$LazySpanLookup.offsetForAddition(StaggeredGridLayoutManager.java:2323)
            at android.support.v7.widget.StaggeredGridLayoutManager.handleUpdate(StaggeredGridLayoutManager.java:1280)
            at android.support.v7.widget.StaggeredGridLayoutManager.onItemsAdded(StaggeredGridLayoutManager.java:1253)
            at android.support.v7.widget.RecyclerView$5.dispatchUpdate(RecyclerView.java:406)
            at android.support.v7.widget.RecyclerView$5.onDispatchSecondPass(RecyclerView.java:422)
            at android.support.v7.widget.AdapterHelper.consumePostponedUpdates(AdapterHelper.java:119)
            at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1896)
            at android.support.v7.widget.RecyclerView.resumeRequestLayout(RecyclerView.java:1101)
            at android.support.v7.widget.RecyclerView$1.run(RecyclerView.java:155)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
            at android.view.Choreographer.doCallbacks(Choreographer.java:574)
            at android.view.Choreographer.doFrame(Choreographer.java:543)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5086)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
            at dalvik.system.NativeStart.main(Native Method)

它可以很好地浏览所有图像,但是当它准备好再次调用onBindViewHolder 时它会崩溃。

当我更改 RecyclerView 中的数据时,我先用它删除所有内容,然后再填充来自查询的新数据

public void removeAll(){
    images.clear();
    notifyDataSetChanged();
}

我还尝试一次将它们全部插入,然后使用起始位置和图像总数调用notifyItemRangeInserted,但它仍然崩溃并出现相同的错误。

编辑

当我击中大约 20 个左右需要放入网格的项目时,似乎会发生这种情况。如果我只使用notifyDatasetChanged,它似乎工作正常,但我想使用notifyItemRangeInserted,所以我得到了漂亮的插入动画

有其他人看到过这个问题吗?

EDIT2

这是我的onCreateViewHolder

@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, final int i) {

    View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.image_normal_grid_cell, viewGroup, false);
    return new ViewHolder(v);
}

还有我的onBindViewHolder

@Override
public void onBindViewHolder(final ViewHolder holder, final int i) {
    final WImage image = images.get(i);
    holder.v.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            listener.onItemClick(v,i,image);
        }
    });

    holder.heart.setActivated(image.getLikeDislike());

    holder.heart.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Uri.Builder ub = SyncProvider.CONTENT_ID_URI_BASE.buildUpon();
            ub.appendPath(String.valueOf(image.getId()));
            ContentValues values = new ContentValues();

            if(image.getLikeDislike()){
                values.put(SyncProvider.LIKE,0);
                holder.heart.setActivated(false);
                image.setLikeDislike(false);
            }else{
                values.put(SyncProvider.LIKE,1);
                holder.heart.setActivated(true);
                image.setLikeDislike(true);
            }
            values.put(SyncProvider.IMAGE_CHANGED,1);
            context.getContentResolver().update(ub.build(),values,null,null);
        }
    });
    if(cancelPotentialDownload(image.getUri(),holder.imageview)){
        LoadImageTask task = new LoadImageTask(holder.imageview,image.getThumbUri());
        holder.imageview.setImageDrawable(new AsyncBitmap(task));
        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }
}

编辑 3

这是我更换适配器时要做的事情

在我的导航抽屉的选定索引更改上,我初始化我的加载器以进行查询

public void initLoader(int page){
    if(currentPage != page && mAdapter != null){
        mAdapter.removeAll();
        hashImages.clear();
        getLoaderManager().destroyLoader(currentPage);
    }
    currentPage = page;
    getLoaderManager().initLoader(page,null,this);
}

然后在我的加载器的 onLoadFinished 中,我将对象添加到适配器

@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
    if(cursor != null && cursor.moveToFirst()){
            do{
                WImage image = new WImage(cursor);
                if(!hashImages.containsKey(image.getId())){
                    newImages.put(tempHash.size(),image);
                }else if(image.getImageChanged()){
                    update = true;
                }
                int pos = tempHash.size();
                tempHash.put(image.getId(),pos);
            }while(cursor.moveToNext());
        }

    if(newImages.size() > 0){
            mAdapter.addImages(newImages);
        }

【问题讨论】:

  • @pskink 什么部分?我发布的代码在我的适配器中
  • 不幸的是,这可能是网格布局管理器中的一个错误 :(。您可以向 b.android.com 发布一个错误报告,其中包含一个重现崩溃的示例应用程序吗?
  • 顺便说一句,您也可以发布替换适配器的代码吗?那里可能有问题。 (例如错过一些事件)
  • @yigit 见编辑 3 的代码
  • 很遗憾地通知您,这看起来像是 StaggeredGrid 中的一个错误。在预测动画期间,它无法增长其内部簿记数组。我会为此创建一个内部票证,但请随时在 b.android.com 上创建一个错误报告,以便在问题得到解决时得到通知。

标签: android android-recyclerview


【解决方案1】:

这个bug已经被google在21.0.2版本中解决了:

compile "com.android.support:support-v4:21.0.2"
compile "com.android.support:recyclerview-v7:21.0.2"

【讨论】:

    【解决方案2】:

    我认为这个问题是由https://code.google.com/p/android/issues/detail?id=77846 触发的。 能否将notifyDataSetChanged 调用替换为特定的通知事件(notifyItemRemoved/Added),看看能否重现?

    【讨论】:

    • 是的,每次删除一个时仍然会出现此错误
    • 嗯,有趣的是,如果我调用 mAdapter.mItems.clear(); mAdapter.notifyItemRangeRemoved(0, size); /* load data*/ mAdapter.mItems.addAll(0, newSize); mAdapter.notifyItemRangeInserted(0, newSize);,我的测试用例通过,如果我这样做,则崩溃:mAdapter.mItems.clear(); mAdapter.notifyDataSetChanged(); /* load data*/ mAdapter.mItems.addAll(0, newSize); mAdapter.notifyItemRangeInserted(0, newSize);
    • 如果你愿意,我可以稍后将我的项目上传到 github,看看它是否可以帮助你(或我)
    • 是的,它会有所帮助。这个错误很容易修复,但我想了解这里发生了什么,因为修复它可能会解锁另一个错误。一个示例项目将非常有帮助。如果您需要将其添加为私人仓库,我的 github 名称与我的 SO 名称相同。谢谢。
    • 这里是回购。 github.com/tyczj/CrashExample我取出了注册应用程序所需的所有不必要的部分,所以它应该可以正确加载,但如果它没有让我知道
    【解决方案3】:

    当您尝试notifyDataSetChanged/notifyItemRemoved/Added/changeRecyclerView isComputingLayout 时发生此崩溃

    如果您有一些自定义逻辑来更改适配器,则可能会发生这种情况 响应 View 回调的内容

    在这些情况下,您应该使用 Handler 推迟更改

    RecyclerView.isComputingLayout() 返回 false 之前不要通知更改

     /**
         * Returns whether RecyclerView is currently computing a layout.
         * <p>
         * If this method returns true, it means that RecyclerView is in a lockdown state and any
         * attempt to update adapter contents will result in an exception because adapter contents
         * cannot be changed while RecyclerView is trying to compute the layout.
         * <p>
         * It is very unlikely that your code will be running during this state as it is
         * called by the framework when a layout traversal happens or RecyclerView starts to scroll
         * in response to system events (touch, accessibility etc).
         * <p>
         * This case may happen if you have some custom logic to change adapter contents in
         * response to a View callback (e.g. focus change callback) which might be triggered during a
         * layout calculation. In these cases, you should just postpone the change using a Handler or a
         * similar mechanism.
         *
         * @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code>
         *         otherwise
         */
        public boolean isComputingLayout() {
            return mLayoutOrScrollCounter > 0;
        }
    

    【讨论】:

      猜你喜欢
      • 2015-09-08
      • 1970-01-01
      • 2021-12-17
      • 1970-01-01
      • 1970-01-01
      • 2020-04-06
      • 1970-01-01
      • 2023-03-31
      • 2020-10-24
      相关资源
      最近更新 更多