更新Adapter的基础数据的最佳实践方法是什么?

时间:2010-04-25 06:23:04

标签: android

我遇到IllegalStateException将基础List更新为适配器(可能是ArrayAdapter或BaseAdapter的扩展,我不记得了)。我目前没有或记住了异常的文本,但它说明了List的内容发生变化,而没有得到适配器通知变更。

此列表/可以/从UI线程(主)以外的其他线程更新。更新此列表(添加项目)后,我调用notifyDataSetChanged。问题似乎是适配器或附加到适配器的ListView在调用此方法之前尝试更新自身。发生这种情况时,抛出IllegalStateException。

如果我在更新之前将ListView的可见性设置为GONE,那么再次可见,则不会发生错误。但这并不总是实用的。

我在某处读到你无法从另一个线程修改底层 - 这似乎限制了一个MVC模式,就像这个特定的List一样,我想从不同的线程中添加项目。我假设只要我调用notifyDataSetChanged()我就是安全的 - 在调用此方法之前,适配器没有重新访问底层列表,但似乎并非如此。

我想我要问的是,从UI以外的线程更新基础List是否安全?此外,如果我想修改适配器中的数据,我是否修改基础列表或适配器本身(通过其add()等方法)。通过适配器修改数据似乎是错误的。

我在另一个网站上遇到了一个似乎与我有类似问题的人:http://osdir.com/ml/Android-Developers/2010-04/msg01199.html(这是我抓住了Visibility.GONE和.VISIBLE的想法)。

为了让您更好地了解我的特定问题,我将介绍一下我的列表,适配器等的设置方法。

我有一个名为Queue的对象,它包含一个LinkedList。队列扩展了Observable,当通过其方法将事物添加到其内部列表时,我调用setChanged()和notifyListeners()。此Queue对象可以在任意数量的线程中添加或删除项目。

我有一个包含适配器的“队列视图”活动。此活动在其onCreate()方法中将Observer侦听器注册到我的Queue对象。在Observer的update()方法中,我在Adapter上调用notifyDataSetChanged()。

我添加了大量的日志输出并确定当发生IllegalStateExcption时,我的Observer回调从未被调用过。因此,在观察者有机会通知其观察者之前,适配器注意到List的更改,并调用我的方法通知适配器内容已更改。

所以我想我要问的是,这是一个装配适配器的好方法吗?这是一个问题,因为我正在从UI线程以外的线程更新适配器的内容吗?如果是这种情况,我可能会想到一个解决方案(在创建时将Queue对象赋予UI线程,并使用该Handler进行所有List修改,但这似乎不合适。)

我意识到这是一个非常开放的帖子,但我对此感到有些失落,并会对我写的内容有任何评论。

2 个答案:

答案 0 :(得分:8)

  

此列表/可能/可从中更新   除UI之外的另一个线程   线程(主要)

那不行。

  

我读到某个你不能的地方   修改底层的   另一个线程 - 这似乎是   限制MVC模式,就像这样   特别列表,我想添加项目   来自不同的线程

MVC与线程无关。

  

可以安全地更新   来自其他线程的底层列表   比UI?

没有。其他线程可以触发对适配器的更新(例如,通过post()),但是必须在主应用程序线程上处理更新本身,对于当前附加到ListView的适配器。

  

另外,如果我想修改   适配器中的数据,我可以修改吗?   基础列表或适配器   本身(通过其add()等方法)。   通过适配器修改数据   似乎错了。

您可以Adapter通过Adapter修改ArrayAdapter。您可以通过Adapter的基础数据库/内容提供商修改CursorAdapter。其他适配器可能会有所不同。

  

我有一个名为Queue的对象   包含LinkedList。队列扩展   可观察的,以及添加的东西   通过它的内部列表   方法,我调用setChanged()和   notifyListeners()。

您是否考虑过使用LinkedBlockingQueue,而不是实施自己的线程安全Queue

  

此活动,在其onCreate()   方法,注册一个Observer监听器   到我的队列对象。在观察者的   我打电话给update()方法   适配器上的notifyDataSetChanged()。

Adapters应该自己调用notifyDataSetChanged()(如果他们做出更改)或者让正在更改数据的实体调用它们(例如,CursorCursorAdapter一个Activity)。 是MVC。 ArrayAdapter在您的数据模型发生变化时既不知道也不关心。

  

所以就好像适配器注意到了   列表在观察者之前的变化   有机会通知其观察员,并且   调用我的方法通知适配器   内容已经改变。

可能你正在使用ArrayAdapter,在这种情况下,所有这些额外的观察者/通知内容都会妨碍你,因为这是为你处理的。您只需要安排更新主应用程序线程上的Handler

  

所以我想我要问的是,是   这是装配适配器的好方法吗?

不是特别,恕我直言。

  

这是一个问题,因为我正在更新   适配器的内容来自一个线程   除了UI线程?

如果您没有强制更新回主应用程序线程,一旦您解决了其他问题,这最终会崩溃。

  

给Queue对象一个Handler   UI线程创建时,make   所有List修改使用它   处理程序,但这似乎不合适

您可以使用post(),也可以在附加的ListView上致电ArrayAdapter

关闭袖口,我将创建一个名为ThreadSafeArrayAdapter的{​​{1}}子类,并使用它代替QueueThreadSafeArrayAdapteradd()insert()remove()替换为具有超类在主应用程序线程上执行其操作的Handler或{{1} }}

答案 1 :(得分:0)

总的好建议是http://developer.android.com/resources/articles/painless-threading.html

我个人使用自定义线程(扩展线程的类),但通过Message发送响应给UI线程。所以在线程的run()函数中有:

Message msg;
msg = Message.obtain();
msg.what = MSG_IMG_SET;                     
mExtHandler.sendMessage(msg);

mExtHandler已分配给线程构造函数中的外部处理程序实例。 UI线程定义了消息处理程序:

private Handler mImagesProgressHandler;

public void onCreate(Bundle bundle) {

    mImagesProgressHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {             
        case LoadImagesThread.MSG_IMG_SET:
            mArrayAdapter.setBitmapList(mImagesList);
            mArrayAdapter.notifyDataSetChanged();
            break;
        case LoadImagesThread.MSG_ERROR:
            break;
        }
        super.handleMessage(msg);
    }
};                 

这实际上比AsyncTask更容易。