RecyclerView: incohérence détectée. Position de l'article non valide
notre QA a détecté un bug: lors de la rotation de L'appareil Android (Droid Turbo), l'accident suivant lié à RecyclerView s'est produit:
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:3
pour moi, cela ressemble à une erreur interne dans RecyclerView, car je ne peux pas penser que ce soit causé directement par notre code...
quelqu'un A rencontré ce problème?
Quelle serait la solution?
un contournement brutal pourrait être peut-être pour attraper l'exception quand elle se produit, et recréer L'instance de RecyclverView à partir de zéro, pour éviter d'être laissé avec un État corrompu.
Mais, si possible, j'aimerais mieux comprendre le problème (et peut-être le fixer à sa source), au lieu de le masquer.
Le bug n'est pas facile à reproduire, mais il est fatal quand il arrive.
full stack-trace:
>W/dalvikvm( 7546): threadid=1: thread exiting with uncaught exception (group=0x41987d40)
>E/AndroidRuntime( 7546): FATAL EXCEPTION: main
>E/AndroidRuntime( 7546): Process: com.oblong.mezzedroid, PID: 7546
>E/AndroidRuntime( 7546): java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:3
>E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3382)
>E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3340)
>E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1810)
>E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1306)
>E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1269)
>E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:523)
>E/AndroidRuntime( 7546): at org.liboid.recycler_view.RecyclerViewContainer$LiLinearLayoutManager.onLayoutChildren(RecyclerViewContainer.java:179)
>E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1942)
>E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2237)
>E/AndroidRuntime( 7546): at org.liboid.recycler_view.LiRecyclerView.onLayout(LiRecyclerView.java:30)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
>E/AndroidRuntime( 7546): at com.oblong.mezzedroid.workspace.content.bins.BinsContainerLayout.onLayout(BinsContainerLayout.java:22)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
>E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
>E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
>E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
>E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
>E/AndroidRuntime( 7546): at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2132)
>E/AndroidRuntime( 7546): at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1872)
>E/AndroidRuntime( 7546): at andro
30 réponses
j'ai eu un problème (peut - être) lié-entrer dans un nouveau cas d'une activité avec un RecyclerView, mais avec un adaptateur plus petit a été le déclenchement de ce crash pour moi.
RecyclerView.dispatchLayout()
peut essayer de tirer des articles de la ferraille avant d'appeler mRecycler.clearOldPositions()
. La conséquence étant, c'est qu'il tirait des articles de la piscine commune qui avaient des positions plus élevées que la taille de l'adaptateur.
heureusement, il ne fait que si Prédictiveanimations sont activées, ainsi ma solution était de sous-classe GridLayoutManager (LinearLayoutManager a le même problème et' correction'), et de surcharger supportsPredictiveItemAnimations () pour retourner false:
/**
* No Predictive Animations GridLayoutManager
*/
private static class NpaGridLayoutManager extends GridLayoutManager {
/**
* Disable predictive animations. There is a bug in RecyclerView which causes views that
* are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
* adapter size has decreased since the ViewHolder was recycled.
*/
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
public NpaGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public NpaGridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
}
public NpaGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
}
}
dans mon cas (supprimer / insérer des données dans ma structure de données), j'avais besoin de vider le pool de recyclage et ensuite notifier un ensemble de données changé!
mRecyclerView.getRecycledViewPool().clear();
mAdapter.notifyDataSetChanged();
utiliser notifyDataSetChanged()
au lieu de notifyItem...
dans ce cas.
j'ai résolu cela en retardant le mRecycler.setAdapter(itemsAdapter)
jusqu'à après avoir ajouté tous les articles à l'adaptateur avec mRecycler.addAll(items)
et il a fonctionné. Aucune idée de la raison pour laquelle j'ai fait ça pour commencer, c'est à partir du code d'une bibliothèque que j'ai regardé et vu ces lignes dans le "mauvais ordre", je suis presque sûr que c'est ça cependant, s'il vous plaît si quelqu'un peut le confirmer expliquer pourquoi c'est ainsi? Vous ne savez pas si c'est une réponse valable même
j'ai eu un problème similaire mais pas exactement le même. Dans mon cas, à 1 point, je nettoyais le tableau qui a été passé au recyclerview
mObjects.clear();
et ne pas appeler notifyDataSetChanged, car je ne voulais pas que le recyclerview immédiatement Effacer les vues. Je remplissais le tableau de mObjects dans AsyncTask.
j'ai la même question .Cela s'est produit lorsque je faisais défiler fast et que j'appelais API et que je mettais à jour des données.Après avoir tout essayé pour éviter un accident , j'ai trouvé une solution.
mRecyclerView.stopScroll();
ça va marcher.
Utiliser
notifyDataSetChanged()
au lieu de
notifyItemRangeInserted(0, YourArrayList.size())
dans ce cas.
pour corriger ce problème, il suffit d'appeler notifyDataSetChanged() avec la liste vide avant de mettre à jour la vue recycler.
par exemple
//Method for refresh recycle view
if (!hcpArray.isEmpty())
hcpArray.clear();//La liste de mise à jour de recyclage vue
adapter.notifyDataSetChanged();
mon problème a disparu après que j'ai modifié mon implémentation Adapter
pour utiliser une copie du tableau items au lieu d'une référence. La méthode setItems()
est appelée chaque fois que nous avons de nouveaux éléments à afficher dans le RecyclerView
.
au lieu de:
private class MyAdapter extends RecyclerView.Adapter<ItemHolder> {
private List<MyItem> mItems;
(....)
void setItems(List<MyItem> items) {
mItems = items;
}
}
j'ai fait:
void setItems(List<MyItem> items) {
mItems = new ArrayList<>(items);
}
dans mon cas, je mettais à jour les éléments et appelais notifyDataSetChanged
dans un thread non-UI. La plupart du temps, ça marchait, mais quand beaucoup de changements se produisaient rapidement, ça s'écrasait. Quand je l'ai fait, à la place, essentiellement
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
changeData();
notifyDataSetChanged();
}
});
puis il a cessé de s'écraser.
je modifie les données pour le RecyclerView
dans l'arrière-plan Thread
. J'ai eu le même Exception
que L'opération. J'ai ajouté ceci après avoir changé les données:
myRecyclerView.post(new Runnable() {
@Override
public void run() {
myRecyclerAdapter.notifyDataSetChanged();
}
});
j'Espère que ça aide
vous avez seulement besoin d'effacer votre liste sur OnPostExecute()
et pas tout en faisant Pull to Refresh
// Setup refresh listener which triggers new data loading
swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
AsyncTask<String,Void,String> task = new get_listings();
task.execute(); // clear listing inside onPostExecute
}
});
j'ai découvert que cela se produit quand vous faites défiler pendant un tirer pour rafraîchir , puisque je dégageais la liste avant le async task
, résultant à java.lang.IndexOutOfBoundsException: Inconsistency detected.
swipeContainer.setRefreshing(false);
//TODO : This is very crucial , You need to clear before populating new items
listings.clear();
de Cette façon, vous n'aurez pas de fin à une incohérence
ce problème peut se produire lorsque vous essayez de vider votre liste, si vous allez effacer votre liste de données surtout lorsque vous utilisez pull pour rafraîchir essayer d'utiliser un drapeau booléen, initialisez-le comme false et à l'intérieur de la méthode OnRefresh faites-le vrai, effacer votre dataList si le drapeau est vrai juste avant d'ajouter les nouvelles données à elle et après que le rendre false.
votre code pourrait être comme ceci
private boolean pullToRefreshFlag = false ;
private ArrayList<your object> dataList ;
private Adapter adapter ;
public class myClass extend Fragment implements SwipeRefreshLayout.OnRefreshListener{
private void requestUpdateList() {
if (pullToRefresh) {
dataList.clear
pullToRefreshFlag = false;
}
dataList.addAll(your data);
adapter.notifyDataSetChanged;
@Override
OnRefresh() {
PullToRefreshFlag = true
reqUpdateList() ;
}
}
il peut également être lié avec le réglage de l'adaptateur plusieurs fois en même temps. J'ai eu une méthode de rappel qui a été déclenché 5-6 fois en même temps et je mettais l'adaptateur dans ce rappel donc recyclé Viewpool ne pouvait pas gérer avec toutes ces données en même temps. C'est une grosse chance, mais tu ferais mieux de vérifier quand même.
j'ai fait face à la même situation. Et il a été résolu en ajoutant des codes avant de vider votre collection.
mRecyclerView.getRecycledViewPool().clear();
j'ai rencontré un problème similaire et juste pensé à elle. J'ai codé quelques exemples pour un cas d'essai, mais je ne me suis pas assuré qu'ils me renvoyaient chacun un ID unique et qui a causé le crash ci-dessous pour moi. Réparer les IDs a résolu le problème, espérons que cela aide quelqu'un d'autre!
dans mon cas je viens de supprimer la ligne avec setHasStableIds(true);
j'ai résolu le problème, en ajoutant des éléments un par un quand il obtient de nouvelles données. J'utilise cette fonction à l'intérieur de l'adaptateur.
public void add(Data item) {
if(!params.contains(item){
params.add(item);
notifyItemInserted(getItemCount() - 1);
}
}
}
j'ai trouvé que paramètre mRecycler.setLayoutFrozen(true); dans la méthode onRefresh du swipeContainer.
a résolu le problème pour moi.
swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
orderlistRecycler.setLayoutFrozen(true);
loadData(false);
}
});
j'ai déjà eu un problème. Enfin trouvé une solution pour que
ce que je fais est de notifier adaptateur que l'article a enlevé et ensuite notifier adaptateur gamme de données changé ""
public void setData(List<Data> dataList) {
if (this.dataList.size() > 0) {
notifyItemRangeRemoved(0, dataList.size());
this.dataList.clear();
}
this.dataList.addAll(dataList)
notifyItemRangeChanged(0, dataList.size());
}
c'est un sacré bug.
pour gérer mon clic d'item, j'ai utilisé une implémentation du RecyclerView.OnItemTouchListener
similaire à la solution trouvée dans cette question .
après plusieurs rafraîchissements de la source de données RecyclerView
et en cliquant sur un élément, ce IndexOutOfBoundsException
planterait mon application. Lorsqu'un élément est cliqué, le RecyclerView
va chercher à l'interne la vue sous-jacente correcte et la rend position. En vérifiant le code source, j'ai vu qu'il y avait des Tasks
et Threads
programmés. Pour faire court, en gros, c'est juste un État illégal où deux sources de données sont mélangées et pas synchronisées et tout devient fou.
basé sur ceci, j'ai enlevé mon implémentation du RecyclerView.OnItemTouchListener
et j'ai simplement attrapé le clic sur le ViewHolder
du Adapter
moi-même:
public void onBindViewHolder (final BaseContentView holder, final int position) {
holder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick (View view) {
// do whatever you like here
}
});
}
ce n'est peut-être pas la meilleure solution, mais sans accident pour l'instant.. Espérons que cela vous fera gagner du temps :).
j'ai une fois EU l'erreur aussi:
Cause: j'essayais de mettre à jour une vue Recycler à partir D'une tâche Async tout en essayant en même temps d'obtenir de vieux view holders supprimés;
Code: je génère des données en appuyant sur un bouton, logique comme suit
- Effacer les derniers articles dans la vue du recycleur
- Appel asynchrone tâche de générer des données
- onPostExecute Update The Recycler view and Notifydatase changed
problème: chaque fois que je fais défiler rapidement avant de générer mes données je reçois
incohérence détectée. Titulaire de l'adaptateur de vue invalide Java.lang.IndexOutOfBoundsException: incohérence détectée. Article non valide position 20 (offset: 2).Etat: 3
Solution: au lieu de vider le RecyclerView avant de générer mes données, je le laisse et je le remplace par les nouvelles données, L'appel Notifydatase a changé, comme indiqué ci-dessous;
@Override
protected void onPostExecute(List<Objects> o) {
super.onPostExecute(o);
recyclerViewAdapter.setList(o);
mProgressBar.setVisibility(View.GONE);
mRecyclerView.setVisibility(View.VISIBLE);
}
Lint m'a donné un conseil concernant l'incohérence: J'ai écrit (onBindViewHolder ()):
pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doStuff(position);
}
});
qui a dû être remplacé par:
pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doStuff(pholder.getAdapterPosition());
}
});
lancez les deux codes dans votre code puis lancez Lint pour l'explication complète!!
il suffit de supprimer toutes les vues de votre layout Manager avant de notifier. comme:
myLayoutmanager.removeAllViews();
dans mon cas, j'essayais de changer le contenu de mon adaptateur sur un thread d'arrière-plan mais appelé notifier* sur le thread main/ui.
C'est impossible! la raison pour laquelle notify est forcé au thread principal est que le recyclerview veut que vous éditiez votre adaptateur de support sur le thread principal, même sur la même pile d'appels.
Pour résoudre le problème, assurez-vous que chaque opération de votre adaptateur comme chaque notification... appel est fait sur le ui/thread principal !
j'ai eu le même problème avec recyclerView J'ai donc informé l'adaptateur du changement de jeu de données juste après que la liste ait été effacée.
mList.clear();
mAdapter.notifyDataSetChanged();
mList.addAll(newData);
mAdapter.notifyDataSetChanged();
add_location.removeAllViews ();
for (int i=0;i<arrayList.size();i++)
{
add_location.addView(new HolderDropoff(AddDropOffActivtity.this,add_location,arrayList,AddDropOffActivtity.this,this));
}
add_location.getAdapter().notifyDataSetChanged();
désolé pour la solution tardive mais parfaite: lorsque vous essayez de supprimer un élément spécifique appelez simplement notifydatasetchange() et obtenir ces élément dans bindviewholder et supprimer cet élément et ajouter de nouveau pour le dernier de la liste, puis chek liste si c'est le dernier indice puis supprimer l'élément. fondamentalement, le problème est venu quand vous essayez de supprimer l'élément du centre. si vous retirez l'article du dernier index alors il n'y a plus de recyclage et aussi votre compte adpter sont mantine (c'est crash point critique venir ici) et l'incident est résolu, l'extrait de code ci-dessous .
holder.itemLayout.setVisibility( View.GONE );//to hide temprory it show like you have removed item
Model current = list.get( position );
list.remove( current );
list.add( list.size(), current );//add agine to last index
if(position==list.size()-1){// remove from last index
list.remove( position );
}
j'ai rencontré cette trace de pile désagréable avec les nouveaux composants D'Architecture Android récemment. Essentiellement, j'ai une liste d'éléments dans mon modèle de vue qui sont observés par mon Fragment, en utilisant LiveData. Lorsque le ViewModel affiche une nouvelle valeur pour les données, le Fragment met à jour l'adaptateur, en passant ces nouveaux éléments de données et en informant l'adaptateur qu'il y a eu des changements.
malheureusement, en passant dans les nouveaux éléments de données à l'adaptateur, Je n'ai pas tenu compte pour le fait que le ViewModel et l'Adaptateur serait pointant vers la même référence d'objet! Ce qui veut dire que si je mets à jour les données et que j'appelle postValue()
depuis le modèle de vue, il y a une très petite fenêtre où les données peuvent être mises à jour et l'adaptateur n'est pas encore averti!
Ma solution était d'installer une nouvelle copie des éléments transmis à la carte:
mList = new ArrayList<>(passedList);
avec ce super facile Fixer vous pouvez être assurez-vous que vos données d'adaptateur ne changeront pas jusqu'à ce que juste avant que votre adaptateur soit notifié.
C'est la seule solution qui a fonctionné pour moi, même en essayant beaucoup de solutions d'en haut.
1.) Utilisation
CustomAdapter scrollStockAdapter = new CustomAdapter(mActivity, new ArrayList<StockListModel>());
list.setAdapter(scrollStockAdapter);
scrollStockAdapter.updateList(stockListModels);
2.) Écrire cette méthode dans l'adaptateur
public void updateList(List<StockListModel> list) {
stockListModels.clear();
stockListModels.addAll(list);
notifyDataSetChanged();
}
stockListModels - > cette liste est que vous utilisez dans Adaptateur .