web-dev-qa-db-ja.com

Android、ListView IllegalStateException:「アダプターのコンテンツは変更されましたが、ListViewは通知を受信しませんでした」

私がしたいこと:バックグラウンドスレッドを実行して、ListViewの内容を計算し、結果を計算しながらListViewを部分的に更新します。

私が避けなければならないことを知っている:私はバックグラウンドスレッドからListAdapterコンテンツを混乱させることはできないので、AsyncTaskを継承し、結果を(アダプタにエントリを追加して)公開しますonProgressUpdate。私のアダプターは結果オブジェクトのArrayListを使用し、それらのarraylistに対するすべての操作は同期されます。

他の人の研究:非常に貴重なデータがあります ここ 。また、500人までのユーザーグループでほぼ毎日クラッシュが発生し、onProgressUpdateにlist.setVisibility(GONE)/trackList.setVisibility(VISIBLE)ブロックを追加すると、クラッシュは10分の1に減少しましたが、消えませんでした。 ( answer で提案されました)

私が時々得たもの:まれにしか発生しません(3.5kユーザーの1人に1週間に1回)。しかし、私はこのバグを完全に取り除きたいです。部分的なスタックトレースは次のとおりです。

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

ヘルプ?もう必要ありません。以下を参照してください

最終回答:判明したように、ちらつきやリストの突然の変更を避けるために、5回の挿入ごとにnotifyDataSetChangedを呼び出していました。そのような方法では実行できません。ベースリストが変更された場合は、常にアダプタに通知してください。このバグは、私にとって今ではなくなっています。

184
tomash

同じ問題がありました。

UIスレッドの外部のArrayListにアイテムを追加していました。

解決策:adding the itemsとUIスレッドでnotifyDataSetChanged()の両方を実行しました。

114
Mullins

私は同じ問題を抱えていましたが、メソッドを使用して修正しました

requestLayout();

クラスListViewから

26
gian1200

これはマルチスレッド問題と適切な使用同期ブロックこれは防ぐことができます。 UIスレッドに余分なものを置くことなく、アプリの応答性を損なうことなく。

私も同じことに直面しました。そして、最も受け入れられている答えが示すように、UIスレッドからアダプターデータに変更を加えることで問題を解決できます。それは機能しますが、迅速で簡単な解決策ですが、最良の解決策ではありません。

通常のケースでわかるように。バックグラウンドスレッドからデータアダプターを更新し、UIスレッドでnotifyDataSetChangedを呼び出します。

このillegalStateExceptionは、UIスレッドがビューを更新しており、別のバックグラウンドスレッドがデータを再度変更したときに発生します。その瞬間がこの問題の原因です。

したがって、アダプターデータを変更し、notifydatasetchange呼び出しを行うすべてのコードを同期する場合。この問題は解消されるはずです。私のために行ったように、私はまだバックグラウンドスレッドからデータを更新しています。

他の人が参照できるように私のケース固有のコードを次に示します。

メイン画面のローダーは、電話帳の連絡先をバックグラウンドでデータソースにロードします。

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

このPhoneBookManager.getPhoneBookContactsは、電話帳から連絡先を読み取り、ハッシュマップに入力します。リストアダプターがリストを描画するために直接使用できます。

画面にボタンがあります。これにより、これらの電話番号がリストされているアクティビティが開きます。前のスレッドが作業を終了する前にリストにAdapterを直接設定すると、高速なnaviagtionのケースが少なくなります。このSO質問のタイトルは例外をポップアップします。だから私は2番目の活動でこのようなことをしなければなりません。

2番目のアクティビティのローダーは、最初のスレッドが完了するまで待機します。進行状況バーが表示されるまで。両方のローダーのloadInBackgroundを確認します。

次に、アダプタを作成し、UIスレッドでsetAdapterを呼び出すアクティビティに配信します。

これで問題が解決しました。

このコードはスニペットのみです。適切にコンパイルするには、変更する必要があります。

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

}

お役に立てれば

20
Javanator

私はこれを2つのリストで解決しました。 1つのリストはアダプターのみに使用し、他のリストではすべてのデータの変更/更新を行います。これにより、バックグラウンドスレッドの1つのリストを更新し、メイン/ 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();
        }
    });
}
15
triad

このコードを作成し、2.1エミュレーターイメージで最大12時間実行しましたが、IllegalStateExceptionが発生しませんでした。 Androidフレームワークにこの疑いのメリットを与え、コードのエラーである可能性が高いと言います。これがお役に立てば幸いです。たぶんあなたはあなたのリストとデータにそれを適応させることができます。

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

数日前、私は同じ問題に遭遇し、1日に数千のクラッシュを引き起こしました。ユーザーの約0.1%がこの状況を満たしています。 setVisibility(GONE/VISIBLE)requestLayout()を試しましたが、クラッシュカウントは少ししか減少しません。

そしてついに解決しました。 setVisibility(GONE/VISIBLE)には何もありません。 requestLayout()には何もありません。

最後に、データの更新後にHandlerを使用してnotifyDataSetChanged()を呼び出したことが原因であることがわかりました。

  1. データをモデルオブジェクトに更新します(DataSourceと呼びます)
  2. ユーザーがリストビューにタッチします(これはcheckForTap()/onTouchEvent()を呼び出し、最後にlayoutChildren()を呼び出します)
  3. アダプタはモデルオブジェクトからデータを取得し、notifyDataSetChanged()を呼び出してビューを更新します

また、getCount()getItem()、およびgetView()で、アダプターにコピーするのではなく、DataSourceのフィールドを直接使用するという別の間違いを犯しました。そのため、最終的に次の場合にクラッシュします。

  1. アダプターは、最後の応答が与えるデータを更新します
  2. 次の応答が返されると、DataSourceはデータを更新し、これによりアイテム数が変更されます
  3. ユーザーがリストビューをタッチします。リストビューはタップ、移動、またはフリップです。
  4. getCount()およびgetView()が呼び出され、listviewはデータに一貫性がないことを検出し、Java.lang.IllegalStateException: The content of the adapter has changed but...のような例外をスローします。別の一般的な例外は、IndexOutOfBoundExceptionでヘッダー/フッターを使用する場合のListViewです。

解決策は簡単です。ハンドラーがアダプターをトリガーしてデータを取得し、notifyDataSetChanged()を呼び出したときに、DataSourceからアダプターにデータをコピーするだけです。クラッシュは二度と起こりません。

3
HJWAJ

私の問題は、ListViewとともにFilterを使用することに関連していました。

ListViewの基になるデータモデルを設定または更新するとき、次のようなことをしていました。

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

最後の行でfilter()を呼び出すと、フィルターのnotifyDataSetChanged()メソッドでpublishResults()が呼び出されます(また、そうする必要があります)。これは時々、特に高速なNexus 5で問題なく動作する可能性があります。しかし、実際には、遅いデバイスやリソースを集中的に使用する状況で気付くバグが隠れています。

問題は、フィルタリングが非同期に行われるため、UIスレッドでfilter()ステートメントの終了とpublishResults()の呼び出しの間に、他のUIスレッドコードが実行され、アダプターのコンテンツが変更される可能性があることです。

実際の修正は簡単です。フィルタリングの実行を要求する前にnotifyDataSetChanged()を呼び出すだけです:

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

フィードオブジェクトのリストがあります。なしUIスレッドから追加および切り捨てられます。以下のアダプターで正常に動作します。とにかくUIスレッドでFeedAdapter.notifyDataSetChangedを呼び出しますが、少し後で。フィードオブジェクトは、UIが停止していてもローカルサービスのメモリにとどまるため、これが好きです。

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

これが断続的に発生した場合、「もっと読み込む」最後のアイテムをクリックした後にリストがスクロールされたときにのみこの問題が発生したことがわかりました。リストがスクロールされなかった場合、すべてが正常に機能しました。

多くのデバッグの後、それは私の側のバグでしたが、Androidコードにも矛盾がありました。

検証が行われると、このコードが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 "

しかし、onChangeが発生すると、AdapterView(ListViewの親)でこのコードが起動されます。

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

アダプタが同じであることが保証されていないことに注意してください!

私の場合、「LoadMoreAdapter」だったので、getAdapter呼び出しでWrappedAdapterを返していました(基になるオブジェクトにアクセスするため)。これにより、余分な「もっと読み込む」アイテムと例外がスローされるため、カウントが異なります。

ドキュメントがそれをするのは大丈夫のように見えるので、私はこれをしました

ListView.getAdapter javadoc

このListViewで現在使用されているアダプターを返します。返されるアダプターは、setAdapter(ListAdapter)に渡されるアダプターとは異なる場合がありますが、WrapperListAdapterである場合があります。

3
aaronvargas

これはAndroid 4〜4.4(KitKat)の既知のバグであり、「> 4.4」で解決されています

こちらをご覧ください: https://code.google.com/p/Android/issues/detail?id=71936

2

XMPP通知アプリケーションで同じ問題に直面した場合でも、受信者メッセージをリストビューに再度追加する必要があります(ArrayListで実装)。 MessageListener(別個のスレッド)を介してレシーバーのコンテンツを追加しようとすると、アプリケーションは上記のエラーで終了します。 Activityの一部であるarraylistメソッドを介してsetListviewadapaterrunOnUiThreadにコンテンツを追加することでこれを解決しました。これで私の問題が解決しました。

2
Balaji

私はまったく同じエラーログで同じ問題に直面していました。私の場合、AsyncTaskのonProgress()は、mAdapter.add(newEntry)を使用してアダプターに値を追加します。 UIの応答が遅くなるのを避けるために、mAdapter.setNotifyOnChange(false)を設定し、mAdapter.notifyDataSetChanged()を2回目に4回呼び出します。 1秒間に1回、配列が並べ替えられます。

これはうまく機能し、非常に中毒性がありますが、残念ながら、表示されているリストアイテムに十分な回数触れるとクラッシュする可能性があります。

ただし、許容できる回避策を見つけたようです UIスレッドで作業しているだけでも、アダプターはnotifyDataSetChanged()を呼び出さずにデータの多くの変更を受け入れないため、上記の300ミリ秒が終わるまで、すべての新しいアイテムを保存しているキュー。この瞬間に到達したら、保存されているすべてのアイテムを一度に追加し、notifyDataSetChanged()を呼び出します。今までリストをもうクラッシュさせることはできませんでした

2
Lars K.

私は同様の問題に直面しました、ここに私の場合の解決方法があります。タスクは1回しか実行できないため、taskが既にRUNNINGまたはFINISHEDであるかどうかを確認します。以下に、私のソリューションからの部分的で適合したコードを示します。

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
Leonardo Costa

私の場合、メインアクティビティのGetFilter()メソッドからアダプターのメソッドTextWatcher()を呼び出し、GetFilter()のForループでデータを追加しました。解決策は、メインアクティビティでForループをAfterTextChanged()サブメソッドに変更し、GetFilter()への呼び出しを削除することでした

1
sek13300

このクラッシュの原因の1つは、ArrayListオブジェクトを完全に変更できないことです。だから、アイテムを削除するとき、私はこれをしなければなりません:

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

これにより、クラッシュが修正されました。

1
andude

私は同じ問題を抱えていて、それを解決しました。私の問題は、配列アダプターとフィルターでlistviewを使用していたことです。メソッドperformFilteringでは、データを持つ配列をいじっていましたが、このメソッドはUIスレッドで実行されておらず、最終的にはいくつかの問題が発生するため、問題でした。

1
Gusthema

これらの解決策のいずれかを試してください:

  1. スレッド(またはdoInBackgroundメソッド)のデータリストに新しいオブジェクトを追加すると、このエラーが発生する場合があります。解決策は、一時リストを作成し、thread(またはdoInBackground)のこのリストにデータを追加してから、一時リストからUIスレッドのアダプターのリスト(またはonPostExcute)にすべてのデータをコピーすることです。

  2. すべてのUI更新がUIスレッドで呼び出されることを確認してください。

0
Phuc Tran

私は同じ状況で、多くのボタングループをリストビューでアイテムにインサイトし、holder.rbVar.setOnclikのようなアイテム内のブール値を変更していました...

getView()内でメソッドを呼び出していたため、問題が発生しました。 sharepreference内にオブジェクトを保存していたため、上記と同じエラーが発生しました

どのように解決したか。 getView()内のメソッドをnotifyDataSetInvalidated()に削除し、問題はなくなりました

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

私は同じ問題を抱えていました。最終的に私は解決策を得た

リストビューを更新する前に、ソフトキーパッドが存在する場合は、最初に閉じます。その後、データソースを設定し、notifydatasetchanged()を呼び出します。

キーパッドを内部で閉じると、listviewはそのUIを更新します。キーパッドを閉じるまで呼び出しを続けます。そのとき、データソースが変更されると、この例外がスローされます。 onActivityResultでデータが更新されている場合、同じエラーが発生する可能性があります。

 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
sreejith

@Mullinsが言ったように「
両方のアイテムを追加し、UIスレッドでnotifyDataSetChanged()を呼び出し、これを解決しました。 –マリンズ」。

私の場合、asynctaskがあり、notifyDataSetChanged()メソッドでdoInBackground()を呼び出しました。onPostExecute()から呼び出したときに問題が解決し、例外を受け取りました。

0
Ciro Mine

私の解決策:

1)temp ArrayListを作成します。

2)doInBackgroundメソッドで重い作業(sqlite行フェッチなど)を実行し、一時配列リストに項目を追加します。

3)一時変数リストのすべての項目をonPostExecuteメソッドでリストビューの配列リストに追加します。

note:リストビューからいくつかのアイテムを削除し、sqliteデータベースからも削除し、sdcardからアイテムに関連するいくつかのファイルを削除し、データベースからアイテムを削除して関連ファイルを削除し、background threadの一時配列リストに追加します。次に、UI threadで、リストビューのarraylistからtemp arraylistに存在するアイテムを削除します。

お役に立てれば。

0
Nobody8

カスタムListAdapterがあり、メソッドの最後ではなく最初でsuper.notifyDataSetChanged()を呼び出していました

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

レイジーイメージローダーに新しいデータを追加するときに同じ問題が発生しました

         adapter.notifyDataSetChanged();

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

それがあなたを助けることを願っています

0
Hobii Sgonf

私もまったく同じエラーが発生し、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

UIスレッドの一番下にputadapter.notifyDataSetChanged();を追加することで解決しました。これはAsyncTask onPostExecuteメソッドです。このような :

 protected void onPostExecute(Void aVoid) {

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

           adapter.notifyDataSetChanged();

                }

            });
        }

これで私のアプリは動作します。

編集:実際、私のアプリは10回に1回の割合でクラッシュし、同じエラーが発生しました。

最終的に、以前の投稿でrunOnUiThreadに出会いました。だから私はこのように私のdoInBackgroundメソッドにそれを置きます:

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

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

そして、adapter.notifyDataSetChanged();メソッドを削除しました。今、私のアプリは決してクラッシュしません。

0
CHarris