一.DiffUtil原理讲解
1.简介
Diffutil是一个可以计算出两个集合之间的差别并且输出从一个集合到另外一个集合之间的变化的工具类。
2.使用步骤
<1> 自定义类继承DiffUtil类的Callback抽象类
public class DiffUtilCallBack extends DiffUtil.Callback {
private List<RecyclerViewBean> mOldList;
private List<RecyclerViewBean> mNewList;
public DiffUtilCallBack(List<RecyclerViewBean> oldList, List<RecyclerViewBean> newList) {
mOldList = oldList;
mNewList = newList;
}
/**
* 获取旧集合数据量大小
*/
@Override
public int getOldListSize() {
return null == mOldList ? 0 : mOldList.size();
}
/**
* 获取新集合数据量大小
*/
@Override
public int getNewListSize() {
return null == mNewList ? 0 : mNewList.size();
}
/**
* 是否是相同的Item 返回值操作 根据具体情况 一般不操作ItemViewType 此方法返回true即可
*/
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return true;
}
/**
* 是否是相同的Contents
*/
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return (mOldList.get(oldItemPosition).getTitle()).equals(mNewList.get(newItemPosition).getTitle());
}
/**
* 更新Item的某一个控件View
*/
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
Bundle payload = new Bundle();
//头像是否更新
if (!mOldList.get(oldItemPosition).getAva().equals(mNewList.get(newItemPosition).getAva())) {
payload.putString("KEY_AVA", mNewList.get(newItemPosition).getAva());
}
//标题是否更新
if (!mOldList.get(oldItemPosition).getTitle().equals(mNewList.get(newItemPosition).getTitle())) {
payload.putString("KEY_TITLE", mNewList.get(newItemPosition).getTitle());
}
if (payload.size() == 0) {//如果没有变化 就传空
return null;
}
//更新Item的某个控件View
return payload;
}
}
<2> 计算得到DiffResult(集合差异)
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtilCallBack(oldList, newList));
<3> 将DiffResult设置给Adapter
//2.更新集合数据
mList = newList;
//3.使用DiffResult对象的dispatchUpdatesTo方法Adapter热更新
diffResult.dispatchUpdatesTo(this);
注意:此处一定要先将新数据设置给Adapter然后再将DiffResult设置给Adapter。
3.源码
<1> 计算得到DiffResult(集合差异)源码
/**
* Calculates the list of update operations that can covert one list into the other one.
*
* @param cb The callback that acts as a gateway to the backing list data
*
* @return A DiffResult that contains the information about the edit sequence to convert the
* old list into the new list.
*/
@NonNull
public static DiffResult calculateDiff(@NonNull Callback cb) {
return calculateDiff(cb, true);
}
此方法
Calculates the list of update operations that can covert one list into the other one.
计算可以将一个列表转换为另一个列表的更新操作列表。
继续
calculateDiff方法源码
/**
* Calculates the list of update operations that can covert one list into the other one.
* <p>
* If your old and new lists are sorted by the same constraint and items never move (swap
* positions), you can disable move detection which takes <code>O(N^2)</code> time where
* N is the number of added, moved, removed items.
*
* @param cb The callback that acts as a gateway to the backing list data
* @param detectMoves True if DiffUtil should try to detect moved items, false otherwise.
*
* @return A DiffResult that contains the information about the edit sequence to convert the
* old list into the new list.
*/
@NonNull
public static DiffResult calculateDiff(@NonNull Callback cb, boolean detectMoves) {
final int oldSize = cb.getOldListSize();
final int newSize = cb.getNewListSize();
final List<Snake> snakes = new ArrayList<>();
// instead of a recursive implementation, we keep our own stack to avoid potential stack
// overflow exceptions
final List<Range> stack = new ArrayList<>();
stack.add(new Range(0, oldSize, 0, newSize));
final int max = oldSize + newSize + Math.abs(oldSize - newSize);
// allocate forward and backward k-lines. K lines are diagonal lines in the matrix. (see the
// paper for details)
// These arrays lines keep the max reachable position for each k-line.
final int[] forward = new int[max * 2];
final int[] backward = new int[max * 2];
// We pool the ranges to avoid allocations for each recursive call.
final List<Range> rangePool = new ArrayList<>();
while (!stack.isEmpty()) {
final Range range = stack.remove(stack.size() - 1);
final Snake snake = diffPartial(cb, range.oldListStart, range.oldListEnd,
range.newListStart, range.newListEnd, forward, backward, max);
if (snake != null) {
if (snake.size > 0) {
snakes.add(snake);
}
// offset the snake to convert its coordinates from the Range's area to global
snake.x += range.oldListStart;
snake.y += range.newListStart;
// add new ranges for left and right
final Range left = rangePool.isEmpty() ? new Range() : rangePool.remove(
rangePool.size() - 1);
left.oldListStart = range.oldListStart;
left.newListStart = range.newListStart;
if (snake.reverse) {
left.oldListEnd = snake.x;
left.newListEnd = snake.y;
} else {
if (snake.removal) {
left.oldListEnd = snake.x - 1;
left.newListEnd = snake.y;
} else {
left.oldListEnd = snake.x;
left.newListEnd = snake.y - 1;
}
}
stack.add(left);
// re-use range for right
//noinspection UnnecessaryLocalVariable
final Range right = range;
if (snake.reverse) {
if (snake.removal) {
right.oldListStart = snake.x + snake.size + 1;
right.newListStart = snake.y + snake.size;
} else {
right.oldListStart = snake.x + snake.size;
right.newListStart = snake.y + snake.size + 1;
}
} else {
right.oldListStart = snake.x + snake.size;
right.newListStart = snake.y + snake.size;
}
stack.add(right);
} else {
rangePool.add(range);
}
}
// sort snakes
Collections.sort(snakes, SNAKE_COMPARATOR);
return new DiffResult(cb, snakes, forward, backward, detectMoves);
}
此方法,大致的算法是通过算法获得两个集合的差异DiffResult对象。具体讲解请看
Myers差分算法详解
https://blog.youkuaiyun.com/weixin_37730482/article/details/74465223
<2> 将DiffResult设置给Adapter
public void dispatchUpdatesTo(@NonNull final RecyclerView.Adapter adapter) {
dispatchUpdatesTo(new AdapterListUpdateCallback(adapter));
}
/**
* Dispatches update operations to the given Callback.
* <p>
* These updates are atomic such that the first update call affects every update call that
* comes after it (the same as RecyclerView).
*
* @param updateCallback The callback to receive the update operations.
* @see #dispatchUpdatesTo(RecyclerView.Adapter)
*/
public void dispatchUpdatesTo(@NonNull ListUpdateCallback updateCallback) {
final BatchingListUpdateCallback batchingCallback;
if (updateCallback instanceof BatchingListUpdateCallback) {
batchingCallback = (BatchingListUpdateCallback) updateCallback;
} else {
batchingCallback = new BatchingListUpdateCallback(updateCallback);
// replace updateCallback with a batching callback and override references to
// updateCallback so that we don't call it directly by mistake
//noinspection UnusedAssignment
updateCallback = batchingCallback;
}
// These are add/remove ops that are converted to moves. We track their positions until
// their respective update operations are processed.
final List<PostponedUpdate> postponedUpdates = new ArrayList<>();
int posOld = mOldListSize;
int posNew = mNewListSize;
for (int snakeIndex = mSnakes.size() - 1; snakeIndex >= 0; snakeIndex--) {
final Snake snake = mSnakes.get(snakeIndex);
final int snakeSize = snake.size;
final int endX = snake.x + snakeSize;
final int endY = snake.y + snakeSize;
if (endX < posOld) {
dispatchRemovals(postponedUpdates, batchingCallback, endX, posOld - endX, endX);
}
if (endY < posNew) {
dispatchAdditions(postponedUpdates, batchingCallback, endX, posNew - endY,
endY);
}
for (int i = snakeSize - 1; i >= 0; i--) {
if ((mOldItemStatuses[snake.x + i] & FLAG_MASK) == FLAG_CHANGED) {
batchingCallback.onChanged(snake.x + i, 1,
mCallback.getChangePayload(snake.x + i, snake.y + i));
}
}
posOld = snake.x;
posNew = snake.y;
}
batchingCallback.dispatchLastEvent();
}
public void dispatchLastEvent() {
if (mLastEventType == TYPE_NONE) {
return;
}
switch (mLastEventType) {
case TYPE_ADD:
mWrapped.onInserted(mLastEventPosition, mLastEventCount);
break;
case TYPE_REMOVE:
mWrapped.onRemoved(mLastEventPosition, mLastEventCount);
break;
case TYPE_CHANGE:
mWrapped.onChanged(mLastEventPosition, mLastEventCount, mLastEventPayload);
break;
}
mLastEventPayload = null;
mLastEventType = TYPE_NONE;
}
大致的意思是,根据计算出来的集合差异。动态调用
notifyItemChanged(int)
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)
也就是
diffResult.dispatchUpdatesTo(this);
也是通过notifyItemChanged等等的方法实现局部刷新。
二.AsyncListDiffer原理讲解
1.简介
AsyncListDiff主要用来解决 DiffUtil.calculateDiff()方法是执行在主线程的,如果新旧数据List比较大,那么这个方法铁定是会阻塞主线程的问题。
2.使用步骤
<1> 自定义类继承DiffUtil类的Callback抽象类
private DiffUtil.ItemCallback<RecyclerViewBean> diffUtil = new DiffUtil.ItemCallback<RecyclerViewBean>() {
/**
* 是否是相同的Item 返回值操作 根据具体情况 一般不操作ItemViewType 此方法返回true即可
*/
@Override
public boolean areItemsTheSame(@NonNull RecyclerViewBean oldItem, @NonNull RecyclerViewBean newItem) {
return true;
}
/**
* 是否是相同的Contents
*/
@Override
public boolean areContentsTheSame(@NonNull RecyclerViewBean oldItem, @NonNull RecyclerViewBean newItem) {
return oldItem.getTitle().equals(newItem.getTitle());
}
/**
* 更新Item的某一个控件View
*/
@Nullable
@Override
public Object getChangePayload(@NonNull RecyclerViewBean oldItem, @NonNull RecyclerViewBean newItem) {
Bundle payload = new Bundle();
//头像是否更新
if (!oldItem.getAva().equals(newItem.getAva())) {
payload.putString("KEY_AVA", newItem.getAva());
}
//标题是否更新
if (!oldItem.getTitle().equals(newItem.getTitle())) {
payload.putString("KEY_TITLE", newItem.getTitle());
}
if (payload.size() == 0) {//如果没有变化 就传空
return null;
}
//更新Item的某个控件View
return payload;
}
};
<2> 将所有对数据的操作代理给AsyncListDiffer。注意:这个Adapter是没有List数据的
private AsyncListDiffer<RecyclerViewBean> mDiffer;
public RecycleViewAsyncUpdateAdapter(Context context) {
mContext = context;
mInflater = LayoutInflater.from(context);
mDiffer = new AsyncListDiffer<>(this, diffUtil);
}
/**
* 刷新数据 AsyncListDiffer差量刷新 异步
*/
public void submitList(List<RecyclerViewBean> data) {
//1.AsyncListDiffer提交差量
mDiffer.submitList(data);
//2.更新集合数据
mList = data;
}
@Override
public int getItemCount() {
return mDiffer.getCurrentList().size();//默认 return mList.size();
}
*********************以上代码在Adapter中***********************
//初始化数据
mRecycleViewAdapter = new RecycleViewAsyncUpdateAdapter(this);
mRecyclerView.setAdapter(mRecycleViewAdapter);
mRecycleViewAdapter.submitList(mList);//AsyncListDiffer差量刷新 异步 初始化也要用这种方式添加数据
//刷新数据
mRecycleViewAdapter.submitList(mList);//AsyncListDiffer差量刷新 异步
*********************以上代码在Activity中***********************
3.源码
从submitList方法开始
mDiffer.submitList(data);
submitList方法源码
public void submitList(@Nullable final List<T> newList) {
// incrementing generation means any currently-running diffs are discarded when they finish
final int runGeneration = ++mMaxScheduledGeneration;
if (newList == mList) {
// nothing to do (Note - still had to inc generation, since may have ongoing work)
return;
}
// fast simple remove all
if (newList == null) {
//noinspection ConstantConditions
int countRemoved = mList.size();
mList = null;
mReadOnlyList = Collections.emptyList();
// notify last, after list is updated
mUpdateCallback.onRemoved(0, countRemoved);
return;
}
// fast simple first insert
if (mList == null) {
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
// notify last, after list is updated
mUpdateCallback.onInserted(0, newList.size());
return;
}
final List<T> oldList = mList;
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);
}
}
});
}
});
}
如果,新的集合和老的集合一致,直接return。因为集合没有变化。不需要刷新。
latchList方法源码
@SuppressWarnings("WeakerAccess") /* synthetic access */
void latchList(@NonNull List<T> newList, @NonNull DiffUtil.DiffResult diffResult) {
mList = newList;
// notify last, after list is updated
mReadOnlyList = Collections.unmodifiableList(newList);
diffResult.dispatchUpdatesTo(mUpdateCallback);
}
可以看出。
<1> 关于DiffUtil.DiffResult的计算 放到mConfig.getBackgroundThreadExecutor()也就是子线程执行。
<2> 关于diffResult.dispatchUpdatesTo的更新 放到mMainThreadExecutor也就是主线程中执行。
子线程操作
if (mBackgroundThreadExecutor == null) {
synchronized (sExecutorLock) {
if (sDiffExecutor == null) {
sDiffExecutor = Executors.newFixedThreadPool(2);
}
}
mBackgroundThreadExecutor = sDiffExecutor;
}
也就是,子线程的实现是 通过Java给我们提供的Executors的newFixedThreadPool方法创建了两个线程池的线程。
主线程操作
final Executor mMainThreadExecutor;
private static class MainThreadExecutor implements Executor {
final Handler mHandler = new Handler(Looper.getMainLooper());
MainThreadExecutor() {}
@Override
public void execute(@NonNull Runnable command) {
mHandler.post(command);
}
}
if (config.getMainThreadExecutor() != null) {
mMainThreadExecutor = config.getMainThreadExecutor();
} else {
mMainThreadExecutor = sMainThreadExecutor;
}
private static final Executor sMainThreadExecutor = new MainThreadExecutor();
也就是,主线程的实现是 通过Handler切换的主线程。
小结1
AsyncListDiffer实现 在子线程中计算DiffResult,在主线程将DiffResult设置给Adapter,解决主线程阻塞问题。
Adapter中设置列表数量
@Override
public int getItemCount() {
return mDiffer.getCurrentList().size();//默认 return mList.size();
}
getCurrentList方法源码
@NonNull
public List<T> getCurrentList() {
return mReadOnlyList;
}
mReadOnlyList集合声明 赋值 操作
//声明 默认空
@NonNull
private List<T> mReadOnlyList = Collections.emptyList();
//外部获取 Adapter中设置Item数量
@NonNull
public List<T> getCurrentList() {
return mReadOnlyList;
}
//submitList方法赋值 即 初始化和更新时
public void submitList(@Nullable final List<T> newList) {
//新集合为空
if (newList == null) {
//mReadOnlyList集合也为空 并且return
mReadOnlyList = Collections.emptyList();
return;
}
//老集合为空
if (mList == null) {
//mReadOnlyList集合为新的集合 并且return
mReadOnlyList = Collections.unmodifiableList(newList);
return;
}
}
//latchList方法赋值 即 此时 新老集合都不为空 且计算出两个集合的差异
@SuppressWarnings("WeakerAccess") /* synthetic access */
void latchList(@NonNull List<T> newList, @NonNull DiffUtil.DiffResult diffResult) {
mList = newList;
// notify last, after list is updated
mReadOnlyList = Collections.unmodifiableList(newList);
diffResult.dispatchUpdatesTo(mUpdateCallback);
}
小结2
AsyncListDiffer实现 将Adapter的数据代理给AsyncListDiffer,解决Adapter与DiffUtil的数据一致性问题。