RecyclerView数据更新神器 进化版- AsyncListDiffer

本文介绍了Android中的AsyncListDiffer,它是DiffUtil的进化版,用于更高效地更新RecyclerView数据。在后台线程计算差异,并在主线程更新UI。核心类包括DiffUtil.ItemCallback、AsyncDifferConfig及其Builder。通过使用AsyncListDiffer,开发者可以避免手动处理线程和DiffUtil的复杂性,简化数据更新流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

RecyclerView数据更新神器 - DiffUtil 译文中,已经了解到DiffUtil如何神助RecyclerView进行UI更新。它有一个缺陷就是DiffUtil在计算新旧数据集差异时需要开启线程,而在更新UI时又要在主线程。尽管可以这么做:

  • Thread + Handler
  • RxJava

虽然这样可以实现,但又显得笨拙。在support-v7:27.1.0又新增了一个DiffUtil的封装类 - AsyncListDiffer,它在后台线程使用DiffUtil计算旧数据集->新数据集的最小量,同时又在主线程中将更新操作的最小量分发给ListUpdateCallback,以完成数据更新,从而弥补了DiffUtil的缺陷。

核心类

DiffUtil.ItemCallback

与DIffUtil.Callback方法类似,DiffUtil在计算旧数据集->新数据集时,回调它。在DiffUtil.Callback中,提供两种处理角色 ->Item的列表索引和Item差异。而,在DiffUtil.ItemCallback中只处理Item差异。它允许从UI和内容特定的差异代码中分离索引到数组或者List中。

/**
 * DiffUtil在计算两个列表之间的非空Item的差异时使用的回调类。
 * DiffUtil.Callback提供两种处理角色 - 列表索引和item差异。而,DiffUtil.ItemCallback只处理第二个,它允许从UI和内容特定的差异代码中分离索引到数组或者List中。
 */
public abstract static class ItemCallback<T> {
    /**
     * 用来判断 两个对象是否是相同的Item
     * 例如,如果你的Item有唯一的id字段,这个方法就 判断id是否相等。
     */
    public abstract boolean areItemsTheSame(T oldItem, T newItem);

    /**
     * 当它想要检查两个Item是否具有相同的数据时由DiffUtil调用。
     * DiffUtil使用返回的信息来判断内容是否已更改
     * DiffUtil 用这个方法替代equals方法去检查是否相等,以便根据UI更改其行为
     * 例如,如果你用RecyclerView.Adapter配合DiffUtil使用,需要返回Item的视觉表现是否相同。
     * 这个方法仅仅在areItemsTheSame()返回true时,才调用。
     * 
     * 返回true,表示新旧数据集中,当前位置的item内容相同,否则,不同
     */
    public abstract boolean areContentsTheSame(T oldItem, T newItem);

    /**
     * 当areItemsTheSame(int, int)返回true并且areContentsTheSame(int, int)返回false时,DiffUtil才会调用此方法,以获取该item改变的payload
     * 例如,如果你用RecyclerView配合DiffUtils,你可以返回  这个Item改变的那些字段,RecyclerView.ItemAnimator可以使用哪些信息执行动画
     * 默认的实现是返回null
     *  返回 一个 代表着新老item的改变内容的 payload对象
     */
    @SuppressWarnings({"WeakerAccess", "unused"})
    public Object getChangePayload(T oldItem, T newItem) {
        return null;
    }
}

AsyncDifferConfig

AsyncDifferConfig是ListAdapter,AsyncListDiffer和后台线程的配置对象。在其内,至少定义一个DiffUtil.ItemCallback,用于DiffUtil在计算旧数据集->新数据集时回调。

AsyncDifferConfig.Builder

AsyncDifferConfig.Builder是AsyncDifferConfig构建类,主要是用来配置Diff计算数据集差异时的后台线程和DiffUtil.ItemCallback回调。

  • AsyncDifferConfig.Builder (DiffUtil.ItemCallback diffCallback)

在创建AsyncDifferConfig.Builder是必须传递一个DiffUtil.ItemCallback对象,用于DiffUtil计算数据集差异时回调。

当AsyncDifferConfig.Builde调用build()创建AsyncDifferConfig对象时,如果未设置指定的后台线程,那么将自动创建一个主线程Executor和长度为2的线程池。也就是说AsyncDifferConfig持有一个长度为2的线程池,由所有的AsyncListDiffer对象共用。如果想指定DiffUtil计算的后台线程,可调用setBackgroundThreadExecutor(Executor executor)方法

核心方法

  • getCurrentList(): 获取当前数据集
  • submitList(List newList):更新新数据集

核心代码解析

每次在更新数据集时,都需要调用AsyncListDiffer的submitList(List<T> newList)方法,下面来看下,这个方法中做了哪些事:

1   public void submitList(final List<T> newList) {
2       if (newList == mList) {
3            // nothing to do
4            return;
5        }
6
7        // 用于临时
8        final int runGeneration = ++mMaxScheduledGeneration;
9
10        if (newList == null) {
11            //noinspection ConstantConditions
12            mUpdateCallback.onRemoved(0, mList.size());
13            mList = null;
14            mReadOnlyList = Collections.emptyList();
15            return;
16        }
17
18        if (mList == null) {
19            // fast simple first insert
20            mUpdateCallback.onInserted(0, newList.size());
21            mList = newList;
22            mReadOnlyList = Collections.unmodifiableList(newList);
23            return;
24        }
25
26        final List<T> oldList = mList;
27        mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
28            @Override
29            public void run() {
30                final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
31                   @Override
32                    public int getOldListSize() {
33                        return oldList.size();
34                    }
35
36                    @Override
37                    public int getNewListSize() {
38                        return newList.size();
39                    }
40
41                    @Override
42                    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
43                        return mConfig.getDiffCallback().areItemsTheSame(
44                                oldList.get(oldItemPosition), newList.get(newItemPosition));
45                    }
46
47                    @Override
48                    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
49                        return mConfig.getDiffCallback().areContentsTheSame(
50                                oldList.get(oldItemPosition), newList.get(newItemPosition));
51                    }
52                });
53
54                mConfig.getMainThreadExecutor().execute(new Runnable() {
55                    @Override
56                    public void run() {
57                        if (mMaxScheduledGeneration == runGeneration) {
58                            latchList(newList, result);
59                        }
60                    }
61                });
62            }
63        });
64    }

65   private void latchList(@NonNull List<T> newList, @NonNull DiffUtil.DiffResult diffResult) {
66        diffResult.dispatchUpdatesTo(mUpdateCallback);
67        mList = newList;
68        mReadOnlyList = Collections.unmodifiableList(newList);
69    }

在2-5行中,首先判断newList与AsyncListDiffer中缓存的数据集mList是否为同一个对象,如果是的话,直接返回。这也就是说,调用submitList()方法所传递的数据集时,需要new一个新的List

在8行,定义了变量runGeneration,用于缓存当前预执行线程的次数的最大值。至于作用后续再说。

在10-16行中,判断newList是否为null。若newList为null,将移除所有Item的操作分发给ListUpdateCallback,mList置为null,同时将只读List - mReadOnlyList清空,最后返回。

在18-24行中,判断newList是否为null。若mList为null,表示这是第一次向Adapter添加数据集,此时将添加最新数据集操作分发给ListUpdateCallback,将mList设置为newList,同时以newList为原本重建创建只读List副本 - mReadOnlyList,最后返回。

在27-63中,AsyncDifferConfig对象获取后台线程,并运行该后台线程。在该线程中,创建一个新的DiffUtil.Callback()回调同时DiffUtil在计算旧数据集->新数据集的最小量时回调该Callbcak。而,这个DiffUtil.Callback在处理Item差异时,调用的是所传递过去的DiffUtil.ItemCallback实例中的areItemsTheSame()方法和areContentsTheSame()。这里需要注意的一点是:在这个DiffUtil.Callback中,并没有重写getChangePayload()方法,这不仅意味着它并没有实现高效地局部绑定,同时自定义DiffUtil.ItemCallback时也没有必要实现getChangePayload()方法

当DiffUtil计算新旧数据集差异之后,首先判断runGeneration与当前预执行线程的次数的最大值是否相等,如果相等,在主线程中将更新操作分发给ListUpdateCallback,将mList设置为newList,同时以newList为原本重建创建只读List副本 - mReadOnlyList,最后返回。如果不相等,表示在此线程运行以后,再一次调用了submitList()方法用于传递新的数据集,此时将不做任何处理,待DiffUtil计算结束以后,再做相应处理。

值得注意的两点就是:

  1. 调用submitList()方法所传递的数据集时,需要new一个新的List。
  2. 在这个DiffUtil.Callback中,并没有重写getChangePayload()方法,这不仅意味着它并没有实现高效地局部绑定,同时自定义DiffUtil.ItemCallback时也没有必要实现getChangePayload()方法。

既然已经了解了submitList()方法的实现方式,如果对没有实现高效地局部绑定,完全可以自定义AsyncListDiffer,然后在创建DiffUtil.Callback()回调时,重写getChangePayload()方法即可。当然在Apdaer中也需要重写onBindViewHolder(holder: DiffViewHolder, position: Int, payloads: MutableList<Any>)方法。

简单应用

与DiffUtil相比较,AsyncListDiffer使用更加的方便,只需要这么做:

  1. 创建Bean

    data class DiffBean(var name: String, var desc: String) {
    
        override fun equals(o: Any?): Boolean {
            if (this === o) return true
            if (o == null || javaClass != o.javaClass) return false
    
            val diff = o as DiffBean?
    
            return diff!!.name == name
        }
    
        override fun hashCode(): Int {
            var result = name?.hashCode() ?: 0
            return result
        }
    }
    
  2. 创建DiffUtil.ItemCallback的实现类

    class DiffItemCallback : DiffUtil.ItemCallback<DiffBean>() {
    
        override fun areItemsTheSame(oldItem: DiffBean?, newItem: DiffBean?): Boolean {
            return oldItem?.name == newItem?.name
        }
    
        override fun areContentsTheSame(oldItem: DiffBean?, newItem: DiffBean?): Boolean {
    
            if (oldItem?.desc != newItem?.desc) {
                return false
            }
            //  //默认两个data内容是相同的
            return true
        }
    }
    
  3. 自定义RecyclerView.Adapter。

    class AsyncListDifferAdapter : RecyclerView.Adapter<DiffViewHolder>() {
    
        val mAld = AsyncListDiffer<DiffBean>(this, DiffItemCallback())
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DiffViewHolder {
            val view: View = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_diff, parent, false)
    
            return DiffViewHolder(view)
        }
    
        override fun getItemCount(): Int {
           return mAld.currentList.size
        }
    
        override fun onBindViewHolder(holder: DiffViewHolder, position: Int) {
    
            val diffBean: DiffBean = mAld.currentList[position]
    
            holder.tvName.text = diffBean.name
            holder.tvDesc.text = diffBean.desc
        }
    
        fun submitList(list: List<DiffBean>?) {
            mAld.submitList(list)
        }
    }
    

    需要注意的是:

    1. 在Adapter中,创建一个AsyncListDiffer实例
    2. 定义submitList(list: List<DiffBean>?)方法,在这个方法中AsyncListDiffer实例调用submitList()方法用来更新数据集。
    3. 如果想获取数据集,只需AsyncListDiffer实例调用getCurrentList()方法即可
  4. 更新数据集

    val newList = mutableListOf<DiffBean>()
    newList.addAll(mList)
    mAdapter.submitList(newList)
    

ListAdapter

ListAdapter是基于RecyclerView.Adapter的AsyncListDiffer封装类,用于在RecyclerView中呈现列表数据。在其内创建了AsyncListDiffer的示例,以便在后台线程中使用DiffUtil计算新旧数据集的差异。

核心方法

为了便于操作Item数据,

  • getItemCount():获取数据集的Size
  • submitList(List list):传递新的数据集
  • getItem(int position):获取Item

简单使用

  1. 创建DiffUtil.ItemCallback的实现类

    class DiffItemCallback : DiffUtil.ItemCallback<DiffBean>() {
    
        override fun areItemsTheSame(oldItem: DiffBean?, newItem: DiffBean?): Boolean {
            return oldItem?.name == newItem?.name
        }
    
        override fun areContentsTheSame(oldItem: DiffBean?, newItem: DiffBean?): Boolean {
    
            if (oldItem?.desc != newItem?.desc) {
                return false
            }
            //  //默认两个data内容是相同的
            return true
        }
    }
    
  2. 创建ListApapter实现类

    class DiffListAdapter : ListAdapter<DiffBean, DiffViewHolder> {
    
        constructor() : this(DiffItemCallback())
    
        constructor(itemCallback: DiffItemCallback) : super(itemCallback)
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DiffViewHolder {
            val view: View = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_diff, parent, false)
    
            return DiffViewHolder(view)
        }
    
        override fun onBindViewHolder(holder: DiffViewHolder, position: Int) {
    
            val diffBean: DiffBean = getItem(position)
    
            holder.tvName.text = diffBean.name
            holder.tvDesc.text = diffBean.desc
        }
    }
    
  3. 更新数据集

    val newList = mutableListOf<DiffBean>()
    newList.addAll(mList)
    mAdapter.submitList(newList)
    

在更新数据集时,只需调用ListAdapter的submitList(List<T> list)方法即可,就像在AsyncListDiffer示例一样,在这个方法中也是调用了 AsyncListDiffer的submitList(List<T> list)方法。

如果想获取当前数据集的长度,可以调用getItemCount()方法。

如果想获取某个Item中的对象,可以调用getItem(int position)方法。

总结

AsyncListDiffer解决了DiffUtil在计算新旧数据集差异时需要开启线程,而在更新UI时又要在主线程的缺陷,它更像DiffUtil的工具类,配合RecylcerView.Adapter使用。同时,开阔了对于DiffUtil处理的方式,我们可以照着这个思路,去创建适合自己的AsyncListDiffer。

Demo地址

DiffUtilDemo

参考文档

  1. AsyncListDiffer
  2. DiffUtil.ItemCallback
  3. ListAdapter
  4. AsyncDifferConfig
  5. AsyncDifferConfig.Builder
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值