Android 高性能列表:RecyclerView + DiffUtil

news/2024/10/16 17:48:18/

文章目录

  • 背景
  • 介绍
  • 一般刷新 notifyDataSetChanged()
  • 局部刷新
  • 实现
    • 调用代码
    • 准备工作
    • 创建 MyDiffUtilCallback 类继承 DiffUtil.Callback 抽象类
    • MyAdpter 类代码实现
    • 步骤总结
  • 通过 log 证实 diffutil 的局部刷新
  • diffutil 优化
    • 后台线程参考
    • 主线程参考
    • diff 更新优化后写法
  • 相关参考

背景

  • 学习记录
  • 针对 recyclerview 实现的多数据列表展示,进一步优化数据频繁更新时的性能

介绍

  • AndroidSupport:v7-24.2.0 中,recyclerview 支持库开始支持了 DiffUtil 工具类的使用
  • DiffUtil 内部使用 Eugene W. Myers’s difference 算法:进行两个数据集的对比,找出新数据与旧数据之间最小的变化部分,和 RecyclerView 一起使用可以实现列表的局部更新

一般刷新 notifyDataSetChanged()

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {...// 一般刷新方式public void notifyUpdate(List<CoderBean> mCoderList){this.mCoderList = mCoderList;if (mCoderList == null || mCoderList.size() == 0){this.mCoderList = new ArrayList<>();}notifyDataSetChanged();}
}

主要缺点:

  • 粗暴的刷新整个列表的可见区域,这时候就会触发每个 item 的视图重绘,当 onBindViewHolder(@NonNull ViewHolder holder, int position) 中的处理逻辑比较复杂,容易出现卡顿

局部刷新

为了进一步优化上面的缺点,recyclerview 提供了局部刷新的方式,如下:

# notifyItemChanged(int)
# notifyItemInserted(int)
# notifyItemRemoved(int)
# notifyItemRangeChanged(int, int)
# notifyItemRangeInserted(int, int)
# notifyItemRangeRemoved(int, int)

上面的几个 recyclerview 提供的局部刷新方法,都只会刷新指定 position 位置的 item,就不会存在一般刷新方式出现的缺点。

但是如果数据量多,且需要更新的 item 也较多,那么这将会需要我们提供较为复杂的局部刷新调用处理逻辑,这无疑是一场灾难。
所以后面 Google 也注意到了这点,后续推出了工具类: DiffUtil ,用来专门计算哪些位置的数据需要进行更新。


实现

调用代码

这里先给出调用的代码,我们来看下相关 api

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {...// diff 更新方式public void diffUpdate(final List<CoderBean> newCoderList){final MyDiffUtilCallback diffUtilCallback = new MyDiffUtilCallback(this.mCoderList, newCoderList);// 获取差异结果(注意这里是耗时操作,如果数据量大的时候需要放到后台线程处理差异,否则会阻塞主线程)final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffUtilCallback);cloneData(newCoderList);// DiffResult 再把差异分发给 Adapter,adapter 最后根据接收到的差异数据做更新diffResult.dispatchUpdatesTo(MyAdapter.this);}// 拷贝一份数据给到当前数据集 mCoderListprivate void cloneData(List<CoderBean> newCoderList) {this.mCoderList.clear();this.mCoderList.addAll(newCoderList);}
}
  • 首先 MyAdapter 就是简单的展示数据逻辑:构建 itemView、获取数据,绑定数据展示
  • mCoderList 是上一次的数据集,newCoderList 是通过参数新传进来的新的数据集
  • 需要一个 DiffUtil.Callback 对象。MyDiffUtilCallback 继承了 DiffUtil.Callback 抽象类

准备工作

  • 创建实体类 CoderBean
package com.example.diffutildemo.bean;import android.os.Parcel;
import android.os.Parcelable;/*** 搬砖工 实体*/
public class CoderBean implements Parcelable {private int id;private String name;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeInt(this.id);dest.writeString(this.name);}public CoderBean() {}protected CoderBean(Parcel in) {this.id = in.readInt();this.name = in.readString();}public static final Parcelable.Creator<CoderBean> CREATOR = new Parcelable.Creator<CoderBean>() {@Overridepublic CoderBean createFromParcel(Parcel source) {return new CoderBean(source);}@Overridepublic CoderBean[] newArray(int size) {return new CoderBean[size];}};
}

创建 MyDiffUtilCallback 类继承 DiffUtil.Callback 抽象类

代码如下:

package com.example.diffutildemo.callback;import android.text.TextUtils;import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;import com.example.diffutildemo.bean.CoderBean;import java.util.ArrayList;
import java.util.List;public class MyDiffUtilCallback extends DiffUtil.Callback {private List<CoderBean> oldCoderList = new ArrayList<>();private List<CoderBean> newCoderList = new ArrayList<>();// 通过构造传入新旧数据集public MyDiffUtilCallback(List<CoderBean> oldCoderList, List<CoderBean> newCoderList) {this.oldCoderList = oldCoderList;this.newCoderList = newCoderList;}@Overridepublic int getOldListSize() {return oldCoderList == null ? 0 : oldCoderList.size();}@Overridepublic int getNewListSize() {return newCoderList == null ? 0 : newCoderList.size();}@Overridepublic boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {CoderBean oldCoderBean = oldCoderList.get(oldItemPosition);CoderBean newCoderBean = oldCoderList.get(newItemPosition);if (oldCoderBean != null && newCoderBean != null){int oldId = oldCoderList.get(oldItemPosition).getId();int newId = newCoderList.get(newItemPosition).getId();if (oldId == newId){return true;}}return false;}@Overridepublic boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {String oldName = oldCoderList.get(oldItemPosition).getName();String newName = newCoderList.get(newItemPosition).getName();if (TextUtils.isEmpty(oldName) || TextUtils.isEmpty(newName)){return false;}if (oldName.equals(newName)){return true;}return false;}@Nullable@Overridepublic Object getChangePayload(int oldItemPosition, int newItemPosition) {System.out.println(":> getChangePayload +++ old: " + oldItemPosition+ ", +++ new: " + newItemPosition);return super.getChangePayload(oldItemPosition, newItemPosition);}
}
  • public int getOldListSize() :

返回旧列表数据集的数量。


  • public int getNewListSize():

返回新列表数据集的数量。


  • public boolean areItemsTheSame(int oldItemPosition, int newItemPosition):

两个位置的对象是否是同一个 item。一般通过实体类中定义的 id 属性值是否相同来进行判断:返回 true 表示是同一个,反之则不是。

  • public boolean areContentsTheSame(int oldItemPosition, int newItemPosition):

用来判断新旧 item 的各内容属性值是否相同(自己实现,也相对简单)。

只有当 areItemsTheSame() 返回 true 时才会触发调用:返回 true
表示是相同的各属性内容,反之则存在属性内容的变化。


  • public Object getChangePayload(int oldItemPosition, int newItemPosition):

当 areItemsTheSame() 返回 true ,并且 areContentsTheSame() 返回 false 时触发调用。

这里可以自己实现返回差异数据,会从 DiffResult 分发给 notifyItemRangeChanged(position,
count, payload) 方法,最终交给 Adapter 的 onBindViewHolder(… List< Object >
payloads) 处理。


MyAdpter 类代码实现

package com.example.diffutildemo.adatper;import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;import com.example.diffutildemo.R;
import com.example.diffutildemo.bean.CoderBean;
import com.example.diffutildemo.callback.MyDiffUtilCallback;
import com.example.diffutildemo.executor.DiffMainThreadExecutor;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {private List<CoderBean> mCoderList = new ArrayList<>();private LayoutInflater inflater;private ViewHolder holder;private Context context;public MyAdapter(Context context, List<CoderBean> mCoderList) {this.mCoderList = mCoderList;this.context = context;this.inflater = LayoutInflater.from(context);}@NonNull@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {System.out.println(":> onCreateViewHolder +++ ");View itemView = inflater.inflate(R.layout.recyclerview_itemview_coder, parent, false);holder = new ViewHolder(itemView);return holder;}@Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int position) {System.out.println(":> onBindViewHolder +++ " + position);String name = mCoderList.get(position).getName();holder.tv_coder.setText(name);}@Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
//        System.out.println(":> onBindViewHolder +++ payloads");super.onBindViewHolder(holder, position, payloads);}@Overridepublic int getItemCount() {return (mCoderList == null) ? 0 : mCoderList.size();}public class ViewHolder extends RecyclerView.ViewHolder {TextView tv_coder;public ViewHolder(@NonNull View itemView) {super(itemView);tv_coder = itemView.findViewById(R.id.tv_coder);}}@Overridepublic int getItemViewType(int position) {return super.getItemViewType(position);}// 一般刷新方式public void notifyUpdate(List<CoderBean> mCoderList){this.mCoderList = mCoderList;if (mCoderList == null || mCoderList.size() == 0){this.mCoderList = new ArrayList<>();}notifyDataSetChanged();}// diff 更新方式public void diffUpdate(final List<CoderBean> newCoderList){final MyDiffUtilCallback diffUtilCallback = new MyDiffUtilCallback(this.mCoderList, newCoderList);// 获取差异结果(注意这里是耗时操作,如果数据量大的时候需要放到后台线程处理差异,否则会阻塞主线程)final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffUtilCallback);cloneData(newCoderList);// DiffResult 再把差异分发给 Adapter,adapter 最后根据接收到的差异数据做更新diffResult.dispatchUpdatesTo(MyAdapter.this);}private void cloneData(List<CoderBean> newCoderList) {this.mCoderList.clear();this.mCoderList.addAll(newCoderList);}}
  • 代码简单,不过多说明。

步骤总结

所以使用 DiffUtil 工具类进行局部刷新可以简单分为下面几步:

  • 自实现 DiffUtil.callback
  • 计算得到 DiffResult
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffUtilCallback);
  • DiffResult 分发给 Adapter 进行局部更新
cloneData(newCoderList);
// DiffResult 再把差异分发给 Adapter,adapter 最后根据接收到的差异数据做更新
diffResult.dispatchUpdatesTo(MyAdapter.this);

计算出 DiffResult 后,咱们必须要将新数据设置给 Adapter,然后才能调用DiffResult.dispatchUpdatesTo(Adapter) 刷新ui

private void cloneData(List<CoderBean> newCoderList) {this.mCoderList.clear();this.mCoderList.addAll(newCoderList);
}

通过 log 证实 diffutil 的局部刷新

原始数据初始化代码:

private void initData() {coderList.clear();for (int i = 0;i < 10;i++){CoderBean bean = new CoderBean();bean.setId(i);bean.setName("原始数据 coder +00" + i);coderList.add(bean);}}

一般更新模拟设置数据代码:

// 一般更新数据模拟,前两个数据保持不变private List<CoderBean> getNewData(){List<CoderBean> list = new ArrayList<>();for (int i = 0;i < 10;i++){CoderBean bean = new CoderBean();bean.setId(i);bean.setName("一般更新 coder +00" + i);if (i < 2){bean.setName("原始数据 coder +00" + i);}list.add(bean);}return list;}

diff 更新模拟设置数据代码:

// diff 更新模拟设置数据 前两个数据保持不变private List<CoderBean> getNewDiffData(){List<CoderBean> list = new ArrayList<>();for (int i = 0;i < 10;i++){CoderBean bean = new CoderBean();bean.setId(i);bean.setName("Diff更新 coder +00" + i);if (i < 2){bean.setName("原始数据 coder +00" + i);}list.add(bean);}return list;}

一般更新调用测试:

		// 一般更新btn_update.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (adapter != null){adapter.notifyUpdate(getNewData());}}});

日志打印如下:
在这里插入图片描述
上图可知:即使前两个 item 的数据一样,一般更新也会重新绘制前两个 itemview 的视图。


diff 更新调用测试:

		// diff 更新btn_update_diff.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (adapter != null){adapter.diffUpdate(getNewDiffData());}}});

日志打印如下:
在这里插入图片描述

完整打印如下:

2023-02-23 11:48:58.116 11037-11037/com.example.diffutildemo I/System.out: :> getChangePayload +++ old: 2, +++ new: 2
2023-02-23 11:48:58.116 11037-11037/com.example.diffutildemo I/System.out: :> getChangePayload +++ old: 3, +++ new: 3
2023-02-23 11:48:58.116 11037-11037/com.example.diffutildemo I/System.out: :> getChangePayload +++ old: 4, +++ new: 4
2023-02-23 11:48:58.116 11037-11037/com.example.diffutildemo I/System.out: :> getChangePayload +++ old: 5, +++ new: 5
2023-02-23 11:48:58.116 11037-11037/com.example.diffutildemo I/System.out: :> getChangePayload +++ old: 6, +++ new: 6
2023-02-23 11:48:58.116 11037-11037/com.example.diffutildemo I/System.out: :> getChangePayload +++ old: 7, +++ new: 7
2023-02-23 11:48:58.116 11037-11037/com.example.diffutildemo I/System.out: :> getChangePayload +++ old: 8, +++ new: 8
2023-02-23 11:48:58.116 11037-11037/com.example.diffutildemo I/System.out: :> getChangePayload +++ old: 9, +++ new: 9
2023-02-23 11:48:58.133 11037-11037/com.example.diffutildemo I/System.out: :> onCreateViewHolder +++ 
2023-02-23 11:48:58.135 11037-11037/com.example.diffutildemo I/System.out: :> onBindViewHolder +++ 7
2023-02-23 11:48:58.135 11037-11037/com.example.diffutildemo I/System.out: :> onCreateViewHolder +++ 
2023-02-23 11:48:58.136 11037-11037/com.example.diffutildemo I/System.out: :> onBindViewHolder +++ 8
2023-02-23 11:48:58.137 11037-11037/com.example.diffutildemo I/System.out: :> onCreateViewHolder +++ 
2023-02-23 11:48:58.138 11037-11037/com.example.diffutildemo I/System.out: :> onBindViewHolder +++ 9
2023-02-23 11:48:58.138 11037-11037/com.example.diffutildemo I/System.out: :> onCreateViewHolder +++ 
2023-02-23 11:48:58.140 11037-11037/com.example.diffutildemo I/System.out: :> onBindViewHolder +++ 2
2023-02-23 11:48:58.140 11037-11037/com.example.diffutildemo I/System.out: :> onCreateViewHolder +++ 
2023-02-23 11:48:58.142 11037-11037/com.example.diffutildemo I/System.out: :> onBindViewHolder +++ 3
2023-02-23 11:48:58.142 11037-11037/com.example.diffutildemo I/System.out: :> onCreateViewHolder +++ 
2023-02-23 11:48:58.142 11037-11037/com.example.diffutildemo I/System.out: :> onBindViewHolder +++ 4
2023-02-23 11:48:58.143 11037-11037/com.example.diffutildemo I/System.out: :> onCreateViewHolder +++ 
2023-02-23 11:48:58.144 11037-11037/com.example.diffutildemo I/System.out: :> onBindViewHolder +++ 5
2023-02-23 11:48:58.144 11037-11037/com.example.diffutildemo I/System.out: :> onCreateViewHolder +++ 
2023-02-23 11:48:58.145 11037-11037/com.example.diffutildemo I/System.out: :> onBindViewHolder +++ 6

由上面日志打印可知,前两个位置的 item 的视图没有重新绘制,也就是说明做到了局部刷新。

相比 notifyDataSetChanged(),性能大有提高。

如果在 Adapter 的 onBindViewHolder(… List< Object > payloads)
中进一步判断,可以做到进一步优化,只改变控件的内容,不用进行重绘,这里就不展开细讲了。


diffutil 优化

  • 如果列表很大,DiffUtil 的计算操作会花费很多时间。所以官方建议在后台线程计算差异,在主线程应用计算结果 DiffResult

Google 当然也考虑到了这个问题,后面推出了 AsyncListDiffer工具类。所以我们来看下这个工具类的源码实现,然后自己参考进行优化即可。


后台线程参考

AsyncListDiffer.java 这个工具类的源码,大家根据自己依赖的库找就行。

在这里插入图片描述

找到 public void submitList(@Nullable final List<T> newList, @Nullable final Runnable commitCallback) 这个方法的实现,如下:

/*** Pass a new List to the AdapterHelper. Adapter updates will be computed on a background* thread.* <p>* If a List is already present, a diff will be computed asynchronously on a background thread.* When the diff is computed, it will be applied (dispatched to the {@link ListUpdateCallback}),* and the new List will be swapped in.* <p>* The commit callback can be used to know when the List is committed, but note that it* may not be executed. If List B is submitted immediately after List A, and is* committed directly, the callback associated with List A will not be run.** @param newList The new List.* @param commitCallback Optional runnable that is executed when the List is committed, if*                       it is committed.*/@SuppressWarnings("WeakerAccess")public void submitList(@Nullable final List<T> newList,@Nullable final Runnable commitCallback) {// incrementing generation means any currently-running diffs are discarded when they finishfinal 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;// fast simple remove allif (newList == null) {//noinspection ConstantConditionsint countRemoved = mList.size();mList = null;mReadOnlyList = Collections.emptyList();// notify last, after list is updatedmUpdateCallback.onRemoved(0, countRemoved);onCurrentListChanged(previousList, commitCallback);return;}// fast simple first insertif (mList == null) {mList = newList;mReadOnlyList = Collections.unmodifiableList(newList);// notify last, after list is updatedmUpdateCallback.onInserted(0, newList.size());onCurrentListChanged(previousList, commitCallback);return;}final List<T> oldList = mList;mConfig.getBackgroundThreadExecutor().execute(new Runnable() {@Overridepublic void run() {final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {@Overridepublic int getOldListSize() {return oldList.size();}@Overridepublic int getNewListSize() {return newList.size();}@Overridepublic 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;}@Overridepublic 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@Overridepublic 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() {@Overridepublic void run() {if (mMaxScheduledGeneration == runGeneration) {latchList(newList, result, commitCallback);}}});}});}

定位到 mConfig.getBackgroundThreadExecutor() 这个地方:

public final class AsyncDifferConfig<T> {...@NonNullprivate final Executor mBackgroundThreadExecutor;@SuppressWarnings("WeakerAccess")@NonNullpublic Executor getBackgroundThreadExecutor() {return mBackgroundThreadExecutor;}
}

然后我们再继续在 AsyncDifferConfig.java 中找 mBackgroundThreadExecutor 是怎么创建的。

最后定位到 public AsyncDifferConfig<T> build() 这个方法,如下:

public final class AsyncDifferConfig<T> {...@NonNullprivate final Executor mBackgroundThreadExecutor;/*** Creates a {@link AsyncListDiffer} with the given parameters.** @return A new AsyncDifferConfig.*/@NonNullpublic AsyncDifferConfig<T> build() {if (mBackgroundThreadExecutor == null) {synchronized (sExecutorLock) {if (sDiffExecutor == null) {sDiffExecutor = Executors.newFixedThreadPool(2);}}mBackgroundThreadExecutor = sDiffExecutor;}return new AsyncDifferConfig<>(mMainThreadExecutor,mBackgroundThreadExecutor,mDiffCallback);}
}

到这里就找到后台线程的创建方式了,如下:

sDiffExecutor = Executors.newFixedThreadPool(2);
mBackgroundThreadExecutor = sDiffExecutor;

使用如下:

Executor background = Executors.newFixedThreadPool(2);background.execute(new Runnable() {@Overridepublic void run() {// 计算差异的耗时操作放到这里执行}});

后面我们就可以将计算差异的耗时操作放到后台线程中进行。


主线程参考

主线程 mMainThreadExecutor 的创建位于 AsyncListDiffer.java 中,如下:

public class AsyncListDiffer<T> {...Executor mMainThreadExecutor;private static class MainThreadExecutor implements Executor {final Handler mHandler = new Handler(Looper.getMainLooper());MainThreadExecutor() {}@Overridepublic void execute(@NonNull Runnable command) {mHandler.post(command);}}// TODO: use MainThreadExecutor from supportlib once one existsprivate static final Executor sMainThreadExecutor = new MainThreadExecutor();/*** Create a AsyncListDiffer with the provided config, and ListUpdateCallback to dispatch* updates to.** @param listUpdateCallback Callback to dispatch updates to.* @param config Config to define background work Executor, and DiffUtil.ItemCallback for*               computing List diffs.** @see DiffUtil.DiffResult#dispatchUpdatesTo(RecyclerView.Adapter)*/@SuppressWarnings("WeakerAccess")public AsyncListDiffer(@NonNull ListUpdateCallback listUpdateCallback,@NonNull AsyncDifferConfig<T> config) {mUpdateCallback = listUpdateCallback;mConfig = config;if (config.getMainThreadExecutor() != null) {mMainThreadExecutor = config.getMainThreadExecutor();} else {mMainThreadExecutor = sMainThreadExecutor;}}public void submitList(@Nullable final List<T> newList,@Nullable final Runnable commitCallback) {...mConfig.getBackgroundThreadExecutor().execute(new Runnable() {@Overridepublic void run() {...mMainThreadExecutor.execute(new Runnable() {@Overridepublic void run() {if (mMaxScheduledGeneration == runGeneration) {latchList(newList, result, commitCallback);}}});}});}}

可以看到如果 config 获取不到主线程对象时,会用默认的 sMainThreadExecutor,如下:

if (config.getMainThreadExecutor() != null) {mMainThreadExecutor = config.getMainThreadExecutor();} else {mMainThreadExecutor = sMainThreadExecutor;}

这里就找到了源码中主线程的创建方式,我们可以用来参考。如下:

private static class MainThreadExecutor implements Executor {final Handler mHandler = new Handler(Looper.getMainLooper());MainThreadExecutor() {}@Overridepublic void execute(@NonNull Runnable command) {mHandler.post(command);}}

使用如下:

new MainThreadExecutor().execute(new Runnable() {@Overridepublic void run() {// 这里执行主线程刷新操作}});

diff 更新优化后写法

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {...// diff 更新方式 优化public void diffUpdate(final List<CoderBean> newCoderList){final MyDiffUtilCallback diffUtilCallback = new MyDiffUtilCallback(this.mCoderList, newCoderList);// 获取差异结果(注意这里是耗时操作,如果数据量大的时候需要放到后台线程处理差异,否则会阻塞主线程)Executor background = Executors.newFixedThreadPool(2);background.execute(new Runnable() {@Overridepublic void run() {final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffUtilCallback);new DiffMainThreadExecutor().execute(new Runnable() {@Overridepublic void run() {cloneData(newCoderList);// DiffResult 再把差异分发给 Adapter,adapter 最后根据接收到的差异数据做更新diffResult.dispatchUpdatesTo(MyAdapter.this);}});}});}}

DiffMainThreadExecutor.java 如下:

package com.example.diffutildemo.executor;import android.os.Handler;
import android.os.Looper;import java.util.concurrent.Executor;public class DiffMainThreadExecutor implements Executor {private final Handler handler = new Handler(Looper.getMainLooper());public DiffMainThreadExecutor(){}@Overridepublic void execute(Runnable command) {try {handler.post(command);}catch (Exception e){e.printStackTrace();}}
}

到这里就完成了对 DiffUtil 的一个使用与说明,更多还是需要同学们自己在实际中多实践应用,最后希望 DiffUtil 带给同学们一个更流畅的数据展示效果。


相关参考

Android高性能列表:RecyclerView + DiffUtil

AsyncListDiffer-RecyclerView最好的伙伴


技术永不眠!下期见!


http://www.ppmy.cn/news/27387.html

相关文章

【项目设计】高并发内存池(一)[项目介绍|内存池介绍|定长内存池的实现]

&#x1f387;C学习历程&#xff1a;入门 博客主页&#xff1a;一起去看日落吗持续分享博主的C学习历程博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 也许你现在做的事情&#xff0c;暂时看不到成果&#xff0c;但不要忘记&…

边缘云是什么?

涂鸦边缘云服务 旨在解决物联网边缘位置的连接需求和提高设备自主管理能力。并与涂鸦 IoT 云服务和 IoT 终端形成云边端三位一体的端到端产品架构。使用涂鸦边缘云&#xff0c;能极大降低设备响应延时、降低网络带宽压力、提高算力分发能力&#xff0c;并构建以下技术优势&…

【数据结构】二叉树(C语言实现)

文章目录一、树的概念及结构1.树的概念2.树的相关概念名词3.树的表示4.树在实际中的运用二、二叉树概念及结构1.二叉树的概念2.特殊的二叉树3.二叉树的性质4.二叉树的存储结构三、二叉树链式结构的实现1.结构的定义2.构建二叉树3.二叉树前序遍历4.二叉树中序遍历5.二叉树后序遍…

如何做好APP性能测试?

随着智能化生活的推进&#xff0c;我们生活中不可避免的要用到很多程序app。有的APP性能使用感很好&#xff0c;用户都愿意下载使用&#xff0c;而有的APP总是出现卡顿或网络延迟的情况&#xff0c;那必然就降低了用户的好感。所以APP性能测试对于软件开发方来说至关重要&#…

带你轻松实现通讯录(C语言版)

文章目录前言通讯录初始化通讯录运行的基本框架和菜单增添联系人删除联系人查找联系人修改联系人信息展示通讯录通讯录联系人个数排序通讯录文件操作储存通讯录信息销毁通讯录整体代码Contacts.hContacts.ctest.c写在最后前言 学习C语言的小伙伴&#xff0c;相信都要经历实现通…

C语言--指针进阶1

目录回顾字符指针指针数组数组指针&数组名和数组名的区别数组指针的使用指针作为形参练习数组参数、指针参数一维数组传参二维数组传参一级指针传参二级指针传参回顾 指针的内容&#xff0c;我们在初级阶段已经有所涉及了&#xff0c;我们先来复习一下 指针就是个变量&am…

Web网页测试全流程解析论Web自动化测试

1、功能测试 web网页测试中的功能测试&#xff0c;主要测试网页中的所有链接、数据库连接、用于在网页中提交或获取用户信息的表单、Cookie 测试等。 &#xff08;1&#xff09;查看所有链接&#xff1a; 测试从所有页面到被测特定域的传出链接。 测试所有内部链接。 测试链…

若依系统如何集成qq邮件发送【超详细,建议收藏】

若依系统的部署博主就不在这儿阐述了&#xff0c;默认大家的电脑已经部署好了若依系统&#xff0c;这里直接开始集成邮件系统&#xff0c;首先我们得需要对qq邮箱进行配置&#xff1b;一套学不会你来打我&#x1f600;&#xff1b; 一、开启我们的qq邮箱发送邮件的配置 1、先进…