Android, ListView IllegalStateException :“어댑터의 내용이 변경되었지만 ListView가 알림을받지 못했습니다”


189

내가하고 싶은 일 : 결과가 계산되는 동안 ListView 내용을 계산하고 ListView를 부분적으로 업데이트하는 백그라운드 스레드를 실행하십시오.

내가 피해야 할 것 : 백그라운드 스레드에서 ListAdapter 내용을 망칠 수 없으므로 AsyncTask를 상속하고 onProgressUpdate에서 결과를 어댑터에 추가합니다. 내 어댑터는 결과 객체의 ArrayList를 사용하며 해당 어레이 목록의 모든 작업이 동기화됩니다.

다른 사람들의 연구 : 여기에는 매우 귀중한 자료가 있습니다 . 또한 ~ 500 명의 사용자 그룹에 대해 거의 매일 충돌이 발생 list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)했으며 onProgressUpdate 에 블록을 추가 하면 충돌이 10 배 감소했지만 사라지지 않았습니다. ( 답변 으로 제안되었습니다 )

내가 때때로 얻는 것 : 실제로는 거의 발생하지 않습니다 (3.5k 사용자 중 한 주에 한 번). 그러나이 버그를 완전히 제거하고 싶습니다. 부분 스택 추적은 다음과 같습니다.

`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 회 삽입 할 때마다 전화 를 걸었습니다. 기본 목록이 변경되면 항상 어댑터에 알리십시오. 이 버그는 오래 전에 사라졌습니다.


3
notifyDataSetChanged ()를 호출 했습니까?
Denis Palnitsky

1
물론, onProgressUpdate에서 순서를 간다 : (GONE) list.setVisibility - 목록 addObjectToList을 / 동기화 작업 / - notifyDataSetChanged () - list.setVisibility은 (VISIBLE가) (및 가시성 수정없이 예외가 훨씬 더 자주 발생)
tomash

1
다른 스레드에서 기본 ArrayList를 수정하고 있습니까? 어댑터가 참조하는 ArrayList에 대한 모든 변경 사항은 UI 스레드에서 발생해야합니다.
Rich Schuler 2016 년

2
@Qberticus-분명히 언급했듯이 다른 스레드에서 ArrayList를 수정하지 않지만 AcyncTask의 onProgressUpdate 메소드에서 GUI 스레드에서 작동합니다.
으깨다

1
@tomash 나는 같은 예외가 있는데 pull을 사용하여 새로 고침하고 포스트 실행에서 adapter.notifyDataSetChanged ()를 작성했습니다 . lvAutherlist.completeRefreshing (); 그러나 때로는이 오류를 해결하는 방법을 알 수 있습니다
Khan

답변:


119

나는 같은 문제가 있었다.

ArrayListUI 스레드 외부에 항목을 추가하고있었습니다 .

솔루션 : 두 가지를 모두 수행 했으며 UI 스레드에서 adding the items호출 notifyDataSetChanged()했습니다.


4
두 항목을 모두 추가하고 UI 스레드에서 notifyDataSetChanged ()를 호출하여 해결했습니다.
멀린

23
진실한 의견입니다.
dentex

2
이것은 멀티 스레딩 문제이며 적절하게 동기화 된 블록 사용 방지 할 수 있습니다. UI 스레드에 추가 항목을 넣지 않고 앱의 응답 성을 잃지 않고 아래 답변을 확인하십시오.
Javanator

2
미래 독자를 위해-백그라운드 스레드에서 계산을 수행하는 것이 좋지만 UI 스레드에 결과를 전달하고 어댑터 기본 콜렉션에 항목을 추가하고 동일한 코드 블록으로 어댑터에 알리는 작업을 수행해야합니다.
tomash

4
@Mullins 솔루션에 대한 샘플을 보여 주실 수 있습니까? 같은 문제가 발생했습니다
Jas

27

같은 문제가 있었지만 방법을 사용하여 문제를 해결했습니다.

requestLayout();

수업에서 ListView


35
언제이 메소드를 호출해야합니까?
JehandadK

3
ListView에 항목을 추가하거나 어댑터를 변경 한 후 @DeBuGGeR
Ahmet Noyan Kızıltan

asynctask에 요소를 추가하는 경우
Dr. aNdRO

2
@ Dr.aNdRO 레코드의 경우 AsyncTask에서 결과를 수집합니다. UI 스레드에서 실행될 AsyncTask.onPostExecute ()의 목록을 업데이트하려면 doInBackground ()를 수행하고 결과를 저장하십시오. 당신이 경우 필요 당신이 가고, 사용 AsyncTask.publishProgress () 또한 UI 스레드에서 실행 AsyncTask.onProgressUpdate ()로 업데이트합니다.
Nicole Borrelli

21

이것은 멀티 스레딩 문제이며 제대로 동기화 된 블록 사용 방지 할 수 있습니다. UI 스레드에 추가 항목을 넣지 않고 앱의 응답 성을 잃지 않습니다.

나도 똑같이 직면했다. 가장 널리 인정되는 답변에서 UI Thread에서 어댑터 데이터를 변경하면 문제를 해결할 수 있습니다. 그것은 작동하지만 빠르고 쉬운 해결책이지만 최선의 해결책은 아닙니다.

보시다시피 정상적인 경우입니다. 백그라운드 스레드에서 데이터 어댑터를 업데이트하고 UI 스레드에서 notifyDataSetChanged를 호출하면 작동합니다.

이 illegalStateException은 UI 스레드가보기를 업데이트하고 다른 백그라운드 스레드가 데이터를 다시 변경하는 경우 발생합니다. 그 순간이 문제가 발생합니다.

따라서 어댑터 데이터를 변경하고 notifydatasetchange를 호출하는 모든 코드를 동기화하는 경우. 이 문제는 사라져야합니다. 나를 위해 사라졌고 여전히 백그라운드 스레드에서 데이터를 업데이트하고 있습니다.

다른 사람들이 참조 할 수있는 사례 별 코드는 다음과 같습니다.

메인 화면의 내 로더는 전화 번호부 연락처를 백그라운드의 내 데이터 소스에로드합니다.

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

이 PhoneBookManager.getPhoneBookContacts는 전화 번호부에서 연락처를 읽고 해시 맵에 채 웁니다. 목록 어댑터가 목록을 그리는 데 직접 사용할 수 있습니다.

화면에 버튼이 있습니다. 전화 번호가 나열된 활동이 열립니다. 이전 스레드가 작업을 완료하기 전에 목록에 어댑터를 직접 설정하면 빠른 탐색 사례가 덜 자주 발생합니다. 이 SO 질문의 제목은 예외입니다. 두 번째 활동에서 이와 같은 작업을 수행해야합니다.

두 번째 활동의 내 로더는 첫 번째 스레드가 완료되기를 기다립니다. 진행률 표시 줄이 표시 될 때까지 두 로더의 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());    
        }
    }

}

도움이 되었기를 바랍니다


어떻게 그랬어? 백그라운드 스레드 코드와 notifydatasetchange 호출을 어떻게 동기화 했습니까? AutocompleteTextView에서 데이터를 필터링 할 때 doInBackground를 사용하고 있는데 같은 문제가 있습니다 ... 해결하기 위해 조언을 해 주시겠습니까?
tonix

@ user3019105-두 개의 백그라운드 스레드 동기화. 하나는 백그라운드에서 데이터를 업데이트하고 다른 하나는 ui 스레드에 setAdadpter에게 알리거나 handler.post 메소드 또는 runOnUiThread 메소드를 사용하여 변경된 데이터 세트에 알립니다. 내 특별한 경우에는 싱글 톤 객체에서 동기화 된 doInBackground와 두 로더를 동기화했습니다. 시작 화면 자체에서 대시 보드 화면에 빠르게 사용할 수 있도록 데이터를 준비하기 시작합니다. 그것은 나의 필요였다.
Javanator

이 구현의 코드 스 니펫을 게시 해 주시겠습니까? 필자의 경우 어댑터에서 clear () 호출을 해결하여 필터링 된 객체의 임시 ArrayList <T>를 만든 다음 addAll (tmpArrayList) 및 최종적으로 notifyDataSetChanged ()를 호출했습니다. 메인 스레드에서 실행되는 publishResult () 필터 메서드 내 에서이 모든 호출을 수행합니다. 이것이 신뢰할만한 솔루션이라고 생각하십니까?
tonix

@ user3019105 수정 된 답변을 확인하십시오. 도움이
되길 바랍니다

이 대답은 그것이 작동하는 이유에 대해 읽기의 제비를 필요로 tutorials.jenkov.com/java-concurrency/synchronized.html은 시작하기에 좋은 장소입니다
압두

15

나는 2 개의 목록을 가지고 이것을 해결했다. 하나는 어댑터에만 사용하고 다른 목록에서는 모든 데이터 변경 / 업데이트를 수행합니다. 이를 통해 백그라운드 스레드의 한 목록에서 업데이트를 수행 한 다음 기본 / 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

이 코드를 작성하여 ~ 12 시간 동안 2.1 에뮬레이터 이미지에서 실행했으며 IllegalStateException을 얻지 못했습니다. 나는 안드로이드 프레임 워크에 이것에 대한 의심의 이점을 줄 것이고 그것이 코드에 오류가 있다고 말할 것입니다. 이게 도움이 되길 바란다. 목록과 데이터에 적용 할 수 있습니다.

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

작업 해 주셔서 감사합니다! ArrayAdapter 메소드의 경우 hasStableIds () = false; 내 구현은 모든 notifyDataSetChanged () 전에 행이 정렬되었으므로 옳지 않은 true를 반환했습니다. 시도해 보겠습니다. 충돌이 지속되면 조사를 계속하겠습니다. 친애하는!
tomash

4

며칠 전에 동일한 문제가 발생하여 하루에 수천 건의 충돌이 발생했으며 약 0.1 %의 사용자가이 상황을 충족합니다. 나는 시도 setVisibility(GONE/VISIBLE)하고 requestLayout()있지만, 충돌 횟수는 약간 감소합니다.

그리고 마침내 그것을 해결했습니다. 와 아무것도 없습니다 setVisibility(GONE/VISIBLE). 와 아무것도 없습니다 requestLayout().

마지막으로 데이터를 업데이트 한 후 Handler호출 하는 데 사용 된 이유는 notifyDataSetChanged()다음과 같습니다.

  1. 데이터를 모델 객체로 업데이트합니다 (데이터 소스라고 함).
  2. 사용자가 listview를 터치합니다 ( checkForTap()/ onTouchEvent()를 호출하고 마지막으로 호출 할 수 있음 layoutChildren())
  3. 어댑터는 모델 오브젝트에서 데이터를 가져오고 notifyDataSetChanged()뷰를 호출 하고 업데이트합니다.

그리고 나는 또 다른 실수를 getCount(), getItem()그리고 getView()내가 직접 오히려 어댑터 사본 그들을보다, 데이터 소스의 필드를 사용합니다. 따라서 다음과 같은 경우에 충돌이 발생합니다.

  1. 어댑터는 마지막 응답이 제공하는 데이터를 업데이트합니다
  2. 다음에 응답 할 때 DataSource는 데이터를 업데이트하여 항목 수를 변경합니다.
  3. 사용자가 탭 또는 이동 또는 뒤집기 일 수있는 목록보기를 터치합니다.
  4. getCount()그리고 getView()라고하며, 목록보기 발견 데이터가 일치하지 않습니다, 그리고 같은 예외가 발생합니다 java.lang.IllegalStateException: The content of the adapter has changed but.... 다른 일반적인 예외는에서 IndexOutOfBoundException헤더 / 바닥 글을 사용 하는 경우입니다 ListView.

그래서 해결책은 쉽습니다. 내 핸들러가 데이터를 가져오고 호출하기 위해 어댑터를 트리거 할 때 데이터 소스에서 어댑터로 데이터를 복사합니다 notifyDataSetChanged(). 이제 충돌이 다시 발생하지 않습니다.


1
이것은 내 문제였다. 필자의 경우 getCount ()는 notifyDataSetChanged가 호출되기 전에 사용 가능한 새 개수를 반환했습니다. 내 목록이 '가상'이었으므로 이제 notifyDataSetChanged 메서드에서 업데이트 된 카운트를 캡처 한 다음 getCount ()를 통해 카운트를 요청하면이 캐시 된 카운트를 반환합니다.
Glenn

3

이 문제가 간헐적으로 발생했다면 마지막 항목을 클릭 한 후 목록을 스크롤했을 때만이 문제가 발생했습니다. 목록이 스크롤되지 않으면 모든 것이 잘 작동합니다.

MUCH 디버깅 후 버그가 발생했지만 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 일 수 있습니다.


비슷한 문제가 있습니다. 고치는 방법을 제안 해 주시겠습니까?
May13ank

위의 두 코드 샘플을 보면 ListView에 mAdapter가 표시되고 ListView의 상위에 getAdapter ()가 표시됩니다 (AdapterView). getAdapter ()를 대체하면 getAdapter ()의 결과가 mAdapter가 아닐 수 있습니다. 2 개의 어댑터 개수가 다르면 오류가 발생합니다.
aaronvargas

3

내 문제는 ListView와 함께 필터를 사용하는 것과 관련이 있습니다 .

ListView의 기본 데이터 모델을 설정하거나 업데이트 할 때 다음과 같이했습니다.

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

filter()마지막 라인에서 호출 notifyDataSetChanged()하면 필터 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);
}

3

Feed 객체 인 경우 List가 있습니다. none-UI 스레드에서 추가되고 잘립니다. 아래 어댑터와 잘 작동합니다. FeedAdapter.notifyDataSetChanged어쨌든 조금 후에 UI 스레드를 호출 합니다. UI가 죽었을 때도 Feed 객체가 로컬 서비스의 메모리에 남아 있기 때문에 이것을 좋아합니다.

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

정확히 동일한 오류 로그로 동일한 문제에 직면했습니다. 필자의 경우 onProgress()AsyncTask는을 사용하여 값을 어댑터에 추가합니다 mAdapter.add(newEntry). UI의 응답 성이 떨어지는 것을 피하기 위해 초를 4 번 설정 mAdapter.setNotifyOnChange(false)하고 호출했습니다 mAdapter.notifyDataSetChanged(). 초당 한 번 배열이 정렬됩니다.

이것은 잘 작동하고 매우 중독성으로 보이지만 불행히도 표시된 목록 항목을 자주 터치하면 충돌이 발생할 수 있습니다.

그러나 수용 가능한 해결 방법을 찾은 것 같습니다. 내 생각에 UI 스레드에서만 작업하더라도 어댑터가 호출하지 않고 데이터의 많은 변경 사항을 수락하지 않는다고 notifyDataSetChanged()생각합니다.이 때문에 언급 된 300ms가 끝날 때까지 모든 새 항목을 저장하는 대기열을 만들었습니다. 이 순간에 도달하면 저장된 모든 항목을 한 번에 추가하고을 호출 notifyDataSetChanged()합니다. 지금까지 나는 더 이상 목록을 추락 할 수 없었다 .



2

내 XMPP 알림 응용 프로그램에서 동일한 문제에 직면하더라도 수신자 메시지를 목록보기에 다시 추가해야합니다 (으로 구현 ArrayList). 수신자 콘텐츠를 MessageListener별도의 스레드를 통해 추가하려고하면 응용 프로그램이 위의 오류로 종료됩니다. Activity 클래스의 일부인 내 arraylist& setListviewadapater통해 runOnUiThread메소드에 내용을 추가 하여이 문제를 해결했습니다 . 이것은 내 문제를 해결했습니다.


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

나는 같은 문제가 있었고 그것을 해결했다. 내 문제는 listview배열 어댑터 및 필터와 함께를 사용하고 있다는 것 입니다. 이 방법 performFiltering에서는 데이터가있는 배열을 엉망으로 만들었습니다.이 방법은 UI 스레드에서 실행되지 않고 EVENTUALLY로 문제가 발생하기 때문에 문제였습니다.


1

이 충돌의 한 가지 원인은 ArrayList개체를 완전히 변경할 수 없기 때문입니다. 따라서 항목을 제거 할 때 다음을 수행해야합니다.

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

이로 인해 충돌이 해결되었습니다.


1

필자의 경우 기본 활동 GetFilter()TextWatcher()메소드에서 어댑터 의 메소드 를 호출했으며 For 루프가있는 데이터를 추가했습니다 GetFilter(). 해결책은 AfterTextChanged()기본 활동 에서 For 루프를 하위 메소드 로 변경하고에 대한 호출을 삭제했습니다.GetFilter()


1
솔루션을 명확히하십시오. 나는 또한 상황이 당신처럼 보인다
nAkhmedov

1
adapter.notifyDataSetChanged()

2
솔루션이 왜 작동해야하는지 또는 기존 솔루션보다 나은지에 대한 설명을 추가하는 것이 Stack Overflow의 모범 사례입니다. 자세한 내용은 답변 방법을 참조하십시오 .
Samuel Liew

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();UI 스레드의 맨 아래 에 배치 하여 AsyncTask onPostExecute 메서드를 사용하여 문제를 해결했습니다 . 이처럼 :

 protected void onPostExecute(Void aVoid) {

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

           adapter.notifyDataSetChanged();

                }

            });
        }

이제 내 앱이 작동합니다.

편집 : 사실, 내 앱은 여전히 ​​약 10 번마다 충돌하여 동일한 오류가 발생합니다.

결국 나는 runOnUiThread이전 게시물을 보았는데 유용하다고 생각했습니다. 그래서 다음과 같이 doInBackground 메소드에 넣었습니다.

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

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

그리고 그 adapter.notifyDataSetChanged();방법을 제거했습니다 . 이제 내 앱이 충돌하지 않습니다.


0

다음 해결책 중 하나를 시도하십시오.

  1. 경우에 따라 스레드 (또는 doInBackground메서드)의 데이터 목록에 새 개체를 추가하면 이 오류가 발생합니다. 해결책은 다음과 같습니다. 임시 목록을 작성하고 thread (또는 doInBackground) 에서이 목록에 데이터를 추가 한 다음 임시 목록에서 UI 스레드의 어댑터 목록으로 모든 데이터를 복사하십시오 (또는 onPostExcute).

  2. 모든 UI 업데이트가 UI 스레드에서 호출되는지 확인하십시오.


0

게으른 이미지 로더에 새 데이터를 추가 할 때 동일한 문제가 발생했습니다.

         adapter.notifyDataSetChanged();

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

그것이 당신을 도울 희망


0

@Mullins가 말했듯이 "
아이템을 추가 notifyDataSetChanged()하고 UI 스레드에서 호출 했는데 이것을 해결했습니다. – Mullins".

내 경우에는 내가 가지고 asynctask내가 전화 notifyDataSetChanged()doInBackground()방법에서 내가 전화했을 때 문제가 해결되고 onPostExecute()난 예외를 받았다.


0

나는 관습을 가지고 있었고 방법의 끝이 아닌 처음 ListAdapter에 전화 super.notifyDataSetChanged()했다.

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

0

나는 같은 sittuation을 가지고 있었고, listview에 많은 항목이있는 단추 그룹을 가지고 있었고 holder.rbVar.setOnclik와 같은 항목 내 부울 값을 변경하고있었습니다 ...

getView () 내부에서 메소드를 호출했기 때문에 문제가 발생했습니다. sharepreference 내부에 객체를 저장하고 있었으므로 위의 동일한 오류가 발생했습니다.

내가 어떻게 해결했는지; getView () 내에서 notifyDataSetInvalidated () 메서드를 제거했는데 문제가 사라졌습니다.

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

0

나는 같은 문제가 있었다. 마침내 나는 해결책을 얻었다

목록보기를 업데이트하기 전에 소프트 키패드가 있으면 먼저 닫으십시오. 그 후에 데이터 소스를 설정하고 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

내 해결책 :

1)을 만듭니다 temp ArrayList.

2) 무거운 작업 (sqlite row fetch ...)을 수행하십시오. doInBackground 메소드 을 수행하고 임시 배열 목록에 항목을 추가하십시오.

3) onPostExecute방법으로 temp araylist의 모든 항목을 listview의 arraylist에 추가하십시오 .

note:listview에서 일부 항목을 삭제하고 sqlite 데이터베이스에서 삭제하고 sdcard에서 항목과 관련된 일부 파일을 삭제할 수도 있습니다. 데이터베이스에서 항목을 제거하고 관련 파일을 제거하고 임시 배열 목록에 추가하십시오 background thread. 그런 다음 UI thread임시 배열 목록에있는 항목을 목록보기의 배열 목록 에서 삭제하십시오.

도움이 되었기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.