DiffUtil
是 Android
工程师提供的用于规范使用 notify*()
方法刷新数据的工具类。算法
在使用 RecyclerView
时常常会用到以下方法更新数据:markdown
当某条数据发生变化(如移除、修改等)时调用以上方法可用于更新数据以及 UI
显示。 想象以下场景,若列表中大量数据产生变化,怎么办呢?通常操做是调用它:异步
将列表从头至尾无脑更新一遍,这时候就出现问题了:ide
联想实际开发中,列表刷新操做是否是就调用了 notifyDataSetChanged()
。函数
基于上述问题咱们有了更高效的解决方案,那就是 - DiffUtil
。DiffUtil
使用 Eugene W. Myers
的差分算法计算两列数据之间的最小更新数,将旧的列表转换为新的列表,并针对不一样的数据变化,执行不一样的调用,而不是无脑的 notifyDataSetChanged()
。工具
关于 Eugene W. Myers
差分算法分析能够参考这篇文章: Myers 差分算法 (Myers Difference Algorithm) —— DiffUtils 之核心算法(一)oop
DiffUtil
用法很简单,一共三步:布局
RecyclerView
具体实现以下:post
//第一步:调用 DiffUtil.calculateDiff 计算最小数据更新数
val diffResult = DiffUtil.calculateDiff(object : Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldStudentList[oldItemPosition].id == newStudentList[newItemPosition].id
}
override fun getOldListSize(): Int {
return oldStudentList.size
}
override fun getNewListSize(): Int {
return newStudentList.size
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldStudentList[oldItemPosition].name == newStudentList[newItemPosition].name
}
})
//第二步:更新旧数据集合
oldStudentList.clear()
oldStudentList.addAll(newStudentList)
//第三部:更新 RecyclerView
diffResult.dispatchUpdatesTo(diffAdapter)
复制代码
第二步、第三部都很简单,主要来看下 DiffUtil.Callback
中四个方法的含义。性能
public abstract static class Callback {
/**
* 旧数据 size
*/
public abstract int getOldListSize();
/**
* 新数据 size
*/
public abstract int getNewListSize();
/**
* DiffUtil 调用判断两个 itemview 对应的数据对象是否同样. 因为 DiffUtil 是对两个不一样数据集合的对比, 因此比较对象引用确定是不行的, 通常会使用 id 等具备惟一性的字段进行比较.
* @param oldItemPosition 旧数据集合中的下标
* @param newItemPosition 新数据集合中的下标
* @return True 返回 true 及判断两个对象相等, 反之则是不一样的两个对象.
*/
public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
/**
* Diffutil 调用判断两个相同对象之间的数据是否不一样. 此方法仅会在 areItemsTheSame() 返回 true 的状况下被调用.
*
* @param oldItemPosition 旧数据集合中的下标
* @param newItemPosition 新数据集合中用以替换旧数据集合数据项的下标
* @return True 返回 true 表明 两个对象的数据相同, 反之则有差异.
*/
public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
/**
* 当 areItemsTheSame() 返回true areContentsTheSame() 返回 false 时, 此方法将被调用, 来完成局部刷新功能.
*/
@Nullable
public Object getChangePayload(int oldItemPosition, int newItemPosition);
}
复制代码
各个方法的含义都加在注释中了,理解不难,用法更简单。
具体 DiffUtil
是怎么更新 RecyclerView
的呢,看下 dispatchUpdatesTo()
方法中都作了什么
public void dispatchUpdatesTo(@NonNull final RecyclerView.Adapter adapter) {
dispatchUpdatesTo(new AdapterListUpdateCallback(adapter));
}
复制代码
adapter
做为参数建立了一个类 AdapterListUpdateCallback
的对象,AdapterListUpdateCallback
类中实现的就是具体的更新操做了。
public final class AdapterListUpdateCallback implements ListUpdateCallback {
@Override
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mAdapter.notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
mAdapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
mAdapter.notifyItemRangeChanged(position, count, payload);
}
}
复制代码
注意:经过了解
DiffUtil
的工做模式,咱们了解到DiffUtil
的经过对两列数据进行对比,产生对比结果来更新列表的。也就是说即便只更新列表中某个对象中的某个元素也要提供新的列表给DiffUtil
。因此能够想到在开发中最符合DiffUtil
的使用场景应该就是列表数据的刷新了。若是只是某个对象的变化本身调用notifyItem*()
就能够了,不必使用DiffUil
。
因为 DiffUtil
计算两个数据集合是在主线程中计算的,那么数据量大耗时的操做势必会影响到主线程代码的执行。Android
中最经常使用的异步执行耗时操做,更新主线程 UI
的方法就是用到 Handler
了。不过不用担忧,贴心的 Android
工程师已经为咱们考虑到了这中状况并提供你了 ListAdapter
和 AsyncListDiffer
两个工具类。
以 AsyncListDiffer
为例看下它的用法: 首先注册 AsyncListDiffer
的实例对象,来看下它的构造函数:
public class AsyncListDiffer<T> {
public AsyncListDiffer(@RecyclerView.Adapter adapter, DiffUtil.ItemCallback<T> diffCallback) {
this(new AdapterListUpdateCallback(adapter),
new AsyncDifferConfig.Builder<>(diffCallback).build());
}
}
复制代码
能够看到 AsyncListDiffer
类声明中有一个泛型,用于指定集合的数据类型。构造函数中接收两个参数一个是 Adapter
对象。Adapter
对象的做用和 DiffUtil
中的做用同样,做为参数建立 AdapterListUpdateCallback
对象,在其内是具体执行列表更新的方法。
另外一个是 DiffUtil.ItemCallback
对象,其做为参数构建了类 AsyncDifferConfig
的对象。
AsyncDifferConfig.class
public final class AsyncDifferConfig<T> {
private final Executor mMainThreadExecutor;
private final Executor mBackgroundThreadExecutor;
private final DiffUtil.ItemCallback<T> mDiffCallback;
public static final class Builder<T> {
public AsyncDifferConfig<T> build() {
if (mBackgroundThreadExecutor == null) {
synchronized (sExecutorLock) {
if (sDiffExecutor == null) {
sDiffExecutor = Executors.newFixedThreadPool(2);
}
}
mBackgroundThreadExecutor = sDiffExecutor;
}
return new AsyncDifferConfig<>(
mMainThreadExecutor,
mBackgroundThreadExecutor,
mDiffCallback);
}
}
}
复制代码
AsyncDifferConfig
对象中保存了两个重要变量,mMainThreadExecutor
和 mBackgroundThreadExecutor
,mBackgroundThreadExecutor
是一个最多容纳两个线程的线程池,用于异步执行 DiffUtil.calculateDiff
。mMainThreadExecutor
中实际真正执行的是 Handler
的调用。以下:
AsyncListDiffer.class -> MainThreadExecutor.class
private static class MainThreadExecutor implements Executor {
final Handler mHandler = new Handler(Looper.getMainLooper());
MainThreadExecutor() {}
@Override
public void execute(@NonNull Runnable command) {
mHandler.post(command);
}
}
复制代码
建立 AsycnListDiffer
对象,首先声明 DiffUtil.ItemCallback
对象:
//1. 声明 DiffUtil.ItemCallback 回调
private val itemCallback = object : DiffUtil.ItemCallback<StudentBean>() {
override fun areItemsTheSame(oldItem: StudentBean, newItem: StudentBean): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: StudentBean, newItem: StudentBean): Boolean {
return oldItem.name == newItem.name && oldItem.age == newItem.age
}
}
复制代码
DiffUtil.ItemCallback
一样具备 areItemTheSame()
、areContentsTheSame()
、getChangePayload()
方法,用法与 DiffUtil.Callback
相同。
接下来建立 AsycnListDiffer
对象:
//2. 建立 AsyncListDiff 对象
private val mDiffer = AsyncListDiffer<StudentBean>(this, itemCallback)
复制代码
最后一步,更新列表数据:
fun submitList(studentList: List<StudentBean>) {
//3. 提交新数据列表
mDiffer.submitList(studentList)
}
复制代码
AsycnListDiffer
的用法就是这么简单。总结其实就两步:
AsyncListDiffer
对象。submitList()
更新数据。完整代码以下:
class StudentAdapter(context: Context) : RecyclerView.Adapter<StudentAdapter.MyViewHolder>() {
private val girlColor = "#FFD6E7"
private val boyColor = "#BAE7FF"
private val layoutInflater: LayoutInflater = LayoutInflater.from(context)
//1. 声明 DiffUtil.ItemCallback 回调
private val itemCallback = object : DiffUtil.ItemCallback<StudentBean>() {
override fun areItemsTheSame(oldItem: StudentBean, newItem: StudentBean): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: StudentBean, newItem: StudentBean): Boolean {
return oldItem.name == newItem.name && oldItem.age == newItem.age
}
}
//2. 建立 AsyncListDiff 对象
private val mDiffer = AsyncListDiffer<StudentBean>(this, itemCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudentAdapter.MyViewHolder {
return MyViewHolder(layoutInflater.inflate(R.layout.item_student, parent, false))
}
override fun getItemCount(): Int {
return mDiffer.currentList.size
}
fun submitList(studentList: List<StudentBean>) {
//3. 提交新数据列表
mDiffer.submitList(studentList)
}
override fun onBindViewHolder(holder: StudentAdapter.MyViewHolder, position: Int) {
//4. 重新数据列表中获取最新数据
val studentBean = mDiffer.currentList[position]
when (studentBean.gender) {
StudentBean.GENDER_GRIL -> {
holder.rlRoot.setBackgroundColor(Color.parseColor(girlColor))
holder.ivIcon.setBackgroundResource(R.mipmap.girl)
}
StudentBean.GENDER_BOY -> {
holder.rlRoot.setBackgroundColor(Color.parseColor(boyColor))
holder.ivIcon.setBackgroundResource(R.mipmap.boy)
}
}
holder.tvName.text = studentBean.name
holder.tvAge.text = studentBean.age.toString()
}
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val rlRoot: RelativeLayout = view.findViewById(R.id.rl_student_root)
val ivIcon: ImageView = view.findViewById(R.id.iv_student_icon)
val tvName: TextView = view.findViewById(R.id.tv_student_name)
val tvAge: TextView = view.findViewById(R.id.tv_student_age)
}
}
复制代码
看下 submitList
中都作了什么:
public void submitList(@Nullable final List<T> newList,
@Nullable final Runnable commitCallback) {
// 累计调用次数, 屡次执行 submitList() 仅生效最后一次调用
final int runGeneration = ++mMaxScheduledGeneration;
// 新\旧 数据集合对象相等时直接返回
if (newList == mList) {
// nothing to do (Note - still had to inc generation, since may have ongoing work)
if (commitCallback != null) {
commitCallback.run();
}
return;
}
final List<T> previousList = mReadOnlyList;
// 新数据空, 全部 item 执行 remove 操做
if (newList == null) {
//noinspection ConstantConditions
int countRemoved = mList.size();
mList = null;
mReadOnlyList = Collections.emptyList();
// notify last, after list is updated
mUpdateCallback.onRemoved(0, countRemoved);
onCurrentListChanged(previousList, commitCallback);
return;
}
// 第一次插入数据, 统一执行 inserted 操做
if (mList == null) {
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
// notify last, after list is updated
mUpdateCallback.onInserted(0, newList.size());
onCurrentListChanged(previousList, commitCallback);
return;
}
final List<T> oldList = mList;
// 异步执行 DiffUtil.calculateDiff 计算数据最小更新数
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition);
T newItem = newList.get(newItemPosition);
if (oldItem != null && newItem != null) {
return mConfig.getDiffCallback().areItemsTheSame(oldItem, newItem);
}
// If both items are null we consider them the same.
return oldItem == null && newItem == null;
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition);
T newItem = newList.get(newItemPosition);
if (oldItem != null && newItem != null) {
return mConfig.getDiffCallback().areContentsTheSame(oldItem, newItem);
}
if (oldItem == null && newItem == null) {
return true;
}
// There is an implementation bug if we reach this point. Per the docs, this
// method should only be invoked when areItemsTheSame returns true. That
// only occurs when both items are non-null or both are null and both of
// those cases are handled above.
throw new AssertionError();
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition);
T newItem = newList.get(newItemPosition);
if (oldItem != null && newItem != null) {
return mConfig.getDiffCallback().getChangePayload(oldItem, newItem);
}
// There is an implementation bug if we reach this point. Per the docs, this
// method should only be invoked when areItemsTheSame returns true AND
// areContentsTheSame returns false. That only occurs when both items are
// non-null which is the only case handled above.
throw new AssertionError();
}
});
//主线程中更新列表
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
latchList(newList, result, commitCallback);
}
}
});
}
});
}
复制代码
最后贴一张图:
下次遇到异步执行计算,根据计算结果主线程更新 UI
也能够学习 AsyncListDiffer
写一个工具类出来 ^_^
。
对你有用的话,留个赞呗^_^
。