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

DiffUtil未触发AdapterDataObserver问题排查及响应式方案修正

问题分析与解决方案

首先,你的AdapterDataObserver没触发的直接原因很容易忽略:你只重写了onChanged()方法,但DiffUtil默认会生成局部数据更新的通知(比如notifyItemInserted、notifyItemChanged等),这些通知会触发Observer的对应方法(如onItemRangeChanged、onItemRangeInserted),而不是全局的onChanged()。只有当DiffUtil计算出整个数据集完全替换时,才会调用onChanged()。

第一步:修复Observer的回调覆盖

先修改Fragment中的Observer,覆盖所有你需要监听的方法,比如:

adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
    override fun onChanged() {
        Log.d("Observer", "整个数据集发生变化")
    }

    override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
        Log.d("Observer", "局部数据变化: 从位置$positionStart开始,共$itemCount条")
    }

    override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
        Log.d("Observer", "插入数据: 从位置$positionStart开始,共$itemCount条")
    }

    // 根据业务需要,还可以覆盖onItemRangeRemoved、onItemRangeMoved等方法
})

这样你就能看到DiffUtil触发的回调了。

第二步:修复响应式实现的潜在问题

你的RxJava实现存在几个风险点,需要调整优化:

1. 数据源的线程安全与不可变性问题

你直接使用传入的MutableList<Message>,如果外部代码修改了这个列表,会导致DiffUtil计算差异时使用的新旧数据不一致。建议在setItems时创建新的列表副本,隔离外部修改:

private fun setItems(newItems: List<Message>) {
    items.clear()
    items.addAll(ArrayList(newItems)) // 创建副本,避免外部修改影响内部数据源
}

2. 订阅的内存泄漏风险

你的updater是PublishSubject,但没有在Adapter销毁时取消订阅,会导致内存泄漏。建议在Adapter中保存订阅的Disposable,并在onDetachedFromRecyclerView时销毁:

private var disposable: Disposable? = null

init {
    items = ArrayList()
    disposable = updater
        .debounce(500, TimeUnit.MILLISECONDS)
        .subscribeOn(Schedulers.computation())
        .map { newItems ->
            val diffResult = DiffUtil.calculateDiff(DiffUtilCallback(newItems, items), true)
            Pair(newItems, diffResult)
        }
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe { (newItems, diffResult) ->
            setItems(newItems)
            diffResult.dispatchUpdatesTo(this)
        }
}

override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
    super.onDetachedFromRecyclerView(recyclerView)
    disposable?.dispose()
}

这里我把setItems和dispatchUpdatesTo都移到了主线程的subscribe块中,确保数据源更新和UI通知都在主线程执行(之前的doOnNext是在computation线程,虽然修改List本身线程安全,但后续和UI相关的操作统一在主线程更稳妥)。

3. 确保DiffUtilCallback的正确性

你的DiffUtilCallback实现直接影响DiffUtil的差异计算结果,一定要保证这两个核心方法的正确性:

class DiffUtilCallback(
    private val newList: List<Message>,
    private val oldList: List<Message>
) : DiffUtil.Callback() {
    override fun getOldListSize() = oldList.size
    override fun getNewListSize() = newList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        // 必须用item的唯一标识判断是否为同一个对象,比如Message的id字段
        return oldList[oldItemPosition].id == newList[newItemPosition].id
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        // 判断item内容是否一致,建议重写Message的equals方法,或者逐一比较关键字段
        return oldList[oldItemPosition] == newList[newItemPosition]
    }
}

如果这两个方法实现错误,DiffUtil会计算出错误的差异,导致通知不符合预期。

总结

  1. 先覆盖Observer的所有相关回调方法,才能捕获DiffUtil的局部更新通知;
  2. 调整RxJava订阅的生命周期,避免内存泄漏;
  3. 确保数据源的不可变性和DiffUtilCallback的正确性,保障差异计算的准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:04:34