【问题标题】:What's the best-practice way to update an Adapter's underlying data?更新适配器的基础数据的最佳实践方法是什么?
【发布时间】:2011-02-14 03:00:47
【问题描述】:

我遇到了 IllegalStateException,将基础列表更新为适配器(可能是 ArrayAdapter 或 BaseAdapter 的扩展,我不记得了)。我目前没有或不记得异常的文本,但它说明了列表内容更改的效果,而适配器没有收到更改通知。

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

如果我在更新前将 ListView 的可见性设置为 GONE,然后再次 VISIBLE,则不会发生错误。但这并不总是可行的。

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

我想我要问的是,从 UI 以外的线程更新底层 List 是否安全?此外,如果我想修改适配器中的数据,我是修改底层列表还是适配器本身(通过其 add() 等方法)。通过Adapter修改数据好像不对。

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

为了让您更好地了解我的特定问题,我将描述一下我的列表、适配器等是如何设置的。

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

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

我添加了很多日志输出,并确定当这个 IllegalStateExcption 发生时,我的 Observer 回调从未被调用。所以就好像 Adapter 在 Observer 有机会通知它的 Observers 之前就注意到了 List 的变化,并调用我的方法通知 Adapter 内容发生了变化。

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

我意识到这是一篇非常开放式的帖子,但我对此有点迷茫,如果有任何关于我所写内容的 cmets,我将不胜感激。

【问题讨论】:

    标签: android


    【解决方案1】:

    此列表/可能/更新自 UI 以外的另一个线程 线程(主)

    那行不通。

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

    MVC 与线程无关。

    是否可以安全更新 来自其他线程的底层列表 比用户界面?

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

    另外,如果我想修改 适配器中的数据,我要修改吗 底层列表或适配器 本身(通过其 add() 等方法)。 通过 Adapter 修改数据 好像错了。

    您通过Adapter 本身为ArrayAdapter 修改您的Adapter。您通过CursorAdapter 的底层数据库/内容提供程序修改您的Adapter。其他适配器可能会有所不同。

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

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

    这个 Activity,在它的 onCreate() 方法,注册一个 Observer 监听器 到我的 Queue 对象。在观察者 我调用的 update() 方法 适配器上的 notifyDataSetChanged()。

    Adapters 应该对自己调用notifyDataSetChanged()(如果更改是由他们进行的),或者由正在更改数据的实体调用它们(例如,Cursor 用于CursorAdapter)。 就是MVC。 Activity 不应该知道也不关心您的数据模型何时发生变化。

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

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

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

    不特别,恕我直言。

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

    如果您不强制将更新返回到主应用程序线程,则在您清除其他问题后,它最终会崩溃。

    给 Queue 对象一个 Handler 到 UI线程创建时,并制作 所有使用它的列表修改 处理程序,但这似乎不合适

    您可以使用Handler,也可以在附加的ListView 上调用post()

    即兴创作,我将创建一个名为ThreadSafeArrayAdapterArrayAdapter 子类,并使用它来代替QueueThreadSafeArrayAdapteradd()insert()remove() 替换为具有超类在主应用程序线程上通过 Handlerpost() 执行其操作的那些。

    【讨论】:

    • 感谢您的 cmets,但如果您不介意进一步提供帮助,我仍然对一些事情感到困惑。 “队列”对象名称有点用词不当——它更像是要播放的视频或要播放的歌曲的媒体队列。它是一个带有 currentIndex int 的项目列表。我理解适配器只是我的模型(我的列表)和视图(列表视图)之间的桥梁。如果我需要从另一个我无权访问适配器的 Activity 更新视图显示的数据怎么办?如果执行此添加的任务可能会阻塞,则可能从 Runnable 更新基础列表似乎很自然。
    • “我理解适配器只是我的模型(我的列表)和视图(列表视图)之间的桥梁。”这通常是正确的。 “如果我需要从另一个我无权访问适配器的 Activity 更新视图显示的数据怎么办?”很可能,您也无权访问列表,因此您将需要更详细的策略。 “如果执行此添加的任务可能会阻塞,则可能从 Runnable 更新基础列表似乎很自然。”恕我直言,线程问题是一个 UI 问题,这就是为什么我建议您的适配器成为处理线程安全的适配器。
    • 但是,您可以随心所欲地实现它,只要您只在主应用程序线程上更新适配器即可。
    • 重新阅读您的回复后,我看到“更新本身必须在主应用程序线程上处理,对于当前附加到 ListView 的适配器”回答了我评论中的问题。我感到困惑的场景是:我的“队列”对象是在服务中的主线程上创建的。该服务将定期检查网络服务以查找要播放的新歌曲。因为这个进程可能会阻塞,所以这是在一个单独的线程中完成的。此时,从 Thread 中,我将 add() 新项目添加到列表中。我想这应该通过 main 上的 Handler 来完成。
    • 抱歉,写的时候心情不好。我对仅将您的数据模型保存在内存中有些怀疑——如果它值得下载,它可能值得存储在数据库中。如果是这样,那么你的一些方法最终会改变。 Android 对真正 MVC 的支持充其量是有限的,当您使用数据库时,唯一明智的方法是让 M 只是数据库本身,这意味着您有一个非常愚蠢的模型。您还可以使用某种形式的 CursorAdapter 来包裹您的数据库查询,以在屏幕上表示数据等。
    【解决方案2】:

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

    我个人使用我的自定义线程(扩展 Thread 的类),但通过 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 更容易。

    【讨论】:

    • 小心非静态内部处理程序类。这可能是内存泄漏的风险。
    猜你喜欢
    • 1970-01-01
    • 2019-10-23
    • 1970-01-01
    • 2015-11-22
    • 1970-01-01
    • 2017-09-11
    • 1970-01-01
    • 2020-11-08
    • 1970-01-01
    相关资源
    最近更新 更多