Android, ListView IllegalStateException: "O conteúdo do adaptador foi alterado, mas o ListView não recebeu uma notificação"


188

O que eu quero fazer : execute um thread em segundo plano que calcule o conteúdo do ListView e atualize o ListView parcialmente, enquanto os resultados são calculados.

O que sei que tenho que evitar : não posso mexer com o conteúdo do ListAdapter do encadeamento em segundo plano, portanto, herdei o AsyncTask e publiquei o resultado (adicione entradas ao adaptador) do onProgressUpdate. Meu adaptador usa ArrayList dos objetos de resultado, todas as operações nessas listas de matriz são sincronizadas.

Pesquisa de outras pessoas : há dados muito valiosos aqui . Também sofri falhas quase diárias para um grupo de ~ 500 usuários e, quando adicionei o list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)bloqueio no onProgressUpdate, falhas reduzidas em um fator de 10, mas não desapareceram. (foi sugerido em resposta )

O que recebi às vezes : observe que isso acontece muito raramente (uma vez por semana para um dos 3,5 mil usuários). Mas eu gostaria de me livrar desse bug completamente. Aqui está o stacktrace parcial:

`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)
[...]

Socorro? Não é mais necessário, veja abaixo

RESPOSTA FINAL: Como se viu, eu estava ligando a notifyDataSetChangedcada 5 inserções para evitar oscilações e alterações repentinas da lista. Isso não pode ser feito dessa maneira, sempre notifique o adaptador quando a lista de base for alterada. Este bug há muito tempo para mim agora.


3
Você ligou para notifyDataSetChanged ()?
Denis Palnitsky

1
Obviamente, em onProgressUpdate, existe a sequência: list.setVisibility (GONE) - operação addObjectToList / sincronizada na lista / - notifyDataSetChanged () - list.setVisibility (VISIBLE) (e sem as modificações de visibilidade, a exceção ocorre com mais frequência)
tomash

1
Você está modificando o ArrayList subjacente em um thread diferente? Todas as alterações, mesmo a ArrayList mencionada pelo adaptador, precisam ocorrer no encadeamento da interface do usuário.
precisa

2
@ Qberticus - como afirmei claramente, não modifico ArrayList de threads diferentes, mas do método onProgressUpdate do AcyncTask - ele funciona no thread da GUI.
tomash

1
@ tomash eu tenho a mesma exceção que estou usando pull para atualizar e no pós-execução escrevi adapter.notifyDataSetChanged (); lvAutherlist.completeRefreshing (); mas às vezes tenho esse erro como resolvê-lo
Khan

Respostas:


119

Eu tive o mesmo problema.

Eu estava adicionando itens ao meu ArrayListfora do thread da interface do usuário.

Solução: fiz as duas coisas adding the itemse chamei notifyDataSetChanged()o thread da interface do usuário.


4
Eu adicionei os itens e chamei notifyDataSetChanged () no thread da interface do usuário e resolvi isso.
Mullins

23
Comentário genial, sério.
dentex 16/12/13

2
Esse é um problema de multithreading e uso de blocos sincronizados corretamente. Isso pode ser evitado. Sem colocar coisas extras no UI Thread e causar perda de capacidade de resposta do aplicativo. Verifique minha resposta abaixo.
JavaScript é necessário para o funcionamento completo desta página

2
Para futuros leitores - é bom fazer cálculos no encadeamento em segundo plano, mas você deve passar resultados para o encadeamento da interface do usuário e adicionar itens à coleção base do adaptador e notificar o adaptador no mesmo bloco de código.
usar o seguinte comando

4
@Mullins u por favor pode mostrar uma amostra para sua solução eu estou preso ao mesmo assunto?
Jas

27

Eu tive o mesmo problema, mas o corrigi usando o método

requestLayout();

da classe ListView


35
quando devemos chamar esse método?
19413 JehandadK

3
@DeBuGGeR depois de adicionar itens ao ListView ou alterar o adaptador.
Ahmet Noyan Kızıltan

e se eu estiver adicionando elementos no asynctask
Dr. aNdRO

2
Apenas para constar, @ Dr.aNdRO, você coleta seus resultados no AsyncTask. doInBackground () e salve seus resultados para atualizar a lista em AsyncTask.onPostExecute (), que será executada no thread da interface do usuário. Se você precisar atualizar enquanto usa, use AsyncTask.publishProgress () e AsyncTask.onProgressUpdate (), que também é executado no thread da interface do usuário.
22814 Nicole Borrelli

21

Este é um problema com vários segmentos e usando blocos sincronizados corretamente. Isso pode ser evitado. Sem colocar coisas extras no UI Thread e causar perda de capacidade de resposta do aplicativo.

Eu também enfrentei o mesmo. E, como a resposta mais aceita sugere que fazer alterações nos dados do adaptador do UI Thread pode resolver o problema. Isso funcionará, mas é uma solução rápida e fácil, mas não é a melhor.

Como você pode ver em um caso normal. A atualização do adaptador de dados do encadeamento em segundo plano e a chamada de notifyDataSetChanged no encadeamento da UI funcionam.

Essa ilegalStateException surge quando um thread da interface do usuário está atualizando a exibição e outro thread de segundo plano altera os dados novamente. Esse momento causa esse problema.

Portanto, se você sincronizará todo o código que está alterando os dados do adaptador e fazendo a chamada notifydatasetchange. Esse problema deve ter desaparecido. Como foi para mim e eu ainda estou atualizando os dados do segmento de plano de fundo.

Aqui está o código específico do meu caso para outras pessoas se referirem.

Meu carregador na tela principal carrega os contatos da lista telefônica em minhas fontes de dados em segundo plano.

    @Override
    public Void loadInBackground() {
        Log.v(TAG, "Init loadings contacts");
        synchronized (SingleTonProvider.getInstance()) {
            PhoneBookManager.preparePhoneBookContacts(getContext());
        }
    }

Este PhoneBookManager.getPhoneBookContacts lê os contatos da agenda e os preenche nos hashmaps. Qual é diretamente utilizável para os Adaptadores de Lista desenharem a lista.

Há um botão na minha tela. Isso abre uma atividade em que esses números de telefone estão listados. Se eu definir diretamente oAdapter sobre a lista antes que o segmento anterior termine seu trabalho, isso significa que a navegação rápida ocorre com menos frequência. Aparece a exceção. Qual é o título desta pergunta SO. Então, eu tenho que fazer algo assim na segunda atividade.

Meu carregador na segunda atividade aguarda a conclusão do primeiro encadeamento. Até que ele mostre uma barra de progresso. Verifique o loadInBackground de ambos os carregadores.

Em seguida, ele cria o adaptador e o entrega para a atividade em que no thread da interface do usuário eu chamo setAdapter.

Isso resolveu meu problema.

Este código é apenas um trecho. Você precisa alterá-lo para compilar bem para você.

@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());    
        }
    }

}

Espero que isto ajude


Desculpe como você fez isso? Como você sincronizou o código de encadeamento em segundo plano e a chamada notifydatasetchange? Estou usando um doInBackground ao filtrar dados em um AutocompleteTextView e estou enfrentando o mesmo problema ... Você poderia me aconselhar algo para resolver?
tonix

@ user3019105 - Sincronizando dois threads em segundo plano. um que atualiza os dados em segundo plano e outro que notifica o encadeamento da interface do usuário para setAdadpter ou notifydatasetchanged usando métodos handler.post ou métodos runOnUiThread disponíveis. No meu caso particular, sincronizei dois carregadores com o doInBackground sincronizados em um objeto singleton. Um começa a preparar os dados na tela inicial para ficar rapidamente disponível para a tela do painel. Essa foi a minha necessidade.
Javanator

Você poderia postar um trecho de código dessa implementação? No meu caso, resolvi chamar clear () no adaptador, criando um ArrayList temporário <T> de objetos filtrados e, em seguida, chamando o addAll (tmpArrayList) e, finalmente, o notifyDataSetChanged (). Eu faço todas essas chamadas dentro do método de filtro publishResult (), executando no thread principal. Você acha que essa é uma solução confiável?
tonix

@ user3019105 verifique a resposta editada. Espero que a sua útil para você agora
Javanator

esta resposta exige uma grande quantidade de leitura sobre por que ela funciona, tutorials.jenkov.com/java-concurrency/synchronized.html é um bom lugar para começar
abdu

15

Eu resolvi isso por ter 2 listas. Uma lista eu uso apenas para o adaptador e faço todas as alterações / atualizações de dados na outra lista. Isso permite que eu faça atualizações em uma lista em um thread em segundo plano e atualize a lista "adapter" no thread 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();
        }
    });
}

7

Eu escrevi esse código e ele foi executado em uma imagem de emulador 2.1 por ~ 12 horas e não recebeu a IllegalStateException. Vou dar ao framework android o benefício da dúvida sobre este e dizer que provavelmente é um erro no seu código. Eu espero que isso ajude. Talvez você possa adaptá-lo à sua lista e dados.

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);
    }
}

Obrigado por seu trabalho! Notei que, para o método ArrayAdapter, hasStableIds () = false; minha implementação retornou true, o que não estava certo, pois as linhas foram classificadas antes de cada notifyDataSetChanged (). Vou tentar - se as falhas persistirem, continuarei a investigação. Cumprimentos!
tomash

4

Alguns dias atrás, encontrei o mesmo problema e causou vários milhares de falhas por dia, cerca de 0,1% dos usuários atendem a essa situação. Eu tentei setVisibility(GONE/VISIBLE)e requestLayout(), mas a contagem de acidentes diminui apenas um pouco.

E finalmente resolvi. Nada com setVisibility(GONE/VISIBLE). Nada com requestLayout().

Finalmente, descobri o motivo pelo qual usei um Handlerpara chamar notifyDataSetChanged()após atualizar os dados, o que pode levar a uma espécie de:

  1. Atualiza dados para um objeto de modelo (eu chamo de DataSource)
  2. O usuário toca em listview (que pode chamar checkForTap()/ onTouchEvent()e finalmente chama layoutChildren())
  3. O adaptador obtém dados do objeto de modelo e chama notifyDataSetChanged()e atualiza visualizações

E cometi outro erro que em getCount() , getItem()e getView()utilizo diretamente os campos no DataSource, em vez de copiá-los para o adaptador. Então, finalmente, ele falha quando:

  1. O adaptador atualiza os dados que a última resposta fornece
  2. Na próxima resposta, o DataSource atualiza os dados, o que causa alterações na contagem de itens
  3. O usuário toca em listview, que pode ser um toque ou um movimento ou flip
  4. getCount()e getView()é chamado, e o listview descobre que os dados não são consistentes e lança exceções como java.lang.IllegalStateException: The content of the adapter has changed but.... Outra exceção comum é IndexOutOfBoundExceptionse você usar cabeçalho / rodapé noListView .

Portanto, a solução é fácil, basta copiar os dados para o adaptador do meu DataSource quando meu manipulador aciona o adaptador para obter dados e chamadas notifyDataSetChanged(). O acidente agora nunca acontece novamente.


1
Este foi o meu problema. No meu caso, o getCount () estava retornando a nova contagem disponível antes que o notifyDataSetChanged fosse chamado. Como minha lista era 'virtual', agora capturo a contagem atualizada no meu método notifyDataSetChanged e, em seguida, retorno essa contagem em cache quando solicitada através de getCount ().
Glenn

3

Se isso acontecesse de forma intermitente, acontece que eu só tive esse problema quando a lista foi rolada depois que um último item 'carregar mais' foi clicado. Se a lista não estivesse rolada, tudo funcionaria bem.

Após MUITA depuração, houve um erro da minha parte, mas também uma inconsistência no código do Android.

Quando a validação acontece, esse código é executado no 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 "

Mas quando onChange acontece, ele dispara esse código no AdapterView (pai do ListView)

    @Override
    public void onChanged() {
        mDataChanged = true;
        mOldItemCount = mItemCount;
        mItemCount = getAdapter().getCount();

Observe como NÃO é garantido que o adaptador seja o mesmo!

No meu caso, como era um 'LoadMoreAdapter', eu estava retornando o WrappedAdapter na chamada getAdapter (para acessar os objetos subjacentes). Isso resultou em contagens diferentes devido ao item extra 'Carregar mais' e à exceção lançada.

Eu só fiz isso porque os documentos fazem parecer que está tudo bem em fazer

ListView.getAdapter javadoc

Retorna o adaptador atualmente em uso neste ListView. O adaptador retornado pode não ser o mesmo adaptador passado para setAdapter (ListAdapter), mas pode ser um WrapperListAdapter.


Estou tendo um problema semelhante . Você pode sugerir como corrigi-lo.
May13ank

Se você observar os dois exemplos de código acima, verá o mAdapter no ListView e o getAdapter () em um pai do ListView (AdapterView). Se você substituir getAdapter (), o resultado de getAdapter () poderá não ser o mAdapter. Se as contagens dos 2 adaptadores forem diferentes, você receberá o erro.
Aaronvargas

3

Meu problema estava relacionado ao uso de um filtro junto com o ListView.

Ao definir ou atualizar o modelo de dados subjacente do ListView, eu estava fazendo algo parecido com isto:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    getFilter().filter(filter);
}

A chamada filter()na última linha fará (e deve) fazer notifyDataSetChanged()com que seja chamada no publishResults()método do filtro . Às vezes, isso pode funcionar bem, especialmente no meu rápido Nexus 5. Mas, na realidade, está ocultando um bug que você notará em dispositivos mais lentos ou em condições de uso intensivo de recursos.

O problema é que a filtragem é feita de forma assíncrona e, portanto, entre o final da filter()instrução e a chamada para publishResults(), ambos no encadeamento da interface do usuário, algum outro código de encadeamento da interface do usuário pode executar e alterar o conteúdo do adaptador.

A correção real é fácil, basta ligar notifyDataSetChanged()também antes de solicitar a filtragem a ser realizada:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    notifyDataSetChanged(); // Fix
    getFilter().filter(filter);
}

3

Eu tenho uma lista se objetos de feed. É anexado e truncado a partir de um thread sem interface do usuário. Funciona bem com o adaptador abaixo. Eu chamo FeedAdapter.notifyDataSetChangedno thread da interface do usuário de qualquer maneira, mas um pouco mais tarde. Eu faço isso porque meus objetos Feed permanecem na memória no Serviço Local, mesmo quando a interface do usuário está inoperante.

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;
    }
}

2

Estou enfrentando o mesmo problema com exatamente o mesmo log de erros. No meu caso, onProgress()o AsyncTask adiciona os valores ao adaptador usando mAdapter.add(newEntry). Para evitar que a interface do usuário se torne menos responsiva, defino mAdapter.setNotifyOnChange(false)e ligo mAdapter.notifyDataSetChanged()4 vezes no segundo. Uma vez por segundo, a matriz é classificada.

Isso funciona bem e parece muito viciante, mas infelizmente é possível travá-lo tocando nos itens da lista exibidos com bastante frequência.

Mas parece que encontrei uma solução aceitável. Meu palpite é que, mesmo que você apenas trabalhe no thread da interface do usuário, o adaptador não aceita muitas alterações nos dados sem chamar notifyDataSetChanged(), por isso criei uma fila que está armazenando todos os novos itens até os 300ms mencionados. Se chegar esse momento, adiciono todos os itens armazenados de uma só vez e ligo notifyDataSetChanged(). Até agora eu não conseguia mais travar a lista .



2

Mesmo que eu tenha enfrentado o mesmo problema no meu aplicativo de notificação XMPP, a mensagem dos receptores precisa ser adicionada novamente à exibição de lista (implementada com ArrayList). Quando tentei adicionar o conteúdo do receptor atravésMessageListener (thread separado), o aplicativo fecha com o erro acima. Resolvi isso adicionando o conteúdo ao meu método arraylist& setListviewadapaterthrough runOnUiThread, que faz parte da classe Activity. Isso resolveu meu problema.


1

Enfrentei um problema semelhante, eis como resolvi no meu caso. Verifico se o taskjá existe RUNNINGou FINISHEDporque uma tarefa pode ser executada apenas uma vez. Abaixo você verá um código parcial e adaptado da minha solução.

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);
    }

}

}

1

Eu tive o mesmo problema e resolvi-o. Meu problema era que eu estava usando um listview, com um adaptador de matriz e com filtro. No método, performFilteringeu estava mexendo com a matriz que possui os dados e era o problema, pois esse método não está sendo executado no thread da interface do usuário e, eventualmente, gera alguns problemas.


1

Uma causa para esta falha é que o ArrayListobjeto não pode mudar completamente. Portanto, quando removo um item, tenho que fazer o seguinte:

mList.clear();
mList.addAll(newDataList);

Isso corrigiu o acidente para mim.


1

No meu caso, chamei o método GetFilter()em um adaptador a partir do TextWatcher()método na Atividade principal e adicionei os dados com um loop For ativado GetFilter(). A solução foi alterar o loop For para o AfterTextChanged()sub-método na Atividade principal e excluir a chamada paraGetFilter()


1
Esclareça sua solução. Eu também estou em situação parece u /
nAkhmedov 27/11/14

1
adapter.notifyDataSetChanged()

2
É uma boa prática no Stack Overflow adicionar uma explicação sobre por que sua solução deve funcionar ou é melhor que as soluções existentes. Para mais informações, leia Como responder .
Samuel Liew

0

Eu também estava recebendo exatamente o mesmo erro e usando o 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

Eu o resolvi colocando adapter.notifyDataSetChanged();na parte inferior do meu thread da interface do usuário, que é o meu método AsyncTask onPostExecute. Como isso :

 protected void onPostExecute(Void aVoid) {

 all my other stuff etc...
    all my other stuff etc...

           adapter.notifyDataSetChanged();

                }

            });
        }

Agora meu aplicativo funciona.

EDIT: Na verdade, meu aplicativo ainda travava a cada 1 em 10 vezes, dando o mesmo erro.

Eventualmente, me deparei runOnUiThreadcom um post anterior, que achei que poderia ser útil. Então eu coloquei no meu método doInBackground, assim:

@Override
protected Void doInBackground(Void... voids) {

    runOnUiThread(new Runnable() {
                      public void run() { etc... etc...

E eu removi o adapter.notifyDataSetChanged();método. Agora, meu aplicativo nunca falha.


0

Por favor, tente uma destas soluções:

  1. Às vezes, se você adicionar um novo objeto à lista de dados em um thread (ou doInBackgroundmétodo), esse erro ocorrerá. A solução é: crie uma lista temporária e inclua dados nessa lista no encadeamento (ou doInBackground) e copie todos os dados da lista temporária para a lista de adaptadores no encadeamento da interface do usuário (ou onPostExcute)

  2. Verifique se todas as atualizações da interface do usuário são chamadas no thread da interface do usuário.


0

eu tive o mesmo problema ao adicionar novos dados no carregador de imagens preguiçoso que acabei de colocar

         adapter.notifyDataSetChanged();

no

       protected void onPostExecute(Void args) {
        adapter.notifyDataSetChanged();
        // Close the progressdialog
        mProgressDialog.dismiss();
         }

espero que ajude você


0

Como o @Mullins disse "
eu adicionei os itens e liguei notifyDataSetChanged()no thread da interface do usuário e resolvi isso. - Mullins".

No meu caso, asynctaskeu chamei notifyDataSetChanged()o doInBackground()método e o problema foi resolvido. Quando liguei onPostExecute(), recebi a exceção.


0

Eu tinha um costume ListAdaptere estava ligando super.notifyDataSetChanged()no início e não no final do método

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}

0

Eu tinha a mesma situação, tinha muitos grupos de botões no meu item no listview e estava alterando alguns valores booleanos dentro do meu item, como holder.rbVar.setOnclik ...

meu problema ocorreu porque eu estava chamando um método dentro de getView (); e estava salvando um objeto dentro da preferência de compartilhamento, então eu tive o mesmo erro acima

Como eu resolvi isso; Eu removi meu método dentro de getView () para notifyDataSetInvalidated () e o problema desapareceu

   @Override
    public void notifyDataSetChanged() {
        saveCurrentTalebeOnShare(currentTalebe);
        super.notifyDataSetChanged();
    }

0

Eu tive o mesmo problema. finalmente consegui a solução

antes de atualizar o listview, se o teclado virtual estiver presente, feche-o primeiro. depois disso, defina a fonte de dados e chame notifydatasetchanged ().

ao fechar o teclado internamente, o listview atualizará sua interface do usuário. ele continua ligando até fechar o teclado. nesse momento, se a fonte de dados mudar, ela lançará essa exceção. se os dados estiverem sendo atualizados no onActivityResult, há uma chance do mesmo erro.

 InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);

        view.postDelayed(new Runnable() {
            @Override
            public void run() {
                refreshList();
            }
        },100L);

0

Minha solução:

1) crie um temp ArrayList.

2) faça seus trabalhos pesados ​​(busca de linha do sqlite, ...) no doInBackgroundmétodo e adicione itens à lista de matriz temporária.

3) adicione todos os itens de temp araylist à sua lista de arrays do listview no onPostExecutemétodo

note:convém excluir alguns itens do listview e também excluir do banco de dados sqlite e talvez excluir alguns arquivos relacionados aos itens do sdcard, apenas remover itens do banco de dados e remover seus arquivos relacionados e adicioná-los ao temp arraylist em background thread. então emUI thread exclua os itens existentes na lista de matriz temporária da lista de matriz da exibição em lista.

Espero que isto ajude.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.