ListView 适配器数据更改而不通知 ListView [英] ListView adapter data change without ListView being notified

查看:29
本文介绍了ListView 适配器数据更改而不通知 ListView的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了一个具有自定义列表适配器的 ListActivity.当 onCreate 运行时,列表正在从 ContentProvider 更新.我还有一个在我运行应用程序时启动的服务,它首先更新 ContentProvider,然后发送内容已更新的广播.
我的 ListActivity 接收广播并尝试更新我的 ListView.我的问题是,在没有通知 ListView 的情况下,我收到有关 ListView 适配器数据更改的间歇性错误.我在更新列表适配器后立即调用它的 notifyDataSetChanged() 方法.看起来正在发生的事情是,当它从服务接收到要更新的广播时,在第一次调用 onCreate 之后,列表仍在更新过程中,因此它会尝试在我的 ListView 从第一次运行完成更新之前更新它.这有意义吗?这是我的一些代码.

I've written a ListActivity that has a custom list adapter. The list is being updated from a ContentProvider when onCreate is run. I also have a service that gets started when I run the app and it first updates the ContentProvider, then sends a Broadcast that the content has been updated.
My ListActivity receives the broadcast and tries to update my ListView. My problem is, I'm getting intermittent errors about the ListView adapter data changing without the ListView being notified. I call the notifyDataSetChanged() method on my list adapter right after I update it. What it seems like is happening is the list is still in the process of being updated after first call in onCreate when it receives the broadcast from the service to update, so it tries to update my ListView before it's finished updating from it's first run. Does this make sense? Here is some of my code.

注意:该服务运行正常,它获取新数据并更新我的 ContentProvider,并且在更新时我确实在我的活动中收到广播.

NOTE: The service is working properly, it gets new data and updates my ContentProvider, and I do get the broadcast in my activity when it is updated.

@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();                           
        }
    }
}

既然已经回答了这个问题,我将发布我更新的代码,以帮助可能遇到它的其他人.

Now that this question is answered, I will post my updated code in an effort to help others who might come across it.

@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 线程上完成所有适配器数据的更新,因此同步块不是必需的.它也是无用的,因为您在每次执行时都会创建新的 AsyncTask 进行同步.

You have to do all your updating of Adapter data on the UI thread so the synchronized block isn't necessary. It's also useless since your synchronizing on the AsyncTask which is created new each time it's executed.

另一个问题是您正在调用 Adapter 外部的 notifyDataSetChanged.您应该在 setListItems 方法的末尾调用它.这不应该导致错误,因为它是在 UI 线程上执行的,但不应该以这种方式调用.

Another problem is that you are calling notifyDataSetChanged external to the Adapter. You should call it at the end of your setListItems method. That shouldn't be causing errors though since it's being executed on the UI thread but it should not be called in that way.

您应该确保您的 getReports 方法不会以任何方式修改 Adapter 的后备存储.由于它在单独的线程上运行,因此它也不能修改 Adapter 可以访问的任何内容.即使它被锁保护.您需要做的是在您的 doInBackground 方法中生成更新列表或新列表等,并将其传递到 onPostExecute 然后将新数据提交到UI 线程上的 Adapter.因此,如果您的 getReports 函数正在更改 report_list 并且您的 Adapter 引用了 report_list,那么您就做错了.getReports 必须创建一个新的 report_list,然后在 UI 线程上完成创建后将其传回给您的 Adapter.

You should make sure that your getReports method is not modifying the backing store of the Adapter in any way. Since it is running on a separate thread it cannot modify anything the Adapter has access too. Even if it's protected by locks. What you need to do is in your doInBackground method is generate the list of updates or a new list, etc., and pass that into onPostExecute which then commits the new data to the Adapter on the UI thread. So, if your getReports function is changing report_list and your Adapter has a reference to report_list you are doing it wrong. getReports must create a new report_list and then pass that back to your Adapter when it's finished creating it on the UI thread.

重申一下,您只能修改 Adapter 的数据,随后 ListView 也可以在 UI 线程上访问.使用同步/锁不会改变这一要求.

To reiterate, you can only modify data the Adapter and subsequently the ListView has access too on the UI thread. Using synchronization/locks does not change this requirement.

这篇关于ListView 适配器数据更改而不通知 ListView的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆