You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RecyclerView中DiffUtil连续插入多Item无法全部显示的技术问询

RecyclerView + AsyncListDiffer 连续插入多个Item显示异常问题

我在开发中使用RecyclerView搭配DiffUtil管理数据列表变更,遇到如下问题:当通过AsyncListDiffer和DiffUtil连续向Adapter插入多个Item时,RecyclerView无法正确显示所有Item,有时仅显示最后一个插入项,或仅部分项显示。

相关代码

Adapter数据处理代码

// 首次加载所有数据正常
fun addAll(items:List<ItemEntity>){
    asyncListDiffer.submitList(items)
}
 
fun add(item: ItemEntity) {
    val updateList = asyncListDiffer.currentList.toMutableList()
    updateList.add(item)
    asyncListDiffer.submitList(updateList.toList())
}

DiffUtil实现

private class DiffCallback:DiffUtil.ItemCallback<ItemEntity>(){
    override fun areItemsTheSame(oldItem: ItemEntity, newItem: ItemEntity): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: ItemEntity, newItem: ItemEntity): Boolean {
        return oldItem == newItem
    }
}

ViewModel核心逻辑

@HiltViewModel
class MainViewModel @Inject constructor(private val repository:MainRepository):ScopedViewModel() {

    private var _allItems:MutableLiveData<List<ItemEntity>> = MutableLiveData()
    val allItems:LiveData<List<ItemEntity>> = _allItems

    private var _itemInserted:MutableLiveData<ItemEntity> = MutableLiveData()
    val itemInserted:LiveData<ItemEntity> = _itemInserted

    init{
        initScope()
    }

    // 应用启动时加载所有数据
    fun fetchAllItems(){
        launch{
            _allItems.value=repository.fetchAllItems()
        }
    }

    fun saveNewItem(itemEntity: ItemEntity){
        launch {
            val idInserted=repository.saveNewItem(itemEntity)
            getItemById(idInserted)
        }
    }

    fun getItemById(itemId:Long){
        launch {
            _itemInserted.value=repository.fetchItemById(itemId)
        }
    }

    override fun onCleared() {
        destroyScope()
        super.onCleared()
    }
}

Fragment观察者逻辑

private fun setUpObservers(){
    mainViewModel.fetchAllItems()

    mainViewModel.allItems.observe(viewLifecycleOwner){
        if (it.isNotEmpty()) {
            adapter.addAll(it)
        }
    }

    mainViewModel.itemInserted.observe(viewLifecycleOwner){item->
        item?.let{
            adapter.add(item)
        }
    }
}

多文件选择处理逻辑

private fun mActivityResult(){
    launcherOpenMultipleDocs= registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()){uris->
        uris.forEach {uri->
            val realPathFromFile = getRealPathFromURI(uri!!, requireContext())
            mainViewModel.saveNewItem(
                ItemEntity(
                    pathLocation = realPathFromFile,
                    timestamp = Date().time
                )
            )
        }
    }
}

问题

  1. 如何确保DiffUtil能正确处理RecyclerView中的连续多Item插入?
  2. 使用AsyncListDiffer和DiffUtil处理快速Item插入时,还有哪些需要注意的事项?

解决方案

问题1:确保连续多Item插入正确显示

问题根源在于:连续调用add(item)时,asyncListDiffer.currentList可能还未完成上一次提交的更新,导致每次获取的都是旧列表,多次操作会覆盖之前的变更,最终仅保留最后一次插入结果。

有两种可靠处理方式:

方式1:批量提交新增Item

避免循环单次调用add(item),而是收集短时间内的所有新增Item,一次性提交:

private val pendingItems = mutableListOf<ItemEntity>()
private val handler = Handler(Looper.getMainLooper())
private val submitRunnable = Runnable {
    val updateList = asyncListDiffer.currentList.toMutableList().apply { addAll(pendingItems) }
    asyncListDiffer.submitList(updateList)
    pendingItems.clear()
}

// 修改Fragment中的itemInserted观察者逻辑
mainViewModel.itemInserted.observe(viewLifecycleOwner){ item->
    item?.let {
        pendingItems.add(it)
        handler.removeCallbacks(submitRunnable)
        // 合并100ms内的批量插入,可根据需求调整时长
        handler.postDelayed(submitRunnable, 100)
    }
}

也可以直接修改ViewModel,支持批量保存Item,一次性返回所有新增数据后批量提交到Adapter。

方式2:确保基于最新列表更新

修改Adapter的add方法,利用submitList的原子性避免竞态条件:

fun add(item: ItemEntity) {
    // 直接基于当前最新的currentList创建新列表并提交
    asyncListDiffer.submitList(asyncListDiffer.currentList.toMutableList().apply { add(item) })
}

同时检查ItemEntity的equals方法是否正确实现,因为areContentsTheSame依赖该方法判断内容是否变化,逻辑错误会导致DiffUtil误判。


问题2:快速Item插入的注意事项

  • 避免频繁单次提交:连续多次调用submitList会触发多次DiffUtil计算,性能差且易引发竞态条件,尽量合并批量操作。
  • 保证Item的id唯一稳定:areItemsTheSame依赖id判断是否为同一Item,新增Item的id必须是唯一值(比如数据库自增ID),否则DiffUtil无法识别新增项。
  • 禁止直接修改currentList:asyncListDiffer.currentList是不可变对象,必须通过toMutableList()创建副本后修改,再提交新列表。
  • 优化DiffUtil计算:重写getChangePayload方法,仅更新Item变化的部分视图,提升快速插入时的性能。
  • 主线程提交列表:submitList必须在主线程调用,AsyncListDiffer会在后台计算Diff,但提交操作需确保线程安全。
  • 避免重复提交相同列表:两次提交的列表引用不能相同,每次提交都要创建新的列表实例(比如用toList()生成新列表),否则AsyncListDiffer会忽略本次提交。

内容的提问来源于stack exchange,提问作者Barry Zea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 06:12:35