【问题标题】:RecyclerView and DiffUtil - A Concurrency NightmareRecyclerView 和 DiffUtil - 并发的噩梦
【发布时间】:2017-03-10 12:42:58
【问题描述】:

The documentation for DiffUtil 建议在后台线程上生成DiffUtil.DiffResult,因为可能需要较长的计算时间。这对我来说似乎是个坏主意,因为该线程可能在如下情况下对陈旧数据进行操作(假设 list 访问是线程安全的):

  1. list添加数据并通知适配器
  2. 需要将 list 替换为 newList,这将有一些添加和删除的差异
  3. 在后台调用DiffUtil.calculateDiff 并获取DiffResult 对应listnewList,然后向将使用newList 的主线程发布消息并调用DiffResult.dispatchUpdatesTo
  4. 在处理该消息之前,用户在主线程上采取了导致list 突变的操作
  5. 消息已处理,newList 设置为新数据源,DiffResult.dispatchUpdatesTo 运行导致底层数据视图不一致 + 自计算 DiffResults 以来所有突变丢失

那不好,让我们从第 3 步开始改变:

  1. 设置newList为新数据源,后台调用DiffUtil.calculateDiff,获取listnewList对应的DiffResult,向主线程发消息,调用DiffResult.dispatchUpdatesTo
  2. 在处理该消息之前,用户在主线程上执行导致newList 突变的操作,并通知适配器,导致数据视图不一致,因为尚未调用DiffResult.dispatchUpdatesTo

这方面有更多变体,但没有一个是好的。似乎将DiffUtil 与大型数据集和变更集可靠使用的唯一方法是禁用或排队所有更新,直到调用DiffResult.dispatchUpdatesTo

我是否遗漏了一些会使上述错误的东西?

【问题讨论】:

  • 首先,你有多少物品?如果不是几千,何必担心呢?
  • @pskink 如文档所述:1000 items and 200 modifications without moves: 13.54 ms, median: 13.36 ms。 1000 件也不算多。用 13.54 毫秒来做这件事让我们有 2.5 毫秒来完成该帧的所有其他工作,否则我们将丢帧。 2.5 毫秒不是很多时间。
  • 好的,所以使用dispatchUpdatesTo(ListUpdateCallback updateCallback),而不是dispatchUpdatesTo(Adapter adapter),这样您就可以控制如何处理您的更新(您可以跳过用户同时更改的项目的更新)
  • 这不是问题。问题是如果有一个巨大的更新需要大约 16 毫秒(或更多)。在主线程上执行此操作会阻塞它并导致 1 帧或更多帧丢失。现在在后台线程上执行此操作可能需要超过 16 毫秒(线程暂停等)。它也不必来自用户。更新可以来自网络或系统行为。
  • 在我自己的使用中,我有不可变的数据集,而不是修改单个列表。然后,我将它与 rx java 结合起来。 listDataChange.observeOn(backgroundThread).switchMap(computeDiff).observeOn(mainThread).subscribe(applyTheChange);

标签: android concurrency android-recyclerview diff android-support-library


【解决方案1】:

我最终在 DiffUtil 计算期间堆叠更新以管理用户交互,并且仍然仅在主线程中访问数据集。

我在那里写的:https://geoffreymetais.github.io/code/diffutil-threading/

【讨论】:

    【解决方案2】:

    看看 BatchingListUpdateCallback。它是类,封装了主回调,当列表发生多次更改时,batchingListCallback 将只通知一次主回调。

    https://developer.android.com/reference/android/support/v7/util/BatchingListUpdateCallback.html

    编辑

    对不起,我的回答不正确。

    我查看了源代码 DiffUtil。我明白了,BatchingListUpdateCallback 无论如何都在dispatchUpdatesTo 方法中使用。

     public void dispatchUpdatesTo(ListUpdateCallback updateCallback) {
            final BatchingListUpdateCallback batchingCallback;
            if (updateCallback instanceof BatchingListUpdateCallback) {
                batchingCallback = (BatchingListUpdateCallback) updateCallback;
            } else {
                batchingCallback = new BatchingListUpdateCallback(updateCallback);
                // replace updateCallback with a batching callback and override references to
                // updateCallback so that we don't call it directly by mistake
                //noinspection UnusedAssignment
                updateCallback = batchingCallback;
            }
    

    但作为正确方式的向量,这可能很有用:)

    【讨论】:

    • 有趣。因此,当新列表出现时,我们用 BatchListUpdateCallback 包装适配器并将 DiffUtils 结果分派给它。仍然存在并发问题,但这将使潜在的解决方案更易于实施。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-28
    • 2010-12-21
    • 2020-03-14
    相关资源
    最近更新 更多