我编写了一个具有自定义列表适配器的 ListActivity。当运行 onCreate 时,该列表正在从 ContentProvider 更新。我还有一项服务在我运行应用程序时启动,它首先更新 ContentProvider,然后发送内容已更新的广播。
我的 ListActivity 收到广播并尝试更新我的 ListView。我的问题是,我在没有通知 ListView 的情况下收到有关 ListView 适配器数据更改的间歇性错误。我在更新列表适配器后立即调用 notifyDataSetChanged()
方法。似乎正在发生的事情是,当它从服务接收到要更新的广播时,在第一次调用 onCreate 后,列表仍在更新过程中,因此它会尝试在第一次运行完成更新之前更新我的 ListView。这有意义吗?这是我的一些代码。
注意:该服务工作正常,它获取新数据并更新我的 ContentProvider,并且在更新时我确实在我的 Activity 中收到了广播。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ctx = this;
getPrefs();
setContentView(R.layout.main);
// Setup preference listener
preferences = PreferenceManager.getDefaultSharedPreferences(this);
preferences.registerOnSharedPreferenceChangeListener(listener);
// Setup report list adapter
ListView nzbLv = (ListView) findViewById(R.id.report_list);
nzbla = new NZBReportListAdaptor(ctx);
getReports();
nzbla.setListItems(report_list);
nzbLv.setAdapter(nzbla);
// Broadcast receiver to get notification from NZBService to update ReportList
registerReceiver(receiver,
new IntentFilter(NZBService.BROADCAST_ACTION));
startService(new Intent(ctx, NZBService.class));
}
@Override
public void onResume() {
super.onResume();
timerHandler.resume();
new updateSabQueue().execute();
//updateList();
}
@Override
public void onPause() {
super.onPause();
timerHandler.pause();
unregisterReceiver(receiver);
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
Toast.makeText(ctx, "NZBService broadcast recieved", Toast.LENGTH_SHORT).show();
updateReportList();
}
};
private void updateReportList() {
new updateReportList().execute();
}
private class updateReportList extends AsyncTask<Void, Void, Boolean> {
/* (non-Javadoc)
* @see android.os.AsyncTask#onPreExecute()
* Show progress dialog
*/
protected void onPreExecute() {
}
/* (non-Javadoc)
* @see android.os.AsyncTask#doInBackground(Params[])
* Get new articles from the internet
*/
protected Boolean doInBackground(Void...unused) {
getReports();
return true;
}
/**
* On post execute.
* Close the progress dialog
*/
@Override
protected void onPostExecute(Boolean updated) {
if (updated) {
Log.d(TAG, "NZB report list adapter updated");
synchronized(this) {
nzbla.setListItems(report_list);
}
Log.d(TAG, "NZB report list notified of change");
nzbla.notifyDataSetChanged();
}
}
}
既然这个问题已经得到解答,我将发布更新后的代码,以帮助可能遇到它的其他人。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ctx = this;
getPrefs();
setContentView(R.layout.main);
// Setup preference listener
preferences = PreferenceManager.getDefaultSharedPreferences(this);
preferences.registerOnSharedPreferenceChangeListener(listener);
// Setup report list adapter
ListView nzbLv = (ListView) findViewById(R.id.report_list);
nzbla = new NZBReportListAdaptor(ctx);
report_list.addAll(getReports());
nzbla.setListItems(report_list);
nzbLv.setAdapter(nzbla);
// Broadcast receiver to get notification from NZBService to update ReportList
registerReceiver(receiver,
new IntentFilter(NZBService.BROADCAST_ACTION));
startService(new Intent(ctx, NZBService.class));
}
private class updateReportList extends AsyncTask<Void, Void, ArrayList<Report>> {
/* (non-Javadoc)
* @see android.os.AsyncTask#onPreExecute()
* Show progress dialog
*/
protected void onPreExecute() {
}
/* (non-Javadoc)
* @see android.os.AsyncTask#doInBackground(Params[])
* Get new articles from the internet
*/
protected ArrayList<Report> doInBackground(Void...unused) {
return getReports();
}
/**
* On post execute.
* Close the progress dialog
*/
@Override
protected void onPostExecute(ArrayList<Report> updated) {
nzbla.setListItems(updated);
nzbla.notifyDataSetChanged();
}
}
private ArrayList<Report> getReports() {
ArrayList<Report> reports = new ArrayList<Report>();
ContentResolver r = getContentResolver();
Cursor c = r.query(NZBReportProvider.CONTENT_URI, null, null, null, NZBReportProvider.ARTICLE_KEY_ROWID + " DESC");
startManagingCursor(c);
Log.d(TAG, "NZBReport cursor.getCount=" + c.getCount());
int title = c.getColumnIndex(NZBReportProvider.ARTICLE_KEY_TITLE);
int desc = c.getColumnIndex(NZBReportProvider.ARTICLE_KEY_DESCRIPTION);
int cat = c.getColumnIndex(NZBReportProvider.ARTICLE_KEY_CAT);
int size = c.getColumnIndex(NZBReportProvider.ARTICLE_KEY_SIZE);
int link = c.getColumnIndex(NZBReportProvider.ARTICLE_KEY_LINK);
int catid = c.getColumnIndex(NZBReportProvider.ARTICLE_KEY_CATID);
int date = c.getColumnIndex(NZBReportProvider.ARTICLE_KEY_DATE_ADDED);
int group = c.getColumnIndex(NZBReportProvider.ARTICLE_KEY_GROUP);
if (c.getCount() > 0) {
c.moveToFirst();
do {
URL url = null;
try {
url = new URL(c.getString(link));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
reports.add(new Report(c.getString(title), url, c.getString(desc), c.getString(cat), c.getString(date), c.getString(size), c.getInt(catid), c.getString(group)));
} while (c.moveToNext());
}
return reports;
}
最佳答案
您必须在 UI 线程上完成适配器数据的所有更新,因此不需要同步块(synchronized block)。它也没有用,因为您在 AsyncTask
上进行同步,每次执行时都会创建新的。
另一个问题是您在 Adapter
外部调用 notifyDataSetChanged
。您应该在 setListItems
方法的末尾调用它。这不应该导致错误,因为它是在 UI 线程上执行的,但不应该以这种方式调用。
您应该确保您的getReports
方法不会以任何方式修改Adapter
的后备存储。由于它在单独的线程上运行,因此它不能修改 Adapter
也可以访问的任何内容。即使它受到锁的保护。您需要做的是在您的 doInBackground
方法中生成更新列表或新列表等,并将其传递给 onPostExecute
然后将新数据提交给UI 线程上的 Adapter
。因此,如果您的 getReports
函数正在更改 report_list
并且您的 Adapter
引用了 report_list
,那么您做错了. getReports
必须创建一个新的 report_list
,然后在它在 UI 线程上完成创建后将其传递回您的 Adapter
。
重申一下,您只能修改 Adapter
的数据,随后 ListView
也可以在 UI 线程上访问。使用同步/锁不会改变此要求。
关于android - ListView adapter 数据变化,ListView 未被通知,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4636679/