我写了一个ListActivity,它有一个自定义列表适配器.运行onCreate时,将从ContentProvider更新列表.我还有一个服务,在我运行应用程序时启动,它首先更新ContentProvider,然后发送内容已更新的广播.
我的ListActivity接收广播并尝试更新我的ListView.我的问题是,我收到有关ListView适配器数据更改的间歇性错误,而不通知ListView.我更新后立即调用我的列表适配器上的notifyDataSetChanged()方法.看起来似乎正在发生的事情是,当它接收来自服务的广播以进行更新时,列表仍然在第一次调用onCreate之后进行更新,因此它尝试更新我的ListView,直到它从第一次运行完成更新.这有意义吗?这是我的一些代码.
我的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,NZBService.class)); } private class updateReportList extends AsyncTask<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,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线程上更新Adapter数据,因此不需要同步块.它也是无用的,因为你在每次执行时都会创建新的AsyncTask同步.
另一个问题是您在Adapter外部调用notifyDataSetChanged.您应该在setListItems方法的末尾调用它.这不应该导致错误,因为它是在UI线程上执行但不应该以这种方式调用.
您应该确保您的getReports方法不以任何方式修改适配器的后备存储.由于它在单独的线程上运行,因此无法修改适配器也可访问的任何内容.即使它受到锁的保护.您需要做的是在doInBackground方法中生成更新列表或新列表等,并将其传递给onPostExecute,然后onPostExecute将新数据提交到UI线程上的Adapter.因此,如果您的getReports函数正在更改report_list,并且您的适配器具有对report_list的引用,那么您做错了. getReports必须创建一个新的report_list,然后在UI线程上完成创建后将其传递回适配器.
重申一下,您只能修改适配器的数据,然后ListView也可以在UI线程上访问.使用同步/锁定不会更改此要求.