Ce que je veux faire: lance un thread d'arrière-plan qui calcule le contenu de ListView et met à jour ListView partiellement, tandis que les résultats sont calculés.
Ce que je sais que je dois éviter: je ne peux pas manipuler le contenu de ListAdapter du thread d'arrière-plan, j'ai donc hérité de AsyncTask et publié le résultat (ajouter des entrées à l'adaptateur) de onProgressUpdate. Mon adaptateur utilise ArrayList des résultats, toutes les opérations effectuées sur ces listes sont synchronisées.
Recherche d’autres personnes: il existe des données très précieuses ici . J'ai également souffert d'accidents quasi quotidiens pour un groupe d'environ 500 utilisateurs. Lorsque j'ai ajouté le bloc list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)
dans onProgressUpdate, les accidents ont été multipliés par 10 mais n'ont pas disparu. (il a été suggéré dans réponse )
Ce que j'ai parfois: remarquez que cela arrive très rarement (une fois par semaine pour un utilisateur de 3.5k). Mais j'aimerais bien éliminer ce bug. Voici un stacktrace partiel:
`Java.lang.IllegalStateException:` The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131296334, class Android.widget.ListView) with Adapter(class com.transportoid.Tracks.TrackListAdapter)]
at Android.widget.ListView.layoutChildren(ListView.Java:1432)
at Android.widget.AbsListView.onTouchEvent(AbsListView.Java:2062)
at Android.widget.ListView.onTouchEvent(ListView.Java:3234)
at Android.view.View.dispatchTouchEvent(View.Java:3709)
at Android.view.ViewGroup.dispatchTouchEvent(ViewGroup.Java:852)
at Android.view.ViewGroup.dispatchTouchEvent(ViewGroup.Java:884)
at Android.view.ViewGroup.dispatchTouchEvent(ViewGroup.Java:884)
at Android.view.ViewGroup.dispatchTouchEvent(ViewGroup.Java:884)
[...]
Aidez-moi? Plus nécessaire, voir ci-dessous
FINAL REPONSE: En fin de compte, j’appelais notifyDataSetChanged
toutes les 5 insertions pour éviter les scintillements et les modifications soudaines de la liste. Cela ne peut pas être fait de la sorte, informez toujours l'adaptateur lorsque la liste de base change. Ce bug est parti depuis longtemps pour moi maintenant.
J'ai eu le même problème.
J'ajoutais des éléments à ma ArrayList
en dehors du fil de l'interface utilisateur.
Solution: J'ai fait les deux, adding the items
et appelé notifyDataSetChanged()
dans le fil de l'interface utilisateur.
J'ai eu le même problème, mais je l'ai corrigé en utilisant la méthode
requestLayout();
de la classe ListView
Ceci est un MultiThreading Problème et utilisation correcte Synchronisé Blocs Ceci peut être empêché .Sans ajouter des éléments supplémentaires sur le thread de l'interface utilisateur et causer une perte de réactivité de l'application.
J'ai aussi fait face au même. Et comme la réponse la plus acceptée suggère de modifier les données de l'adaptateur à partir de l'interface utilisateur Thread peut résoudre le problème. Cela fonctionnera, mais c'est une solution rapide et facile, mais pas la meilleure.
Comme vous pouvez le voir pour un cas normal. La mise à jour de l'adaptateur de données à partir du thread en arrière-plan et l'appel de notifyDataSetChanged dans le thread de l'interface utilisateur fonctionnent.
Cette illégaleStateException se produit lorsqu'un thread d'interface utilisateur met à jour la vue et qu'un autre thread d'arrière-plan modifie à nouveau les données. Ce moment provoque ce problème.
Donc, si vous voulez synchroniser tout le code qui modifie les données de l'adaptateur et passe un appel notifydatasetchange. Ce problème devrait avoir disparu. Comme parti pour moi et je suis toujours en train de mettre à jour les données du fil de fond.
Voici le code spécifique à mon cas pour que les autres se réfèrent.
Mon chargeur sur l'écran principal charge les contacts de l'annuaire dans mes sources de données en arrière-plan.
@Override
public Void loadInBackground() {
Log.v(TAG, "Init loadings contacts");
synchronized (SingleTonProvider.getInstance()) {
PhoneBookManager.preparePhoneBookContacts(getContext());
}
}
Ce PhoneBookManager.getPhoneBookContacts lit le contact du répertoire et le remplit dans le hashmaps. Ce qui est directement utilisable par les adaptateurs de liste pour dessiner des listes.
Il y a un bouton sur mon écran. Cela ouvre une activité où ces numéros de téléphone sont listés . Si je place directement l'adaptateur sur la liste avant que le thread précédent ne finisse son travail, la casse de navigation rapide est moins fréquente. Il affiche l'exception .Quel est le titre de cette question SO. Donc, je dois faire quelque chose comme ça dans la deuxième activité.
Mon chargeur dans la deuxième activité attend la fin du premier thread. Jusqu'à ce qu'il montre une barre de progression. Vérifiez le loadInBackground des deux chargeurs.
Ensuite, il crée l’adaptateur et le transmet à l’activité où, sur le fil ui, j’appelle setAdapter.
Cela a résolu mon problème.
Ce code est un extrait uniquement. Vous devez le changer pour bien compiler pour vous.
@Override
public Loader<PhoneBookContactAdapter> onCreateLoader(int arg0, Bundle arg1) {
return new PhoneBookContactLoader(this);
}
@Override
public void onLoadFinished(Loader<PhoneBookContactAdapter> arg0, PhoneBookContactAdapter arg1) {
contactList.setAdapter(adapter = arg1);
}
/*
* AsyncLoader to load phonebook and notify the list once done.
*/
private static class PhoneBookContactLoader extends AsyncTaskLoader<PhoneBookContactAdapter> {
private PhoneBookContactAdapter adapter;
public PhoneBookContactLoader(Context context) {
super(context);
}
@Override
public PhoneBookContactAdapter loadInBackground() {
synchronized (SingleTonProvider.getInstance()) {
return adapter = new PhoneBookContactAdapter(getContext());
}
}
}
J'espère que cela t'aides
J'ai résolu ceci en ayant 2 listes. Une liste que je n'utilise que pour l'adaptateur et je fais toutes les modifications/mises à jour de données sur l'autre liste. Cela me permet d’effectuer des mises à jour sur une liste d’un fil d’arrière-plan, puis de mettre à jour la liste "adaptateur" dans le fil principal/UI:
List<> data = new ArrayList<>();
List<> adapterData = new ArrayList();
...
adapter = new Adapter(adapterData);
listView.setAdapter(adapter);
// Whenever data needs to be updated, it can be done in a separate thread
void updateDataAsync()
{
new Thread(new Runnable()
{
@Override
public void run()
{
// Make updates the "data" list.
...
// Update your adapter.
refreshList();
}
}).start();
}
void refreshList()
{
runOnUiThread(new Runnable()
{
@Override
public void run()
{
adapterData.clear();
adapterData.addAll(data);
adapter.notifyDataSetChanged();
listView.invalidateViews();
}
});
}
J'ai écrit ce code et je l'ai fait fonctionner dans une image d'émulateur 2.1 pendant environ 12 heures. Je vais donner au framework Android le bénéfice du doute sur celui-ci et dire que c'est probablement une erreur dans votre code. J'espère que ça aide. Peut-être pourrez-vous l’adapter à votre liste et à vos données.
public class ListViewStressTest extends ListActivity {
ArrayAdapter<String> adapter;
ListView list;
AsyncTask<Void, String, Void> task;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.adapter = new ArrayAdapter<String>(this, Android.R.layout.simple_list_item_1);
this.list = this.getListView();
this.list.setAdapter(this.adapter);
this.task = new AsyncTask<Void, String, Void>() {
Random r = new Random();
int[] delete;
volatile boolean scroll = false;
@Override
protected void onProgressUpdate(String... values) {
if(scroll) {
scroll = false;
doScroll();
return;
}
if(values == null) {
doDelete();
return;
}
doUpdate(values);
if(ListViewStressTest.this.adapter.getCount() > 5000) {
ListViewStressTest.this.adapter.clear();
}
}
private void doScroll() {
if(ListViewStressTest.this.adapter.getCount() == 0) {
return;
}
int n = r.nextInt(ListViewStressTest.this.adapter.getCount());
ListViewStressTest.this.list.setSelection(n);
}
private void doDelete() {
int[] d;
synchronized(this) {
d = this.delete;
}
if(d == null) {
return;
}
for(int i = 0 ; i < d.length ; i++) {
int index = d[i];
if(index >= 0 && index < ListViewStressTest.this.adapter.getCount()) {
ListViewStressTest.this.adapter.remove(ListViewStressTest.this.adapter.getItem(index));
}
}
}
private void doUpdate(String... values) {
for(int i = 0 ; i < values.length ; i++) {
ListViewStressTest.this.adapter.add(values[i]);
}
}
private void updateList() {
int number = r.nextInt(30) + 1;
String[] strings = new String[number];
for(int i = 0 ; i < number ; i++) {
strings[i] = Long.toString(r.nextLong());
}
this.publishProgress(strings);
}
private void deleteFromList() {
int number = r.nextInt(20) + 1;
int[] toDelete = new int[number];
for(int i = 0 ; i < number ; i++) {
int num = ListViewStressTest.this.adapter.getCount();
if(num < 2) {
break;
}
toDelete[i] = r.nextInt(num);
}
synchronized(this) {
this.delete = toDelete;
}
this.publishProgress(null);
}
private void scrollSomewhere() {
this.scroll = true;
this.publishProgress(null);
}
@Override
protected Void doInBackground(Void... params) {
while(true) {
int what = r.nextInt(3);
switch(what) {
case 0:
updateList();
break;
case 1:
deleteFromList();
break;
case 2:
scrollSomewhere();
break;
}
try {
Thread.sleep(0);
} catch(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
this.task.execute(null);
}
}
Il y a plusieurs jours, j'ai rencontré le même problème et provoqué plusieurs milliers d'accidents par jour, soit environ 0,1% des utilisateurs rencontrés. J'ai essayé setVisibility(GONE/VISIBLE)
et requestLayout()
, mais le nombre d'accidents diminue seulement un peu.
Et je l'ai finalement résolu. Rien avec setVisibility(GONE/VISIBLE)
. Rien avec requestLayout()
.
Enfin, j’ai trouvé que c’est parce que j’ai utilisé une Handler
pour appeler notifyDataSetChanged()
après la mise à jour des données, ce qui peut donner lieu à une sorte de:
checkForTap()
/onTouchEvent()
et appelle finalement layoutChildren()
)notifyDataSetChanged()
et met à jour les vues.Et j’ai fait une autre erreur en indiquant que, dans getCount()
, getItem()
et getView()
, j’utilise directement les champs de DataSource plutôt que de les copier sur l’adaptateur. Donc, finalement, il se bloque lorsque:
getCount()
et getView()
sont appelés, et listview trouve que les données ne sont pas cohérentes et lève des exceptions telles que Java.lang.IllegalStateException: The content of the adapter has changed but...
. Une autre exception courante est IndexOutOfBoundException
si vous utilisez en-tête/pied de page dans ListView
.La solution est donc simple, je copie simplement les données sur l’adaptateur de mon source de données lorsque mon gestionnaire déclenche l’adaptateur pour obtenir les données et appelle notifyDataSetChanged()
. Le crash ne se reproduit plus jamais.
Mon problème était lié à l'utilisation d'un Filter avec ListView.
Lors de la définition ou de la mise à jour du modèle de données sous-jacent du ListView, je faisais quelque chose comme ceci:
public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
this.allContacts = newContacts;
this.filteredContacts = newContacts;
getFilter().filter(filter);
}
Si vous appelez filter()
dans la dernière ligne, notifyDataSetChanged()
sera appelé (et doit obligatoirement) par la méthode publishResults()
du filtre. Cela peut fonctionner parfois, surtout dans mon rapide Nexus 5. Mais, en réalité, il cache un bug que vous remarquerez avec des périphériques plus lents ou dans des conditions nécessitant de nombreuses ressources.
Le problème est que le filtrage est effectué de manière asynchrone, donc entre la fin de l'instruction filter()
et l'appel à publishResults()
, tous deux dans le thread d'interface utilisateur, un autre code de thread d'interface utilisateur peut être exécuté et modifier le contenu de l'adaptateur.
Le correctif est facile, appelez simplement notifyDataSetChanged()
également avant de demander que le filtrage soit effectué:
public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
this.allContacts = newContacts;
this.filteredContacts = newContacts;
notifyDataSetChanged(); // Fix
getFilter().filter(filter);
}
J'ai un objet List if Feed . Il est ajouté et tronqué à partir du thread none-UI . Il fonctionne correctement avec l'adaptateur ci-dessous . J'appelle FeedAdapter.notifyDataSetChanged
dans le thread UI de toute façon mais un peu plus tard . I faire comme cela parce que mes objets Feed restent en mémoire dans le service local même lorsque l'interface utilisateur est morte.
public class FeedAdapter extends BaseAdapter {
private int size = 0;
private final List<Feed> objects;
public FeedAdapter(Activity context, List<Feed> objects) {
this.context = context;
this.objects = objects;
size = objects.size();
}
public View getView(int position, View convertView, ViewGroup parent) {
...
}
@Override
public void notifyDataSetChanged() {
size = objects.size();
super.notifyDataSetChanged();
}
@Override
public int getCount() {
return size;
}
@Override
public Object getItem(int position) {
try {
return objects.get(position);
} catch (Error e) {
return Feed.emptyFeed;
}
}
@Override
public long getItemId(int position) {
return position;
}
}
Si cela se produisait par intermittence, ce problème ne se produisait que lorsque la liste était défilée après le clic sur le dernier élément "charger plus". Si la liste n'a pas défilé, tout a bien fonctionné.
Après BEAUCOUP de débogage, c’était un bug de ma part, mais aussi une incohérence dans le code Android.
Lorsque la validation est effectuée, ce code est exécuté dans ListView.
} else if (mItemCount != mAdapter.getCount()) {
throw new IllegalStateException("The content of the adapter has changed but "
+ "ListView did not receive a notification. Make sure the content of "
Mais quand onChange se produit, il déclenche ce code dans AdapterView (parent de ListView)
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
Notez que l'adaptateur n'est PAS garanti identique!
Dans mon cas, puisqu'il s'agissait d'un 'LoadMoreAdapter', je renvoyais le WrappedAdapter dans l'appel getAdapter (pour accéder aux objets sous-jacents). Cela a eu pour résultat que les comptes sont différents en raison de l'élément supplémentaire "Charger plus" et de l'exception levée.
Je ne l'ai fait que parce que les docs donnent l'impression que ça va aller
ListView.getAdapter javadoc
Renvoie l'adaptateur actuellement utilisé dans cette liste. Le rendu adaptateur peut ne pas être le même adaptateur transmis à setAdapter (ListAdapter) mais peut être un WrapperListAdapter.
Ceci est un bogue connu sous Android 4 à 4.4 (KitKat) et est résolu dans "> 4.4"
Voir ici: https://code.google.com/p/Android/issues/detail?id=71936
Même si j'ai rencontré le même problème dans mon application de notification XMPP, le message du destinataire doit être ajouté à la liste (implémenté avec ArrayList
). Lorsque j'ai essayé d'ajouter le contenu du destinataire via MessageListener
(thread distinct), l'application se ferme avec l'erreur ci-dessus. J'ai résolu ce problème en ajoutant le contenu à ma méthode arraylist
& setListviewadapater
à runOnUiThread
qui fait partie de la classe d'activité. Cela a résolu mon problème.
J'étais confronté au même problème avec exactement le même journal d'erreur . Dans mon cas, onProgress()
de l'AsyncTask ajoute les valeurs à l'adaptateur à l'aide de mAdapter.add(newEntry)
. Pour éviter que l'interface utilisateur ne devienne moins réactive, définissez mAdapter.setNotifyOnChange(false)
et appelez mAdapter.notifyDataSetChanged()
4 fois par seconde. Une fois par seconde, le tableau est trié.
Cela fonctionne bien et a l'air très addictif, mais malheureusement, il est possible de le bloquer en touchant assez souvent les éléments de la liste affichée.
Mais il semble que j'ai trouvé une solution de contournement acceptable. Je suppose que même si vous travaillez uniquement sur le thread ui, l'adaptateur n'accepte pas beaucoup de modifications de ses données sans appeler notifyDataSetChanged()
, à cause de cela, j'ai créé une file d'attente qui stocke tous les nouveaux éléments jusqu'à la 300 ms mentionnée. sont finis. Si ce moment est atteint, j'ajoute tous les éléments stockés d'un coup et appelle notifyDataSetChanged()
. Jusqu'à présent, je ne pouvais plus écraser la liste .
J'ai rencontré un problème similaire, voici comment je l'ai résolu dans mon cas. Je vérifie si la task
est déjà RUNNING
ou FINISHED
car une tâche ne peut s'exécuter qu'une seule fois. Ci-dessous, vous verrez un code partiel et adapté de ma solution.
public class MyActivity... {
private MyTask task;
@Override
protected void onCreate(Bundle savedInstanceState) {
// your code
task = new MyTask();
setList();
}
private void setList() {
if (task != null)
if (task.getStatus().equals(AsyncTask.Status.RUNNING)){
task.cancel(true);
task = new MyTask();
task.execute();
} else if (task.getStatus().equals(AsyncTask.Status.FINISHED)) {
task = new MyTask();
task.execute();
} else
task.execute();
}
class MyTask extends AsyncTask<Void, Item, Void>{
List<Item> Itens;
@Override
protected void onPreExecute() {
//your code
list.setVisibility(View.GONE);
adapterItem= new MyListAdapter(MyActivity.this, R.layout.item, new ArrayList<Item>());
list.setAdapter(adapterItem);
adapterItem.notifyDataSetChanged();
}
@Override
protected Void doInBackground(Void... params) {
Itens = getItens();
for (Item item : Itens) {
publishProgress(item );
}
return null;
}
@Override
protected void onProgressUpdate(Item ... item ) {
adapterItem.add(item[0]);
}
@Override
protected void onPostExecute(Void result) {
//your code
adapterItem.notifyDataSetChanged();
list.setVisibility(View.VISIBLE);
}
}
}
Dans mon cas, j'ai appelé la méthode GetFilter()
sur un adaptateur de la méthode TextWatcher()
de l'activité principale, et j'ai ajouté les données avec une boucle For sur GetFilter()
. La solution a été de changer la méthode For en AfterTextChanged()
de la méthode principale et de supprimer l'appel à GetFilter()
Une cause de cet incident est que l'objet ArrayList
ne peut pas changer complètement . Ainsi, lorsque je supprime un élément, je dois procéder comme suit:
mList.clear();
mList.addAll(newDataList);
Cela a corrigé le crash pour moi.
J'ai eu le même problème et je l'ai résolu. Mon problème était que j'utilisais une listview
, avec un adaptateur de tableau et avec un filtre. Sur la méthode performFiltering
, je déconnais le tableau contenant les données et le problème était lié au fait que cette méthode ne s'exécutait pas sur le thread d'interface utilisateur et ÉVENTUELLEMENT, cela posait quelques problèmes.
Veuillez essayer l'une de ces solutions:
Parfois, si vous ajoutez un nouvel objet à la liste de données dans un thread (ou une méthode doInBackground
), cette erreur se produit. La solution est la suivante: créer une liste temporaire et ajouter des données à cette liste dans le fil (ou doInBackground
), puis copier toutes les données de la liste temporaire dans la liste de l'adaptateur dans le fil de l'interface utilisateur (ou onPostExcute
)
Assurez-vous que toutes les mises à jour de l'interface utilisateur sont appelées dans le fil de l'interface utilisateur.
J'ai eu la même situation, beaucoup de groupes de membres ont été insérés dans mon article sur listview et je changeais certaines valeurs booléennes à l'intérieur de mon article, comme le titulaire, ...
mon problème est survenu parce que j'appelais une méthode dans getView (); et était en train de sauver un objet dans sharepreference, alors j'ai eu la même erreur ci-dessus
Comment je l'ai résolu J'ai supprimé ma méthode à l'intérieur de getView () pour notifyDataSetInvalidated () et le problème a disparu
@Override
public void notifyDataSetChanged() {
saveCurrentTalebeOnShare(currentTalebe);
super.notifyDataSetChanged();
}
j'ai eu le même problème. enfin j'ai la solution
avant de mettre à jour la liste, si le clavier est présent, fermez-le d'abord. Après cela, définissez la source de données et appelez notifydatasetchanged ().
lors de la fermeture interne du clavier, listview mettra à jour son interface utilisateur. continue d’appeler jusqu’à la fermeture du clavier. cette fois si la source de données change, elle lève cette exception . si les données sont mises à jour dans onActivityResult, il est possible que la même erreur se produise.
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
view.postDelayed(new Runnable() {
@Override
public void run() {
refreshList();
}
},100L);
Comme @ Mullins a dit "
J'ai ajouté les éléments et appelé notifyDataSetChanged()
dans le thread d'interface utilisateur et j'ai résolu le problème. - Mullins ".
Dans mon cas, j'ai asynctask
et j'ai appelé notifyDataSetChanged()
dans la méthode doInBackground()
et le problème est résolu lorsque j'ai appelé depuis onPostExecute()
j'ai reçu l'exception.
Ma solution:
1) créer un temp ArrayList
.
2) faites vos gros travaux (sqlite row fetch, ...) avec la méthode doInBackground
et ajoutez des objets à la liste de contrôle temporaire.
3) ajoutez tous les éléments de temp araylist à la liste de votre liste dans la méthode onPostExecute
.
note:
vous voudrez peut-être supprimer certains éléments de listview et également de la base de données sqlite et éventuellement supprimer certains fichiers liés aux éléments de sdcard, il suffit de supprimer des éléments de la base de données et de supprimer leurs fichiers associés et de les ajouter à la liste des éléments temporaires dans background thread
. puis dans UI thread
, supprimez les éléments existant dans arraylist temporaire de l'arraylist de listview.
J'espère que cela t'aides.
J'avais une coutume ListAdapter
et appelais super.notifyDataSetChanged()
au début et non à la fin de la méthode
@Override
public void notifyDataSetChanged() {
recalculate();
super.notifyDataSetChanged();
}
j'ai eu le même problème quand ajouter de nouvelles données dans le chargeur d'images paresseux Je viens de mettre
adapter.notifyDataSetChanged();
dans
protected void onPostExecute(Void args) {
adapter.notifyDataSetChanged();
// Close the progressdialog
mProgressDialog.dismiss();
}
espérons que cela vous aide
J'obtenais aussi exactement la même erreur et en utilisant AsyncTask:
`Java.lang.IllegalStateException:` The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131296334, class Android.widget.ListView) with Adapter... etc
Je l'ai résolu en mettant adapter.notifyDataSetChanged();
au bas de mon thread d'interface utilisateur, c'est-à-dire ma méthode AsyncTask onPostExecute. Comme ça :
protected void onPostExecute(Void aVoid) {
all my other stuff etc...
all my other stuff etc...
adapter.notifyDataSetChanged();
}
});
}
Maintenant, mon application fonctionne.
EDIT: En fait, mon application s'est toujours écrasée environ tous les 1 sur 10, donnant la même erreur.
Finalement, je suis tombé sur runOnUiThread
sur un post précédent, ce qui, à mon avis, pourrait être utile. Alors je le mets dans ma méthode doInBackground, comme ceci:
@Override
protected Void doInBackground(Void... voids) {
runOnUiThread(new Runnable() {
public void run() { etc... etc...
Et j'ai supprimé la méthode adapter.notifyDataSetChanged();
. Maintenant, mon application ne se bloque jamais.