【发布时间】:2017-03-10 12:42:58
【问题描述】:
The documentation for DiffUtil 建议在后台线程上生成DiffUtil.DiffResult,因为可能需要较长的计算时间。这对我来说似乎是个坏主意,因为该线程可能在如下情况下对陈旧数据进行操作(假设 list 访问是线程安全的):
- 向
list添加数据并通知适配器 - 需要将
list替换为newList,这将有一些添加和删除的差异 - 在后台调用
DiffUtil.calculateDiff并获取DiffResult对应list和newList,然后向将使用newList的主线程发布消息并调用DiffResult.dispatchUpdatesTo - 在处理该消息之前,用户在主线程上采取了导致
list突变的操作 - 消息已处理,
newList设置为新数据源,DiffResult.dispatchUpdatesTo运行导致底层数据视图不一致 + 自计算DiffResults以来所有突变丢失
那不好,让我们从第 3 步开始改变:
- 设置
newList为新数据源,后台调用DiffUtil.calculateDiff,获取list和newList对应的DiffResult,向主线程发消息,调用DiffResult.dispatchUpdatesTo - 在处理该消息之前,用户在主线程上执行导致
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