AsyncListDiffer、DiffUtil.DiffResult 流程源码解析

使用RecyclerView时,如果使用 AsyncListDiffer 用于分页加载的话,是合适的,因为在你通过 addAll的方式添加分页数据后,根据 DiffUtil.DiffResult 的差异算法,之前的数据如果没有发生变化,areContentsTheSame 方法如果返回true,则不会调用 Adapter. notifyItemRangeChanged 方法去重新绑定数据。

        但是当你不用于分页数据,而仅仅用于正常的列表数据,并且你希望可以根据 DiffUtil.DiffResult 的差异算法,不更新某项的绑定,此时,可能会出现一个bug。

        这个bug的产生,是由于使用了MVVM框架并使用ViewDataBinding 去绑定数据跟xml。根据源码流程走下去,你会发现实际上列表项数据还是完全替换了,只不过在 areContentsTheSame 方法如果返回true时,不会调用 Adapter. notifyItemRangeChanged 方法去重新绑定数据。

        但是因为我们使用了 ViewDataBinding 去绑定数据,那就会导致全局列表变量里面的数据已经是新数据了,但是 ViewDataBinding 绑定的还是之前的旧数据。导致当新Item数据变动的时候,View并不会随之改变

接下来进去源码流程去验证这个结果吧

        当我们拿到新数据后,就会调用 AsyncListDiffer.submitList(newItems) 进行数据更新,并提交


    private val differ: AsyncListDiffer<T> = AsyncListDiffer<T>(adapter, callback)

    /**
     * 将列表更新为给定的项。一个diff将在后台线程中运行,然后这个集合将被更新。
     */
    fun update(newItems: List<T>) {
        differ.submitList(newItems)
    }

        来到 AsyncListDiffer.submitList(@Nullable final List<T> newList,
            @Nullable final Runnable commitCallback) 方法


    public void submitList(@Nullable final List<T> newList,
            @Nullable final Runnable commitCallback) {
        // 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)
            if (commitCallback != null) {
                commitCallback.run();
            }
            return;
        }

        final List<T> previousList = mReadOnlyList;

        // fast simple remove all
        // 新数据为空,则移除所有数据,并通知Adapter
        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;
        }

        // fast simple first insert
        // 旧数据为空,则将新数据全部添加,并通知Adapter
        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);
                        }
                    }
                });
            }
        });
    }

        接着来到了AsyncListDiffer.latchList(
            @NonNull List<T> newList,
            @NonNull DiffUtil.DiffResult diffResult,
            @Nullable Runnable commitCallback) 方法


    void latchList(
            @NonNull List<T> newList,
            @NonNull DiffUtil.DiffResult diffResult,
            @Nullable Runnable commitCallback) {
        final List<T> previousList = mReadOnlyList;
        //直接把新列表赋值给 mList变量
        mList = newList;
        // notify last, after list is updated
        //将 newList 转为不可变列表,赋值给 mReadOnlyList
        mReadOnlyList = Collections.unmodifiableList(newList);
        //通知Adapter更新
        diffResult.dispatchUpdatesTo(mUpdateCallback);
        onCurrentListChanged(previousList, commitCallback);
    }


    /**
     * 通过该方法拿到列表数据,然后去绑定xml跟数据
     */
    @NonNull
    public List<T> getCurrentList() {
        return mReadOnlyList;
    }

然后来到DiffUtil.dispatchUpdateTo() 方法

        //通过之前的差异算法,将需要通知更新的项的 position 信息放到 batchingCallback 里面去
        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;
            }
            //通知Adapter更新
            batchingCallback.dispatchLastEvent();
        }

来到BatchingListUpdateCallback.dispatchLastEvent()方法

    //通知AdapterListUpdateCallback mWrapped
    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;
    }

最后调用Adapter的更新方法


    /** {@inheritDoc} */
    @Override
    public void onInserted(int position, int count) {
        mAdapter.notifyItemRangeInserted(position, count);
    }

    /** {@inheritDoc} */
    @Override
    public void onRemoved(int position, int count) {
        mAdapter.notifyItemRangeRemoved(position, count);
    }

    /** {@inheritDoc} */
    @Override
    public void onMoved(int fromPosition, int toPosition) {
        mAdapter.notifyItemMoved(fromPosition, toPosition);
    }

    /** {@inheritDoc} */
    @Override
    public void onChanged(int position, int count, Object payload) {
        mAdapter.notifyItemRangeChanged(position, count, payload);
    }

到此结束源码流程,可以看到AsyncListDiffer、DiffUtil.DiffResult 使用的差异算法,最终列表数据还是会改变,仅根据差异来决定是否通知Adapter 更新

所以普通的数据更新,目前有2种处理办法:

1.不使用 DiffUtil.ItemCallback,那么每次更新数据的时候,都会重新绑定RecyclerView 的各项数据,如果有图片加载,则会白光一闪,体验不好

2.自定义 AsyncListDiffer,在里面做处理,如果某一项数据 areContentsTheSame 返回true,则这一项数据还是使用原来的数据

3. 第一步是正常的判定 areItemsTheSame 方法,然后在 areContentsTheSame 方法中返回false。再重写 DiffUtil.ItemCallback 的 getChangePayload(oldItem: ItemLayoutRes, newItem: ItemLayoutRes): Any? 方法,根据 areItemsTheSame 的结果,如果为true,则返回任一参数,代表局部刷新;如果为false,则返回null,代表全局刷新。这样子就解决了,当 areItemsTheSame 以及 areContentsTheSame 方法都返回true的时候,数据改变了,但是没有回调 notifyItemRangeChanged 方法,导致的bug。(MVVM模式、ViewDatabinding)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值