RecyclerView e java.lang.IndexOutOfBoundsException: rilevata incoerenza. Posizione dell'adattatore del supporto della vista non valida VisualizzaHolder nei dispositivi Samsung


253

Ho una vista del riciclo che funziona perfettamente su tutti i dispositivi tranne Samsung. Su Samsung, ho capito

java.lang.IndexOutOfBoundsException: rilevata incoerenza. Posizione dell'adattatore del supporto della vista non valida ViewHolder

quando torno al frammento con la vista del riciclatore da un'altra attività.

Codice adattatore:

public class FeedRecyclerAdapter extends RecyclerView.Adapter<FeedRecyclerAdapter.MovieViewHolder> {
    public static final String getUserPhoto = APIConstants.BASE_URL + APIConstants.PICTURE_PATH_SMALL;
    Movie[] mMovies = null;
    Context mContext = null;
    Activity mActivity = null;
    LinearLayoutManager mManager = null;
    private Bus uiBus = null;
    int mCountOfLikes = 0;

    //Constructor
    public FeedRecyclerAdapter(Movie[] movies, Context context, Activity activity,
                               LinearLayoutManager manager) {
        mContext = context;
        mActivity = activity;
        mMovies = movies;
        mManager = manager;
        uiBus = BusProvider.getUIBusInstance();
    }

    public void setMoviesAndNotify(Movie[] movies, boolean movieIgnored) {
        mMovies = movies;
        int firstItem = mManager.findFirstVisibleItemPosition();
        View firstItemView = mManager.findViewByPosition(firstItem);
        int topOffset = firstItemView.getTop();
        notifyDataSetChanged();
        if(movieIgnored) {
            mManager.scrollToPositionWithOffset(firstItem - 1, topOffset);
        } else {
            mManager.scrollToPositionWithOffset(firstItem, topOffset);
        }
    }

    // Create new views (called by layout manager)
    @Override
    public MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.feed_one_recommended_movie_layout, parent, false);

        return new MovieViewHolder(view);
    }

    // Replaced contend of each view (called by layout manager)
    @Override
    public void onBindViewHolder(MovieViewHolder holder, int position) {
        setLikes(holder, position);
        setAddToCollection(holder, position);
        setTitle(holder, position);
        setIgnoreMovieInfo(holder, position);
        setMovieInfo(holder, position);
        setPosterAndTrailer(holder, position);
        setDescription(holder, position);
        setTags(holder, position);
    }

    // returns item count (called by layout manager)
    @Override
    public int getItemCount() {
        return mMovies != null ? mMovies.length : 0;
    }

    private void setLikes(final MovieViewHolder holder, final int position) {
        List<Reason> likes = new ArrayList<>();
        for(Reason reason : mMovies[position].reasons) {
            if(reason.title.equals("Liked this movie")) {
                likes.add(reason);
            }
        }
        mCountOfLikes = likes.size();
        holder.likeButton.setText(mContext.getString(R.string.like)
            + Html.fromHtml(getCountOfLikesString(mCountOfLikes)));
        final MovieRepo repo = MovieRepo.getInstance();
        final int pos = position;
        final MovieViewHolder viewHolder = holder;
        holder.likeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mMovies[pos].isLiked) {
                    repo.unlikeMovie(AuthStore.getInstance()
                        .getAuthToken(), mMovies[pos].id, new Callback<Movie>() {
                        @Override
                        public void success(Movie movie, Response response) {
                            Drawable img = mContext.getResources().getDrawable(R.drawable.ic_like);
                            viewHolder.likeButton
                                .setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
                            if (--mCountOfLikes <= 0) {
                                viewHolder.likeButton.setText(mContext.getString(R.string.like));
                            } else {
                                viewHolder.likeButton
                                    .setText(Html.fromHtml(mContext.getString(R.string.like)
                                        + getCountOfLikesString(mCountOfLikes)));
                            }
                            mMovies[pos].isLiked = false;
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext.getApplicationContext(),
                                mContext.getString(R.string.cannot_like), Toast.LENGTH_LONG)
                                .show();
                        }
                    });
                } else {
                    repo.likeMovie(AuthStore.getInstance()
                        .getAuthToken(), mMovies[pos].id, new Callback<Movie>() {
                        @Override
                        public void success(Movie movie, Response response) {
                            Drawable img = mContext.getResources().getDrawable(R.drawable.ic_liked_green);
                            viewHolder.likeButton
                                .setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
                            viewHolder.likeButton
                                .setText(Html.fromHtml(mContext.getString(R.string.like)
                                    + getCountOfLikesString(++mCountOfLikes)));
                            mMovies[pos].isLiked = true;
                            setComments(holder, position);
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext,
                                mContext.getString(R.string.cannot_like), Toast.LENGTH_LONG).show();
                        }
                    });
                }
            }
        });
    }

    private void setComments(final MovieViewHolder holder, final int position) {
        holder.likeAndSaveButtonLayout.setVisibility(View.GONE);
        holder.commentsLayout.setVisibility(View.VISIBLE);
        holder.sendCommentButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (holder.commentsInputEdit.getText().length() > 0) {
                    CommentRepo repo = CommentRepo.getInstance();
                  repo.sendUserComment(AuthStore.getInstance().getAuthToken(), mMovies[position].id,
                        holder.commentsInputEdit.getText().toString(), new Callback<Void>() {
                            @Override
                            public void success(Void aVoid, Response response) {
                                Toast.makeText(mContext, mContext.getString(R.string.thanks_for_your_comment),
                                    Toast.LENGTH_SHORT).show();
                                hideCommentsLayout(holder);
                            }

                            @Override
                            public void failure(RetrofitError error) {
                                Toast.makeText(mContext, mContext.getString(R.string.cannot_add_comment),
                                    Toast.LENGTH_LONG).show();
                            }
                        });
                } else {
                    hideCommentsLayout(holder);
                }
            }
        });
    }

    private void hideCommentsLayout(MovieViewHolder holder) {
        holder.commentsLayout.setVisibility(View.GONE);
        holder.likeAndSaveButtonLayout.setVisibility(View.VISIBLE);
    }

    private void setAddToCollection(final MovieViewHolder holder, int position) {
        final int pos = position;
        if(mMovies[position].isInWatchlist) {
            holder.saveButton
              .setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_check_green, 0, 0, 0);
        }
        final CollectionRepo repo = CollectionRepo.getInstance();
        holder.saveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!mMovies[pos].isInWatchlist) {
                   repo.addMovieToCollection(AuthStore.getInstance().getAuthToken(), 0, mMovies[pos].id, new Callback<MovieCollection[]>() {
                            @Override
                            public void success(MovieCollection[] movieCollections, Response response) {
                                holder.saveButton
                                    .setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_check_green, 0, 0, 0);

                                mMovies[pos].isInWatchlist = true;
                            }

                            @Override
                            public void failure(RetrofitError error) {
                                Toast.makeText(mContext, mContext.getString(R.string.movie_not_added_to_collection),
                                    Toast.LENGTH_LONG).show();
                            }
                        });
                } else {
                 repo.removeMovieFromCollection(AuthStore.getInstance().getAuthToken(), 0,
                        mMovies[pos].id, new Callback<MovieCollection[]>() {
                        @Override
                        public void success(MovieCollection[] movieCollections, Response response) {
                            holder.saveButton
                                .setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_plus, 0, 0, 0);

                            mMovies[pos].isInWatchlist = false;
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext,
                                mContext.getString(R.string.cannot_delete_movie_from_watchlist),
                                Toast.LENGTH_LONG).show();
                        }
                    });
                }
            }
        });
    }

    private String getCountOfLikesString(int countOfLikes) {
        String countOfLikesStr;
        if(countOfLikes == 0) {
            countOfLikesStr = "";
        } else if(countOfLikes > 999) {
            countOfLikesStr = " " + (countOfLikes/1000) + "K";
        } else if (countOfLikes > 999999){
            countOfLikesStr = " " + (countOfLikes/1000000) + "M";
        } else {
            countOfLikesStr = " " + String.valueOf(countOfLikes);
        }
        return "<small>" + countOfLikesStr + "</small>";
    }

    private void setTitle(MovieViewHolder holder, final int position) {
        holder.movieTitleTextView.setText(mMovies[position].title);
        holder.movieTitleTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieDetailActivity.openView(mContext, mMovies[position].id, true, false);
            }
        });
    }

    private void setIgnoreMovieInfo(MovieViewHolder holder, final int position) {
        holder.ignoreMovie.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieRepo repo = MovieRepo.getInstance();
                repo.hideMovie(AuthStore.getInstance().getAuthToken(), mMovies[position].id,
                    new Callback<Void>() {
                        @Override
                        public void success(Void aVoid, Response response) {
                            Movie[] newMovies = new Movie[mMovies.length - 1];
                            for (int i = 0, j = 0; j < mMovies.length; i++, j++) {
                                if (i != position) {
                                    newMovies[i] = mMovies[j];
                                } else {
                                    if (++j < mMovies.length) {
                                        newMovies[i] = mMovies[j];
                                    }
                                }
                            }
                            uiBus.post(new MoviesChangedEvent(newMovies));
                            setMoviesAndNotify(newMovies, true);
                            Toast.makeText(mContext, mContext.getString(R.string.movie_ignored),
                                Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext, mContext.getString(R.string.movie_ignored_failed),
                                Toast.LENGTH_LONG).show();
                        }
                    });
            }
        });
    }

    private void setMovieInfo(MovieViewHolder holder, int position) {
        String imdp = "IMDB: ";
        String sources = "", date;
        if(mMovies[position].showtimes != null && mMovies[position].showtimes.length > 0) {
            int countOfSources = mMovies[position].showtimes.length;
            for(int i = 0; i < countOfSources; i++) {
                sources += mMovies[position].showtimes[i].name + ", ";
            }
            sources = sources.trim();
            if(sources.charAt(sources.length() - 1) == ',') {
                if(sources.length() > 1) {
                    sources = sources.substring(0, sources.length() - 2);
                } else {
                    sources = "";
                }
            }
        } else {
            sources = "";
        }
        imdp += mMovies[position].imdbRating + " | ";
        if(sources.isEmpty()) {
            date = mMovies[position].releaseYear;
        } else {
            date = mMovies[position].releaseYear + " | ";
        }

        holder.movieInfoTextView.setText(imdp + date + sources);
    }

    private void setPosterAndTrailer(final MovieViewHolder holder, final int position) {
        if (mMovies[position] != null && mMovies[position].posterPath != null
            && !mMovies[position].posterPath.isEmpty()) {
            Picasso.with(mContext)
                .load(mMovies[position].posterPath)
             .error(mContext.getResources().getDrawable(R.drawable.noposter))
                .into(holder.posterImageView);
        } else {
            holder.posterImageView.setImageResource(R.drawable.noposter);
        }
        holder.posterImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieDetailActivity.openView(mActivity, mMovies[position].id, false, false);
            }
        });
        if(mMovies[position] != null && mMovies[position].trailerLink  != null
            && !mMovies[position].trailerLink.isEmpty()) {
            holder.playTrailer.setVisibility(View.VISIBLE);
            holder.playTrailer.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    MovieDetailActivity.openView(mActivity, mMovies[position].id, false, true);
                }
            });
        }
    }

    private void setDescription(MovieViewHolder holder, int position) {
        String text = mMovies[position].overview;
        if(text == null || text.isEmpty()) {
       holder.descriptionText.setText(mContext.getString(R.string.no_description));
        } else if(text.length() > 200) {
            text = text.substring(0, 196) + "...";
            holder.descriptionText.setText(text);
        } else {
            holder.descriptionText.setText(text);
        }
        final int pos = position;
        holder.descriptionText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieDetailActivity.openView(mActivity, mMovies[pos].id, false, false);
            }
        });
    }

    private void setTags(MovieViewHolder holder, int position) {
        List<String> tags = Arrays.asList(mMovies[position].tags);
        if(tags.size() > 0) {
            CastAndTagsFeedAdapter adapter = new CastAndTagsFeedAdapter(tags,
                mContext, ((FragmentActivity) mActivity).getSupportFragmentManager());
            holder.tags.setItemMargin(10);
            holder.tags.setAdapter(adapter);
        } else {
            holder.tags.setVisibility(View.GONE);
        }
    }

    // class view holder that provide us a link for each element of list
    public static class MovieViewHolder extends RecyclerView.ViewHolder {
        TextView movieTitleTextView, movieInfoTextView, descriptionText, reasonsCountText;
        TextView reasonText1, reasonAuthor1, reasonText2, reasonAuthor2;
        EditText commentsInputEdit;
        Button likeButton, saveButton, playTrailer, sendCommentButton;
        ImageButton ignoreMovie;
        ImageView posterImageView, userPicture1, userPicture2;
        TwoWayView tags;
        RelativeLayout mainReasonsLayout, firstReasonLayout, secondReasonLayout, reasonsListLayout;
        RelativeLayout commentsLayout;
        LinearLayout likeAndSaveButtonLayout;
        ProgressBar progressBar;

        public MovieViewHolder(View view) {
            super(view);
            movieTitleTextView = (TextView)view.findViewById(R.id.movie_title_text);
            movieInfoTextView = (TextView)view.findViewById(R.id.movie_info_text);
            descriptionText = (TextView)view.findViewById(R.id.text_description);
            reasonsCountText = (TextView)view.findViewById(R.id.reason_count);
            reasonText1 = (TextView)view.findViewById(R.id.reason_text_1);
            reasonAuthor1 = (TextView)view.findViewById(R.id.author_1);
            reasonText2 = (TextView)view.findViewById(R.id.reason_text_2);
            reasonAuthor2 = (TextView)view.findViewById(R.id.author_2);
            commentsInputEdit = (EditText)view.findViewById(R.id.comment_input);
            likeButton = (Button)view.findViewById(R.id.like_button);
            saveButton = (Button)view.findViewById(R.id.save_button);
            playTrailer = (Button)view.findViewById(R.id.play_trailer_button);
            sendCommentButton = (Button)view.findViewById(R.id.send_button);
            ignoreMovie = (ImageButton)view.findViewById(R.id.ignore_movie_imagebutton);
            posterImageView = (ImageView)view.findViewById(R.id.poster_image);
            userPicture1 = (ImageView)view.findViewById(R.id.user_picture_1);
            userPicture2 = (ImageView)view.findViewById(R.id.user_picture_2);
            tags = (TwoWayView)view.findViewById(R.id.list_view_feed_tags);
            mainReasonsLayout = (RelativeLayout)view.findViewById(R.id.reasons_main_layout);
            firstReasonLayout = (RelativeLayout)view.findViewById(R.id.first_reason);
            secondReasonLayout = (RelativeLayout)view.findViewById(R.id.second_reason);
            reasonsListLayout = (RelativeLayout)view.findViewById(R.id.reasons_list);
            commentsLayout = (RelativeLayout)view.findViewById(R.id.comments_layout);
            likeAndSaveButtonLayout = (LinearLayout)view
                .findViewById(R.id.like_and_save_buttons_layout);
            progressBar = (ProgressBar)view.findViewById(R.id.centered_progress_bar);
        }
    }
}

Eccezione:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{42319ed8 position=1 id=-1, oldPos=0, pLpos:0 scrap tmpDetached no parent}
 at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:4166)
 at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4297)
 at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4278)
 at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1947)
 at android.support.v7.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:434)
 at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1322)
 at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:556)
 at android.support.v7.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:171)
 at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2627)
 at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2971)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:562)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
 at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1626)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
 at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
 at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1626)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
 at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
 at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
 at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
 at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
 at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
 at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
07-30 12:48:22.688    9590-9590/com.Filmgrail.android.debug W/System.err? at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
 at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
 at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
 at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2356)
 at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2069)
 at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1254)
 at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6630)
 at android.view.Choreographer$CallbackRecord.run(Choreographer.java:803)
 at android.view.Choreographer.doCallbacks(Choreographer.java:603)
 at android.view.Choreographer.doFrame(Choreographer.java:573)
 at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:789)
 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:5479)
 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:1283)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
 at dalvik.system.NativeStart.main(Native Method)

Come posso risolvere questo problema?


quando torni, i tuoi dati sono gli stessi di quando lasci la pagina?
khusrav,

Sto risolvendo lo stesso problema con cui risolvi ...
Ashvin solanki,

@ Владимир Hai trovato la risposta definitiva?
Alireza Noorali,

Nel mio caso, è stato perché ho iniziato l'attività asincrona E quando uno di essi viene completato prima che un altro scorra verso il basso e l'utente scorre verso il basso e nel frattempo un altro completa e aggiorna l'utente dell'adattatore può ottenere tale eccezione perché la seconda attività ha restituito una quantità inferiore di dati
Vasif

Risposte:


195

Questo problema è causato da RecyclerViewDati modificati in thread diversi. Il modo migliore è controllare l'accesso a tutti i dati. E una soluzione alternativa sta avvolgendo LinearLayoutManager.

Risposta precedente

Si è verificato effettivamente un errore in RecyclerView e il supporto 23.1.1 non è stato ancora corretto.

Per ovviare al problema, si noti che il backtrace si accumula, se riusciamo a catturarlo Exceptionin una classe, potrebbe saltare questo crash. Per me, creo un LinearLayoutManagerWrappere sovrascrivo il onLayoutChildren:

public class WrapContentLinearLayoutManager extends LinearLayoutManager {
    //... constructor
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            Log.e("TAG", "meet a IOOBE in RecyclerView");
        }
    }
}

Quindi impostalo su RecyclerView:

RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view);

recyclerView.setLayoutManager(new WrapContentLinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false));

In realtà cogliere questa eccezione e non sembra ancora avere alcun effetto collaterale.

Inoltre, se usi GridLayoutManagero StaggeredGridLayoutManagerdevi creare un wrapper per questo.

Avviso: RecyclerViewpotrebbe essere in uno stato interno errato.


1
dove lo metti esattamente? su adattatore o attività?
Steve Kamau,

estendere a LinearLayoutManagere sovrascriverlo. Farò un'aggiunta nella mia risposta.
sabato

14
code.google.com/p/android/issues/detail?id=158046 risposta n. 12 ha detto di non farlo.
Robert,

ummm, hai ragione. Sembra difficile disinnescare tutte le potenziali modifiche al thread non UI nella mia app, lo terrò solo come soluzione alternativa.
sakiM

1
Per il mio caso lo sto facendo sullo stesso thread. . mDataHolder.get () removeAll (mHiddenGenre); mAdapter.notifyItemRangeRemoved (mExpandButtonPosition, mHiddenGenre.size ());
JehandadK,

73

Questo è un esempio per l'aggiornamento dei dati con contenuti completamente nuovi. Puoi facilmente modificarlo per adattarlo alle tue esigenze. Ho risolto questo nel mio caso chiamando:

notifyItemRangeRemoved(0, previousContentSize);

prima:

notifyItemRangeInserted(0, newContentSize);

Questa è la soluzione corretta ed è anche menzionata in questo post da un membro del progetto AOSP.


2
Questa soluzione funziona per me. Ho provato molte risposte qui ma non funzionano (non ho testato la prima soluzione)
AndroLife,

Il problema è che questi metodi creano tale incoerenza, anche se eseguita sullo stesso thread.
JehandadK,

Non utilizzo notifyItemRangeInsertede ho questo problema con alcuni dispositivi Samsung
user25

E abbastanza fuori tema qui. L'autore non ha utilizzatonotifyItemRangeInserted
user25

1
Grazie! Questo mi ha aiutato.
DmitryKanunnikoff

35

Ho affrontato questo problema una volta e l'ho risolto avvolgendo LayoutManagere disabilitando le animazioni predittive.

Ecco un esempio:

public class LinearLayoutManagerWrapper extends LinearLayoutManager {

  public LinearLayoutManagerWrapper(Context context) {
    super(context);
  }

  public LinearLayoutManagerWrapper(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
  }

  public LinearLayoutManagerWrapper(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }

  @Override
  public boolean supportsPredictiveItemAnimations() {
    return false;
  }
}

E impostalo su RecyclerView:

RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManagerWrapper(context, LinearLayoutManager.VERTICAL, false);

questo sembra funzionare per me, ma puoi dire perché funziona?
Dennis Anderson,

Risolto anche per me. Come hai previsto che questa potrebbe essere la causa di questo incidente.
Rahul Rastogi,

1
La classe base del metodo LinearLayoutManager supportaPredictiveAnimations () restituisce false per impostazione predefinita. Cosa stiamo ottenendo sovrascrivendo il metodo qui? public boolean supportsPredictiveItemAnimations() { return false; }
M. Hig,

1
@ M.Hig La documentazione per LinearLayoutManagerdice che il valore predefinito è falso, ma tale affermazione è falsa :-( Il codice decompilato LinearLayoutManagerha questo: public boolean supportPredictiveItemAnimations () {return this.mPendingSavedState == null && this.mLastStackFromEnd == this.mStackFromEnd ;}
Clyde il

Uso diff utils per aggiornare il mio adattatore di visualizzazione del riciclatore e questa risposta ha risolto un arresto anomalo. Grazie mille, caro autore!
Eugene P.

29

Nuova risposta: utilizzare DiffUtil per tutti gli aggiornamenti di RecyclerView. Questo aiuterà sia con le prestazioni che con il bug sopra. Vedere qui

Risposta precedente: ha funzionato per me. La chiave è non usare notifyDataSetChanged()e fare le cose giuste nell'ordine corretto:

public void setItems(ArrayList<Article> newArticles) {
    //get the current items
    int currentSize = articles.size();
    //remove the current items
    articles.clear();
    //add all the new items
    articles.addAll(newArticles);
    //tell the recycler view that all the old items are gone
    notifyItemRangeRemoved(0, currentSize);
    //tell the recycler view how many new items we added
    notifyItemRangeInserted(0, newArticles.size());
}

1
Questa è la soluzione più completa con una buona spiegazione. Grazie!
Sakiboy,

quindi qual è lo scopo dell'utilizzo di notificationitemrangeinserted invece di notifydatasetchanged (), @Bolling.
Ankur_009,

@FilipLuch Puoi spiegare perché?
Sreekanth Karumanaghat

3
@SreekanthKarumanaghat certo, non so perché non ho spiegato il motivo. Fondamentalmente cancella quindi ricrea tutti gli elementi nell'elenco. Come nei risultati di ricerca, molto spesso si ottengono gli stessi elementi o quando si esegue l'aggiornamento si ottengono gli stessi elementi e si finisce per ricreare tutto, il che è uno spreco di prestazioni. Utilizzare invece DiffUtils e aggiornare solo le modifiche anziché tutti gli elementi. È come andare dalla A alla Z ogni volta, ma hai solo cambiato la F lì dentro.
Filip Luchianenco,

2
DiffUtil è un tesoro nascosto. Grazie per la condivisione!
Sileria,

22

Le ragioni hanno causato questo problema:

  1. Un problema interno in Recycler quando le animazioni degli oggetti sono abilitate
  2. Modifica sui dati di Recycler in un altro thread
  3. Chiamare i metodi di notifica in modo errato

SOLUZIONE:

----------------- SOLUZIONE 1 ---------------

  • Cattura dell'eccezione (non consigliata soprattutto per il motivo n. 3)

Creare un LinearLayoutManager personalizzato come segue e impostarlo su ReyclerView

    public class CustomLinearLayoutManager extends LinearLayoutManager {

            //Generate constructors

            @Override
            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {

                try {

                    super.onLayoutChildren(recycler, state);

                } catch (IndexOutOfBoundsException e) {

                    Log.e(TAG, "Inconsistency detected");
                }

            }
        }

Quindi impostare RecyclerVIew Layout Manager come segue:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- SOLUZIONE 2 ---------------

  • Disabilita le animazioni degli oggetti (risolve il problema se ha causato de al motivo n. 1):

Ancora una volta, creare un gestore layout lineare personalizzato come segue:

    public class CustomLinearLayoutManager extends LinearLayoutManager {

            //Generate constructors

             @Override
             public boolean supportsPredictiveItemAnimations() {
                 return false;
             }
        }

Quindi impostare RecyclerVIew Layout Manager come segue:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- SOLUZIONE 3 ---------------

  • Questa soluzione risolve il problema se causato dal motivo n. 3. Devi assicurarti di utilizzare i metodi di notifica nel modo corretto. In alternativa, utilizzare DiffUtil per gestire le modifiche in modo intelligente, semplice e fluido. Utilizzo di DiffUtil in Android RecyclerView

----------------- SOLUZIONE 4 ---------------

  • Per il motivo n. 2, è necessario controllare l'accesso a tutti i dati all'elenco dei riciclatori e assicurarsi che non vi siano modifiche su un altro thread.

questo ha funzionato nel mio scenario, non posso usare DiffUtil perché ho componenti personalizzati per riciclatori e adattatori e l'errore si verifica esattamente in scenari specifici che sono noti, ho solo bisogno di correggerlo SENZA ricorrere alla rimozione di animatori di oggetti, quindi ho appena avvolto in un tentativo e cattura
RJFares

17

Ho avuto un problema simile.

Problema nel codice di errore di seguito:

int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize - 1, messageListHistory.size() -1);

Soluzione:

int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize, messageListHistory.size() -prevSize);

Questo ha funzionato alla grande per me! Non so perché non possiamo semplicemente usare newList.size() - 1però.
waseefakhtar,

15

Secondo questo problema , il problema è stato risolto ed è stato probabilmente rilasciato qualche volta verso l'inizio del 2015. Una citazione dallo stesso thread :

È specificamente correlato alla chiamata notificationDataSetChanged. [...]

A proposito, consiglio vivamente di non utilizzare notificationDataSetChanged perché uccide animazioni e prestazioni. Anche in questo caso, l'utilizzo di eventi di notifica specifici risolverà il problema.

Se hai ancora problemi con una versione recente della libreria di supporto, ti suggerirei di rivedere le tue chiamate notifyXXX(in particolare, il tuo utilizzo notifyDataSetChanged) all'interno dell'adattatore, per assicurarti di aderire al RecyclerView.Adaptercontratto (alquanto delicato / oscuro) . Assicurati inoltre di inviare tali notifiche sul thread principale.


16
non proprio, sono d'accordo con la tua parte riguardo alle prestazioni, ma notificationDataSetChanged () non uccide le animazioni, per animare usando NotifyDataSetChanged (), a) chiama setHasStableIds (true) sull'oggetto RecyclerView.Adapter eb) ignora getItemId all'interno del tuo adattatore per restituire un unico valore lungo per ogni riga e verificalo, le animazioni funzionano
PirateApp il

@PirateApp Dovresti considerare di scrivere il tuo commento come risposta. L'ho provato e funziona benissimo.
sig. 5

Non vero! Ricevi ancora rapporti da Google Console su questo problema. E il dispositivo ovviamente è Samsung -Samsung Galaxy J3(2017) (j3y17lte), Android 8.0
user25

10

Ho avuto lo stesso problema. È stato causato perché ho ritardato la notifica dell'adattatore per l'inserimento degli elementi.

Ma ha ViewHolderprovato a ridisegnare alcuni dati nella sua vista e ha iniziato la RecyclerViewmisurazione e il conteggio dei conteggi dei bambini - in quel momento si è arrestato in modo anomalo (l'elenco degli elementi e le sue dimensioni erano già stati aggiornati, ma l'adattatore non era stato ancora notificato).


8

Questo accade quando si specifica la posizione errata per notifyItemChanged, notifyItemRangeInserted ecc. Per me:

Prima: (Erroneous)

public void addData(List<ChannelItem> list) {
  int initialSize = list.size();
  mChannelItemList.addAll(list);
  notifyItemRangeChanged(initialSize - 1, mChannelItemList.size());
 } 

Dopo: (corretto)

 public void addData(List<ChannelItem> list) {
  int initialSize = mChannelItemList.size();
  mChannelItemList.addAll(list);
  notifyItemRangeInserted(initialSize, mChannelItemList.size()-1); //Correct position 
 }

1
Perché notifyItemRangeInserted(initialSize, mChannelItemList.size()-1);e no notifyItemRangeInserted(initialSize, list.size());?
CoolMind

Comprensione passa. Hai confuso initialSizee listdimensioni. Quindi, entrambe le varianti sono sbagliate.
CoolMind,

Per me funziona notifyItemRangeInserted(initialSize, list.size()-1);ma non capisco. Perché devo ridurre la dimensione inserita di una per ItemCount?
plesso,

7

un altro motivo per cui questo problema si verifica è quando si chiamano questi metodi con indici errati (gli indici che NON sono avvenuti NON li inseriscono o li rimuovono)

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInserted

-notifyItemInserted

controlla i parametri indexe per questi metodi e assicurati che siano precisi e corretti.


2
Questo era il mio problema Si verifica un'eccezione quando non si aggiunge nulla nell'elenco.
Rasel,

6

Questo errore non è stato ancora risolto in 23.1.1, ma una soluzione alternativa comune sarebbe quella di catturare l'eccezione.


18
Cattura dove, esattamente? L'unico codice nella traccia dello stack è il codice Android nativo.
howettl

1
Cattura proprio come la risposta @saki_M.
Renan Bandeira,

Funziona davvero per te, anche se Renan? Hai provato la correzione per un po '? Il bug si verifica solo occasionalmente, quindi vedrò solo se funziona nel tempo.
Simon,

Questo in realtà funziona, ma alcuni punti di vista sui miei sono incoerentemente rimasti.
David,

@david Incoerentemente rimane qual è la conseguenza di questo?
Sreekanth Karumanaghat,

4

Questo problema è causato da RecyclerView Data modificato in thread diversi

Posso confermare il threading come un problema e da quando ho riscontrato il problema e RxJava sta diventando sempre più popolare: assicurati di utilizzare .observeOn(AndroidSchedulers.mainThread())ogni volta che chiaminotify[whatever changed]

esempio di codice dall'adattatore:

myAuxDataStructure.getChangeObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<AuxDataStructure>() {

    [...]

    @Override
    public void onNext(AuxDataStructure o) {
        [notify here]
    }
});

Sono sul thread principale mentre chiamo DiffUtil.calculateDiff (diffUtilForecastItemChangesAnlayser (this.mWeatherForecatsItemWithMainAndWeathers, weatherForecastItems)). DispatchUpdatesTo (this); il registro è chiaro onThread: Thread [main, 5, main]
Mathias Seguy Android2ee

4

Nel mio caso ogni volta che chiamo notifyItemRemoved (0), si è bloccato. Si è scoperto che ho impostato setHasStableIds(true)e getItemIdho appena restituito la posizione dell'oggetto. Ho finito per aggiornarlo per restituire l' hashCode()ID univoco o auto-definito, che ha risolto il problema.


4

Nel mio caso, stavo riscontrando questo problema a causa degli aggiornamenti dei dati dal server (sto utilizzando Firebase Firestore) e mentre il primo set di dati viene elaborato da DiffUtil in background, arriva un altro set di dati che causa un problema di concorrenza avviando un altro DiffUtil.

In breve, se stai usando DiffUtil su un thread in Background che ritorna al thread principale per inviare i risultati a RecylerView, allora hai la possibilità di ottenere questo errore quando arrivano più aggiornamenti di dati in breve tempo.

Ho risolto questo seguendo i consigli di questa meravigliosa spiegazione: https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2

Giusto per spiegare la soluzione è di spingere gli aggiornamenti mentre quello corrente è in esecuzione su un Deque. Il deque può quindi eseguire gli aggiornamenti in sospeso al termine di quello corrente, quindi gestendo tutti gli aggiornamenti successivi ma evitando anche errori di incoerenza!

Spero che questo aiuti perché questo mi ha fatto grattare la testa!


Grazie per il link!
CoolMind,

3

Il problema si è verificato per me solo quando:

Ho creato l'adattatore con un elenco vuoto . Quindi ho inserito elementi e chiamato notifyItemRangeInserted.

Soluzione:

Ho risolto il problema creando l'adapter solo dopo aver avuto il primo blocco di dati e inizializzandolo immediatamente. Il blocco successivo può quindi essere inserito e notifyItemRangeInsertedchiamato senza problemi.


Non penso sia il motivo. Ho molti adattatori con elenchi vuoti, quindi ho aggiunto elementi con notifyItemRangeInserted, ma non ho mai avuto questa eccezione.
CoolMind

3

Il mio problema era che, anche se ho cancellato entrambi l'elenco di array contenente il modello di dati per la vista del riciclatore, non ho notificato all'adattatore la modifica, quindi aveva dati obsoleti dal modello precedente. Ciò ha causato confusione sulla posizione del supporto della vista. Per risolvere questo problema, informare sempre l'adattatore che il set di dati è stato modificato prima di eseguire nuovamente l'aggiornamento.


o semplicemente notifica se l'articolo rimosso invece
Remario

il mio modello usa il riferimento del contenitore ecco perché
Remario

3

Nel mio caso stavo cambiando i dati in precedenza all'interno di un thread con mRecyclerView.post (nuovo Runnable ...) e poi in seguito ho cambiato i dati nel thread dell'interfaccia utente, il che ha causato incoerenza.


1
ho la tua stessa situazione, come l'hai risolta? grazie
baderkhane l'

2

L'errore può essere causato da modifiche non coerenti con ciò che stai notificando. Nel mio caso:

myList.set(position, newItem);
notifyItemInserted(position);

Quello che ovviamente dovevo fare:

myList.add(position, newItem);
notifyItemInserted(position);

2

Nel mio caso il problema era che ho usato notifyDataSetChanged quando la quantità di dati appena caricati era inferiore ai dati iniziali. Questo approccio mi ha aiutato:

adapter.notifyItemRangeChanged(0, newAmountOfData + 1);
adapter.notifyItemRangeRemoved(newAmountOfData + 1, previousAmountOfData);

Perché notifyDataSetChangeddipende da nuovi dati? Ho pensato che avrebbe aggiornato l'intero elenco.
CoolMind

2

Ho riscontrato lo stesso problema.

La mia app utilizza i componenti di navigazione con un frammento contenente il mio recyclerView. La mia lista è stata visualizzata bene la prima volta che il frammento è stato caricato ... ma dopo essersi allontanato e tornare indietro si è verificato questo errore.

Durante la navigazione, il ciclo di vita del frammento è passato solo su onDestroyView e al suo ritorno è iniziato su onCreateView. Tuttavia, il mio adattatore è stato inizializzato in onCreate del frammento e non è stato reinizializzato al ritorno.

La correzione era inizializzare l'adattatore in onCreateView.

Spero che questo possa aiutare qualcuno.


0

Ho ricevuto questo errore perché stavo erroneamente chiamando un metodo per rimuovere più volte una riga specifica dal mio recyclerview. Ho avuto un metodo come:

void removeFriends() {
    final int loc = data.indexOf(friendsView);
    data.remove(friendsView);
    notifyItemRemoved(loc);
}

Ho chiamato questo metodo per caso tre volte anziché una volta, quindi la seconda volta è locstata -1 e l'errore è stato dato quando ha tentato di rimuoverlo. Le due correzioni consistevano nell'assicurare che il metodo fosse chiamato solo una volta, e anche per aggiungere un controllo di integrità come questo:

void removeFriends() {
    final int loc = data.indexOf(friendsView);
    if (loc > -1) {
        data.remove(friendsView);
        notifyItemRemoved(loc);
    }
}

0

Ho avuto lo stesso problema e ho letto che questo è successo solo nei telefoni Samsung ... Ma la realtà ha dimostrato che ciò accade in molti marchi.

Dopo il test mi sono reso conto che questo accade solo quando si scorre velocemente il RecyclerView e si torna indietro con il pulsante Indietro o il pulsante Su. Quindi ho inserito il pulsante Su e ho premuto il frammento di seguito:

someList = new ArrayList<>();
mainRecyclerViewAdapter = new MainRecyclerViewAdapter(this, someList, this);
recyclerViewMain.setAdapter(mainRecyclerViewAdapter);
finish();

Con questa soluzione basta caricare un nuovo Arraylist sull'adattatore e un nuovo adattatore su recyclerView e quindi terminare l'attività.

Spero che aiuti qualcuno


0

Ho ricevuto questo errore perché stavo chiamando due volte "notifyItemInserted" per errore.


0

Nel mio caso ho avuto più di 5000 voci nell'elenco. Il mio problema era che quando si scorreva la vista del riciclatore, a volte viene chiamato "onBindViewHolder" mentre il metodo "myCustomAddItems" modifica l'elenco.

La mia soluzione era aggiungere "sincronizzato (syncObject) {}" a tutti i metodi che alterano l'elenco dei dati. In questo modo, in qualsiasi momento, solo un metodo può leggere questo elenco.


0

Nel mio caso, i dati dell'adattatore sono cambiati. E stavo usando erroneamente notificaItemInserted () per queste modifiche. Quando uso notifyItemChanged, l'errore è scomparso.


0

Mi sono imbattuto nello stesso problema quando ho rimosso elementi sia rimossi che aggiornati nell'elenco ... Dopo giorni di ricerche penso di aver finalmente trovato una soluzione.

Quello che devi fare è prima fare tutto il notifyItemChangedtuo elenco e solo allora fare tutto notifyItemRemoved in ordine decrescente

Spero che questo possa aiutare le persone che stanno riscontrando lo stesso problema ...


0

Sto usando un cursore quindi non posso usare i DiffUtils come proposto nelle risposte popolari. Per farlo funzionare, sto disabilitando le animazioni quando l'elenco non è inattivo. Questa è l'estensione che risolve questo problema:

 fun RecyclerView.executeSafely(func : () -> Unit) {
        if (scrollState != RecyclerView.SCROLL_STATE_IDLE) {
            val animator = itemAnimator
            itemAnimator = null
            func()
            itemAnimator = animator
        } else {
            func()
        }
    }

Quindi puoi aggiornare il tuo adattatore in questo modo

list.executeSafely {
  adapter.updateICursor(newCursor)
}

0

Se il problema si verifica dopo il multi-touch è possibile disabilitare il multi-touch con

android:splitMotionEvents="false" 

nel file di layout.


-1

Se i tuoi dati cambiano molto, puoi usarli

 mAdapter.notifyItemRangeChanged(0, yourData.size());

o alcuni singoli elementi nelle modifiche al set di dati che puoi utilizzare

 mAdapter.notifyItemChanged(pos);

Per un utilizzo dettagliato dei metodi, puoi fare riferimento al documento , in un certo senso, cerca di non utilizzarlo direttamente mAdapter.notifyDataSetChanged().


2
l'utilizzo notifyItemRangeChangedproduce anche lo stesso arresto anomalo.
Lionelmessi,

Va bene per qualche situazione. Forse hai aggiornato il tuo set di dati sia nel thread in background che nel thread dell'interfaccia utente, questo causerà anche un'incoerenza. Se aggiorni solo il set di dati nel thread dell'interfaccia utente, funzionerà.
Arron Cao,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.