내가하고 싶은 일 : 결과가 계산되는 동안 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 회 삽입 할 때마다 전화 를 걸었습니다. 기본 목록이 변경되면 항상 어댑터에 알리십시오. 이 버그는 오래 전에 사라졌습니다.
답변
나는 같은 문제가 있었다.
ArrayList
UI 스레드 외부에 항목을 추가하고있었습니다 .
솔루션 : 두 가지를 모두 수행 했으며 UI 스레드에서 adding the items
호출 notifyDataSetChanged()
했습니다.
답변
같은 문제가 있었지만 방법을 사용하여 문제를 해결했습니다.
requestLayout();
수업에서 ListView
답변
이것은 멀티 스레딩 문제이며 제대로 동기화 된 블록 사용 방지 할 수 있습니다. 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());
}
}
}
도움이 되었기를 바랍니다
답변
나는 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();
}
});
}
답변
이 코드를 작성하여 ~ 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);
}
}
답변
며칠 전에 동일한 문제가 발생하여 하루에 수천 건의 충돌이 발생했으며 약 0.1 %의 사용자가이 상황을 충족합니다. 나는 시도 setVisibility(GONE/VISIBLE)
하고 requestLayout()
있지만, 충돌 횟수는 약간 감소합니다.
그리고 마침내 그것을 해결했습니다. 와 아무것도 없습니다 setVisibility(GONE/VISIBLE)
. 와 아무것도 없습니다 requestLayout()
.
마지막으로 데이터를 업데이트 한 후 Handler
호출 하는 데 사용 된 이유는 notifyDataSetChanged()
다음과 같습니다.
- 데이터를 모델 객체로 업데이트합니다 (데이터 소스라고 함).
- 사용자가 listview를 터치합니다 (
checkForTap()
/onTouchEvent()
를 호출하고 마지막으로 호출 할 수 있음layoutChildren()
) - 어댑터는 모델 오브젝트에서 데이터를 가져오고
notifyDataSetChanged()
뷰를 호출 하고 업데이트합니다.
그리고 나는 또 다른 실수를 getCount()
, getItem()
그리고 getView()
내가 직접 오히려 어댑터 사본 그들을보다, 데이터 소스의 필드를 사용합니다. 따라서 다음과 같은 경우에 충돌이 발생합니다.
- 어댑터는 마지막 응답이 제공하는 데이터를 업데이트합니다
- 다음에 응답 할 때 DataSource는 데이터를 업데이트하여 항목 수를 변경합니다.
- 사용자가 탭 또는 이동 또는 뒤집기 일 수있는 목록보기를 터치합니다.
getCount()
그리고getView()
라고하며, 목록보기 발견 데이터가 일치하지 않습니다, 그리고 같은 예외가 발생합니다java.lang.IllegalStateException: The content of the adapter has changed but...
. 다른 일반적인 예외는에서IndexOutOfBoundException
헤더 / 바닥 글을 사용 하는 경우입니다ListView
.
그래서 해결책은 쉽습니다. 내 핸들러가 데이터를 가져오고 호출하기 위해 어댑터를 트리거 할 때 데이터 소스에서 어댑터로 데이터를 복사합니다 notifyDataSetChanged()
. 이제 충돌이 다시 발생하지 않습니다.
답변
이 문제가 간헐적으로 발생했다면 마지막 항목을 클릭 한 후 목록을 스크롤했을 때만이 문제가 발생했습니다. 목록이 스크롤되지 않으면 모든 것이 잘 작동합니다.
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 일 수 있습니다.