Изменение данных адаптера ListView без уведомления ListView
Я написал ListActivity, у которого есть собственный адаптер списка. Список обновляется из ContentProvider при запуске onCreate. У меня также есть служба, которая запускается при запуске приложения, и она сначала обновляет ContentProvider, а затем отправляет широковещательную рассылку о том, что контент был обновлен.
Мой ListActivity получает широковещательную рассылку и пытается обновить мой ListView. Моя проблема в том, что я получаю периодические ошибки об изменении данных адаптера ListView без уведомления ListView. Я звоню notifyDataSetChanged()
метод в моем списке адаптера сразу после его обновления. Кажется, что происходит, когда список все еще находится в процессе обновления после первого вызова в onCreate, когда он получает широковещательную рассылку от службы для обновления, поэтому он пытается обновить мой ListView до того, как закончится обновление с первого запуска. Имеет ли это смысл? Вот часть моего кода.
ПРИМЕЧАНИЕ. Служба работает правильно, она получает новые данные и обновляет мой ContentProvider, и я получаю широковещательную рассылку в своей деятельности, когда она обновляется.
@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;
}
2 ответа
Вы должны выполнить все свои обновления данных Адаптера в потоке пользовательского интерфейса, чтобы синхронизированный блок не был необходим. Это также бесполезно, так как ваша синхронизация на AsyncTask
который создается новый каждый раз, когда он выполняется.
Другая проблема заключается в том, что вы звоните notifyDataSetChanged
внешний по отношению к Adapter
, Вы должны позвонить в конце вашего setListItems
метод. Это не должно вызывать ошибок, поскольку оно выполняется в потоке пользовательского интерфейса, но не должно вызываться таким образом.
Вы должны убедиться, что ваш getReports
Метод не изменяет резервное хранилище Adapter
в любом случае. Так как он работает в отдельном потоке, он не может ничего изменить Adapter
имеет доступ тоже. Даже если он защищен замками. Что вам нужно сделать, это в вашем doInBackground
Метод - это создание списка обновлений или нового списка и т. д., и передача его в onPostExecute
который затем передает новые данные в Adapter
в потоке пользовательского интерфейса. Итак, если ваш getReports
функция меняется report_list
и ваш Adapter
имеет ссылку на report_list
вы делаете это не правильно. getReports
должен создать новый report_list
а затем передать это обратно вашему Adapter
когда он закончил создавать его в потоке пользовательского интерфейса.
Чтобы повторить, вы можете только изменить данные Adapter
и впоследствии ListView
имеет доступ тоже в потоке пользовательского интерфейса. Использование синхронизации / блокировок не меняет это требование.
Если вы хотите обновить представление списка пользовательского интерфейса из службы, вам следует вызвать notifyDataSetChanged() в onDestroy службы.
сделайте адаптер статичным из вашей основной деятельности и вызовите его adaptername.notifyDataSetChanged()
как это
@Override
public void onDestroy() {
if (MainActivity.isInFront == true) {
if (MainActivity.adapter != null)
MainActivity.adapter.notifyDataSetChanged();
MainActivity.listView.setAdapter(MainActivity.adapter);
}
}