tom*_*ash 187 android listview exception adapter
我想做什么:运行后台线程计算ListView内容并部分更新ListView,同时计算结果.
我知道我必须避免:我不能从后台线程中弄乱ListAdapter内容,所以我继承了AsyncTask并从onProgressUpdate发布结果(向适配器添加条目).我的适配器使用结果对象的ArrayList,这些arraylists上的所有操作都是同步的.
其他人的研究:有非常有价值的数据在这里.对于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)
[...]
Run Code Online (Sandbox Code Playgroud)
救命?不再需要了,见下文
最终答案:事实证明,我notifyDataSetChanged
每次调用5次以避免闪烁和突然更改列表.它不能这样做,总是在基本列表更改时通知适配器.这个错误现在已经消失了.
Mul*_*ins 116
我遇到过同样的问题.
我ArrayList
在UI线程的外部添加了项目.
解决方案:我已经完成了两个,adding the items
并notifyDataSetChanged()
在UI线程中调用.
gia*_*200 27
我有同样的问题,但我使用该方法修复它
requestLayout();
Run Code Online (Sandbox Code Playgroud)
来自全班 ListView
Jav*_*tor 20
这是一个MultiThreading问题并使用正确同步的块这可以防止.不在UI线程上添加额外的东西,导致应用程序的响应能力丧失.
我也面临同样的问题.而且最常见的答案表明,从UI Thread更改适配器数据可以解决问题.这将是有效的,但是一个快速简单的解决方案,但不是最好的解决方案.
正如你可以看到正常情况.从后台线程更新数据适配器并在UI线程中调用notifyDataSetChanged.
当ui线程正在更新视图而另一个后台线程再次更改数据时,会出现此illegalStateException.那一刻导致了这个问题.
因此,如果您将同步所有正在更改适配器数据的代码并进行notifydatasetchange调用.这个问题应该消失了.对我来说,我仍在更新后台线程的数据.
这是我的案例特定代码供其他人参考.
我的主屏幕上的加载程序将电话簿联系人加载到后台的数据源中.
@Override
public Void loadInBackground() {
Log.v(TAG, "Init loadings contacts");
synchronized (SingleTonProvider.getInstance()) {
PhoneBookManager.preparePhoneBookContacts(getContext());
}
}
Run Code Online (Sandbox Code Playgroud)
此PhoneBookManager.getPhoneBookContacts从电话簿中读取联系人并在哈希映射中填充它们.哪个可以直接用于列表适配器绘制列表.
我的屏幕上有一个按钮.这将打开列出这些电话号码的活动.如果我在上一个线程完成其工作之前直接在列表上设置了Adapter,那么快速导航的情况就会发生.它弹出了例外.这个问题的标题是什么.所以我必须在第二个活动中做这样的事情.
我在第二个活动中的加载程序等待第一个线程完成.直到它显示进度条.检查两个加载器的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());
}
}
}
Run Code Online (Sandbox Code Playgroud)
希望这可以帮助
tri*_*iad 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();
}
});
}
Run Code Online (Sandbox Code Playgroud)
我编写了这段代码并让它在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);
}
}
Run Code Online (Sandbox Code Playgroud)
小智 5
几天前我遇到了同样的问题,每天导致数千次崩溃,大约有 0.1% 的用户遇到这种情况。我试过setVisibility(GONE/VISIBLE)
and requestLayout()
,但崩溃次数只会减少一点。
我终于解决了它。什么都没有setVisibility(GONE/VISIBLE)
。什么都没有requestLayout()
。
最后我发现原因是我在更新数据后使用了一个Handler
来调用notifyDataSetChanged()
,这可能会导致一种:
checkForTap()
/onTouchEvent()
并最终调用layoutChildren()
)notifyDataSetChanged()
和更新视图我犯了另一个错误,在getCount()
, getItem()
and 中getView()
,我直接使用了 DataSource 中的字段,而不是将它们复制到适配器中。所以最终它在以下情况下崩溃:
getCount()
andgetView()
被调用,listview 发现数据不一致,抛出java.lang.IllegalStateException: The content of the adapter has changed but...
. 另一个常见的例外是IndexOutOfBoundException
如果您在ListView
.所以解决方案很简单,当我的 Handler 触发适配器获取数据并调用时,我只需将数据从我的 DataSource 复制到适配器notifyDataSetChanged()
。崩溃现在再也不会发生了。