197

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

避けなければならないことがわかっていること: バックグラウンド スレッドから ListAdapter の内容をいじることができないため、AsyncTask を継承し、onProgressUpdate から結果を発行 (アダプターにエントリを追加) しました。私のアダプターは、結果オブジェクトの ArrayList を使用します。これらの arraylist に対するすべての操作は同期されます。

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

私が時々得たもの:注意してください、それは本当にめったに起こりません(3.5kユーザーの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)
[...]

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

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

4

25 に答える 25

122

私も同じ問題を抱えていました。

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

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

于 2011-06-15T12:55:31.400 に答える
27

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

requestLayout();

クラスからListView

于 2011-09-01T16:27:16.010 に答える
23

これはマルチスレッドの問題であり、適切に同期されたブロックを使用することで回避できます。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 は、電話帳から連絡先を読み取り、ハッシュマップに入力します。これは、List Adapters がリストを描画するために直接使用できます。

画面にボタンがあります。これらの電話番号がリストされているアクティビティが開きます。前のスレッドが作業を終了する前にリストに直接 setAdapter を設定すると、高速なナビゲーション ケースが発生する頻度が低くなります。例外がポップアップします。これは 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());    
        }
    }

}

お役に立てれば

于 2014-01-02T07:24:57.327 に答える
15

私は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();
        }
    });
}
于 2013-11-13T02:33:56.497 に答える
7

私はこのコードを書き、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);
    }
}
于 2010-06-29T20:10:08.437 に答える
3

私の問題は、ListView と一緒にフィルターを使用することに関連していました。

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

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

filter()最後の行で呼び出すnotifyDataSetChanged()と、Filter のpublishResults()メソッドで呼び出されます (必要があります)。これは、特に私の高速の Nexus 5 では問題なく動作する場合があります。しかし、実際には、遅いデバイスやリソースを大量に消費する状況で気付くバグが隠れています。

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

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

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    notifyDataSetChanged(); // Fix
    getFilter().filter(filter);
}
于 2014-04-03T11:41:30.363 に答える
3

フィード オブジェクトのリストがあります。非 UI スレッドから追加され、切り捨てられます。以下のアダプタで問題なく動作します。とにかくUIスレッドを呼び出しFeedAdapter.notifyDataSetChangedますが、少し後で。UI が機能しなくなった場合でも、Feed オブジェクトが Local Service のメモリに残るため、これが気に入っています。

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;
    }
}
于 2014-11-24T14:02:28.593 に答える
2

まったく同じエラーログで同じ問題に直面していました。私の場合onProgress()、AsyncTaskは、を使用してアダプターに値を追加しますmAdapter.add(newEntry)。UIの応答性が低下するのを防ぐために、1秒間に4回設定mAdapter.setNotifyOnChange(false)して呼び出します。mAdapter.notifyDataSetChanged()1秒に1回、配列がソートされます。

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

しかし、許容できる回避策を見つけたようです。 uiスレッドで作業しているだけでも、アダプターは呼び出さずにデータへの多くの変更を受け入れないのではないかと思います。notifyDataSetChanged()このため、前述の300ミリ秒が経過するまですべての新しいアイテムを格納するキューを作成しました。この瞬間に達したら、保存されているすべてのアイテムを1回のショットで追加し、を呼び出しますnotifyDataSetChanged()。今まで私はもうリストをクラッシュさせることができませんでした。

于 2011-04-18T06:28:50.113 に答える
2

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

于 2011-02-14T10:06:35.120 に答える
1

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

于 2011-03-09T02:01:46.053 に答える
1

このクラッシュの原因の 1 つは、ArrayListオブジェクトを完全に変更できないことです。したがって、アイテムを削除するときは、次のことを行う必要があります。

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

これでクラッシュが修正されました。

于 2013-03-27T21:04:24.247 に答える
1

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

于 2013-04-30T08:22:19.230 に答える
1

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

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

}

}
于 2016-06-08T21:09:27.203 に答える
0

次の解決策のいずれかを試してください。

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

  2. すべての UI 更新が UI スレッドで呼び出されるようにします。

于 2013-05-22T15:32:15.297 に答える
0

私には習慣があり、メソッドの最後ではなく最初ListAdapterに呼び出していましたsuper.notifyDataSetChanged()

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}
于 2015-11-09T16:18:54.020 に答える
0

私もまったく同じエラーを受け取り、 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

adapter.notifyDataSetChanged();AsyncTask onPostExecute メソッドである UI スレッドの一番下に置くことで解決しました。このような :

 protected void onPostExecute(Void aVoid) {

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

           adapter.notifyDataSetChanged();

                }

            });
        }

今私のアプリは動作します。

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

最終的に、以前の投稿に出くわしrunOnUiThreadました。これは役立つと思いました。したがって、次のように doInBackground メソッドに入れます。

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

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

そして、 adapter.notifyDataSetChanged();メソッドを削除しました。これで、アプリがクラッシュすることはありません。

于 2016-05-08T22:14:04.353 に答える