Изменение данных адаптера 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);
               }
}               
Другие вопросы по тегам