android – ListView适配器数据更改没有通知ListView

我写了一个ListActivity,它有一个自定义列表适配器.运行onCreate时,将从ContentProvider更新列表.我还有一个服务,在我运行应用程序时启动,它首先更新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,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线程上访问.使用同步/锁定不会更改此要求.

相关文章

AdvserView.java package com.earen.viewflipper; import an...
ImageView的scaleType的属性有好几种,分别是matrix(默认)...
文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win1...
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由main...
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件...
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项...