DiffUtil+AsyncListDiffer工具类原理讲解

本文详细介绍了DiffUtil的原理及使用步骤,包括计算集合差异并进行局部刷新的过程。同时,针对DiffUtil在主线程计算导致的阻塞问题,深入解析了AsyncListDiffer的实现方式,有效避免了UI卡顿。

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

一.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的数据一致性问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值