Notre QA a détecté un bogue: lors de la rotation du périphérique Android (Droid Turbo), les événements suivants accident lié à RecyclerView se sont produits:
Java.lang.IndexOutOfBoundsException: incohérence détectée. Position non valide de l'article 2 (décalage: 2) .state: 3
Pour moi, cela ressemble à une erreur interne dans RecyclerView, car je ne vois pas comment cela pourrait être directement causé par notre code ...
Est-ce que quelqu'un à rencontré ce problème?
Quelle serait la solution?
Une solution de contournement brutale pourrait consister peut-être à intercepter l'exception lorsqu'elle se produit et à recréer l'instance RecyclverView à partir de zéro, pour éviter de se retrouver avec un état corrompu.
Mais, si possible, j'aimerais mieux comprendre le problème (et peut-être le régler à la source) au lieu de le masquer.
Le bug n'est pas facile à reproduire, mais il est fatal quand cela se produit.
La trace de pile complète:
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
J'ai eu un problème (éventuellement) lié - entrer dans une nouvelle instance d'une activité avec un RecyclerView, mais avec un adaptateur plus petit provoquait ce blocage pour moi.
RecyclerView.dispatchLayout()
peut essayer d'extraire des objets de la chute avant d'appeler mRecycler.clearOldPositions()
. La conséquence en est que les éléments du pool commun étaient extraits et que leurs positions étaient supérieures à la taille de l'adaptateur.
Heureusement, cela n’est possible que si PredictiveAnimations
est activé. Ma solution consistait donc à sous-classer GridLayoutManager
(LinearLayoutManager
a le même problème et à "corriger") et à remplacer supportsPredictiveItemAnimations()
par retourne faux:
/**
* 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), je devais vider le pool de recyclage, puis notifier le changement de fichier!
mRecyclerView.getRecycledViewPool().clear(); mAdapter.notifyDataSetChanged();
Utilisez notifyDataSetChanged()
à la place notifyItem...
dans ce cas.
J'ai résolu ce problème en retardant la mRecycler.setAdapter(itemsAdapter)
jusqu'à l'ajout de tous les éléments à l'adaptateur avec mRecycler.addAll(items)
et tout fonctionnait. Aucune idée de la raison pour laquelle j'ai fait cela au départ, c'est à partir du code d'une bibliothèque que j'ai regardé et que j'ai vu ces lignes dans le "mauvais ordre", je suis à peu près sûr alors? Pas sûr que ce soit une réponse valable même
J'ai eu un problème similaire mais pas exactement le même. Dans mon cas, à un moment donné, je nettoyais le tableau qui avait été transmis à Recyclerview.
mObjects.clear();
et ne pas appeler notifyDataSetChanged, car je ne voulais pas que recyclerview efface immédiatement les vues. Je remplissais le tableau mObjects en AsyncTask.
J'ai le même problème. Il s'est produit lorsque je faisais défiler rapidement et appeler l'API et mettre à jour les données. Après avoir tout essayé pour empêcher le crash, j'ai trouvé la solution.
mRecyclerView.stopScroll();
Ça va marcher.
Cette erreur se produit lorsque la liste de l’adaptateur est effacée lorsque le défilement de l’utilisateur modifie la position du détenteur de l’article, la référence perdue entre la liste et l’élément sur ui, une erreur se produisant dans la prochaine "notifyDataSetChanged"request.
Réparer:
Examinez votre méthode de liste de mise à jour. Si vous faites quelque chose comme
mainList.clear();
...
mainList.add() or mainList.addAll()
...
notifyDataSetChanged();
===> Error occur
Comment réparer. Créez un nouvel objet de liste pour le traitement de la mémoire tampon et réattribuez-le ensuite à la liste principale.
List res = new ArrayList();
…..
res.add(); //add item or modify list
….
mainList = res;
notifyDataSetChanged();
Merci à Nhan Cao pour cette aide précieuse :)
J'ai eu le même problème avec recyclerView. Je viens donc d'avertir l'adaptateur de la modification de l'ensemble de données juste après la suppression de la liste.
mList.clear();
mAdapter.notifyDataSetChanged();
mList.addAll(newData);
mAdapter.notifyDataSetChanged();
Mon problème est parti après avoir modifié mon implémentation Adapter
afin d'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 la section 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);
}
Pour résoudre ce problème, appelez notifyDataSetChanged () avec une liste vide avant de mettre à jour la vue de recyclage.
Par exemple
//Method for refresh recycle view
if (!hcpArray.isEmpty())
hcpArray.clear (); // Liste de la vue de recyclage de la mise à jour
adapter.notifyDataSetChanged();
Utilisation
notifyDataSetChanged()
au lieu
notifyItemRangeInserted(0, YourArrayList.size())
dans ce cas.
Je modifie les données pour le RecyclerView
en arrière-plan Thread
. J'ai le même Exception
que l'OP. 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
J'ai récemment rencontré cette trace de pile désagréable avec les nouveaux Android Architecture Components. Pour l'essentiel, j'ai une liste d'éléments dans mon ViewModel qui sont observés par mon fragment, à l'aide de LiveData. Lorsque le ViewModel publie une nouvelle valeur pour les données, le fragment met à jour l'adaptateur, en transmettant ces nouveaux éléments de données et en informant l'adaptateur que des modifications ont été apportées.
Malheureusement, lors de la transmission des nouveaux éléments de données à l'adaptateur, je n'ai pas réussi à prendre en compte le fait que ViewModel et l'adaptateur pointaient tous deux vers la même référence d'objet! Cela signifie que si je mets à jour les données et appelle postValue()
à partir de ViewModel, il existe une très petite fenêtre dans laquelle les données peuvent être mises à jour et l'adaptateur pas encore notifié!
Mon correctif consistait à instancier une nouvelle copie des éléments lorsqu'ils étaient transmis à l'adaptateur:
mList = new ArrayList<>(passedList);
Avec cette solution très simple, vous pouvez être assuré que les données de votre adaptateur ne seront pas modifiées tant que votre adaptateur n’aura pas été notifié.
Dans mon cas, je mettais à jour les éléments et appelais notifyDataSetChanged
dans un thread non-UI. La plupart du temps, cela fonctionnait, mais lorsque beaucoup de changements se produisaient rapidement, cela se bloquait. Quand j'ai fait, au lieu de cela, essentiellement
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
changeData();
notifyDataSetChanged();
}
});
alors il a cessé de s'écraser.
Vous devez seulement effacer votre liste sur OnPostExecute()
et pas 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 produisait lorsque vous faisiez défiler tirez pour rafraîchir, puisque je nettoyais la liste avant le async task
, ce qui donne 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 ne finirez pas par une incohérence
J'ai fait face à la même situation. Et cela a été résolu en ajoutant des codes avant d'effacer votre collection.
mRecyclerView.getRecycledViewPool().clear();
ce problème peut se produire lorsque vous essayez d'effacer votre liste. Si vous souhaitez effacer votre liste de données, en particulier lorsque vous utilisez l'extraction pour actualiser, essayez d'utiliser un indicateur booléen, initialisez-le à false et à la méthode OnRefresh, effacez votre liste de données. si flag est true juste avant d’ajouter les nouvelles données et ensuite de le rendre faux.
votre code pourrait être comme ça
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() ;
}
}
Je suis tombé sur un problème similaire et je l'ai compris. J'ai codé en dur quelques exemples pour un scénario de test, mais je ne me suis pas assuré qu'ils renvoyaient chacun un ID unique, ce qui a provoqué le blocage ci-dessous pour moi. La correction des identifiants a résolu le problème, espérons que cela aidera quelqu'un d'autre!
Dans mon cas, je viens de supprimer la ligne avec setHasStableIds(true);
Dans mon cas, j'essayais de changer le contenu de mon adaptateur sur un thread d'arrière-plan mais appelé notify * sur le thread principal/ui.
Ce n'est pas possible! La raison pour laquelle notify est forcée de se connecter au thread principal, c'est que recyclerview veut vous permettre de modifier 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 sur votre adaptateur, ainsi que chaque appel de notification ... est effectué sur le thread principal/ui!
Cela peut également être lié au paramétrage de l'adaptateur plusieurs fois en même temps. J'avais une méthode de rappel déclenchée 5 à 6 fois en même temps et je configurais l'adaptateur dans ce rappel afin que RecycledViewPool ne puisse pas gérer simultanément toutes ces données. C'est une grosse chance mais vous feriez mieux de vérifier quand même.
Supprimez simplement toutes les vues de votre gestionnaire de disposition avant de notifier. comme:
myLayoutmanager.removeAllViews();
C'est un bug assez méchant.
Pour manipuler mon élément, j'ai utilisé une implémentation du RecyclerView.OnItemTouchListener
similaire à la solution trouvée dans cette question .
Après de nombreuses mises à jour de la source de données de RecyclerView
et un clic sur un élément, cette IndexOutOfBoundsException
plantait mon application. Quand un élément est cliqué, la RecyclerView
cherche en interne la vue sous-jacente correcte et la restitue. En vérifiant le code source, j'ai vu qu'il y avait quelques Tasks
et Threads
planifiés. Pour résumer, il s’agit en gros d’un état illégal dans lequel deux sources de données sont mélangées et non synchronisées et le tout se déchaîne.
Sur cette base, j'ai retiré mon implémentation du RecyclerView.OnItemTouchListener
et pris simplement le clic sur la 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 un système sans crash pour le moment .. Espérons que cela vous fera gagner du temps :).
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();
Lint m'a donné un conseil concernant les incohérences: j'ai écrit (onBindViewHolder ()):
pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doStuff(position);
}
});
qui devait être remplacé par:
pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doStuff(pholder.getAdapterPosition());
}
});
Exécutez les deux codes dans votre code puis exécutez Lint pour une explication complète !!
j'ai aussi eu une fois l'erreur:
Cause: J'essayais de mettre à jour une vue Recycler à partir d'une tâche asynchrone tout en essayant d'obtenir d'anciens viewHolders supprimés;
Code: Je génère des données en appuyant sur un bouton, la logique est la suivante
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. Adaptateur de titulaire de vue non valide positionViewHolder Java.lang.IndexOutOfBoundsException: incohérence détectée. Position non valide de l'article 20 (décalage: 2) .state: 3
Solution: Au lieu d'effacer le RecyclerView avant de générer mes données, je les laisse plutôt, puis les remplace par les nouvelles données, l'appel NotifyDatasetChanged, 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);
}
Pour moi, cela a fonctionné après avoir ajouté cette ligne de code:
mRecyclerView.setItemAnimator(null);
Le problème est survenu sur moi alors que je ne l'avais pas reconnu, j'ai appelé deux fois avec des threads différents simultanément.
notifyDataSetChanged
un de la fonction de chargement sqlite un après la fonction d'appel
J'ai résolu le problème, en ajoutant des éléments un par un lorsqu'il reçoit 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);
}
}
}
Désolé, c'est une solution tardive mais parfaite: lorsque vous essayez de supprimer un élément spécifique, appelez simplement notifydatasetchange () et obtenez-le dans bindviewholder. article. En gros, le problème vient lorsque vous essayez de supprimer un élément du centre. si vous supprimez un élément du dernier index, il n'y a plus de recyclage et votre nombre d'adresses est inchangé (il s'agit d'un crash de point critique, venez ici) et le crash est résolu dans 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 eu un même problème précédemment. Enfin trouvé une solution de contournement pour cela
Ce que je fais est de notifier à l'adaptateur que l'élément a été supprimé, puis d'informer l'adaptateur de la plage de données modifiée
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 la seule solution qui a fonctionné pour moi, même en essayant plusieurs des solutions ci-dessus.
1.) Intilisation
CustomAdapter scrollStockAdapter = new CustomAdapter(mActivity, new ArrayList<StockListModel>());
list.setAdapter(scrollStockAdapter);
scrollStockAdapter.updateList(stockListModels);
2.) Écrivez cette méthode dans l'adaptateur
public void updateList(List<StockListModel> list) {
stockListModels.clear();
stockListModels.addAll(list);
notifyDataSetChanged();
}
stockListModels -> cette liste est celle que vous utilisez dans l'adaptateur.
Réponse très tardive, mais cela peut aider quelqu'un sur la fonctionnalité.
Assurez-vous que vos méthodes onStop ou onPause n'effacent aucune de vos listes.
J'ai trouvé ce paramètre mRecycler.setLayoutFrozen (true); dans la méthode onRefresh du swipeContainer.
résolu le problème pour moi.
swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
orderlistRecycler.setLayoutFrozen(true);
loadData(false);
}
});