Android 高性能列表:RecyclerView + DiffUtil

news2024/11/17 5:54:22

文章目录

  • 背景
  • 介绍
  • 一般刷新 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);

    }

	// 拷贝一份数据给到当前数据集 mCoderList
	private 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;
    }


    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public 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>() {
        @Override
        public CoderBean createFromParcel(Parcel source) {
            return new CoderBean(source);
        }

        @Override
        public 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;
    }

    @Override
    public int getOldListSize() {
        return oldCoderList == null ? 0 : oldCoderList.size();
    }

    @Override
    public int getNewListSize() {
        return newCoderList == null ? 0 : newCoderList.size();
    }

    @Override
    public 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;
    }

    @Override
    public 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
    @Override
    public 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
    @Override
    public 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;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        System.out.println(":> onBindViewHolder +++ " + position);
        String name = mCoderList.get(position).getName();
        holder.tv_coder.setText(name);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
//        System.out.println(":> onBindViewHolder +++ payloads");
        super.onBindViewHolder(holder, position, payloads);
    }

    @Override
    public 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);
        }
    }

    @Override
    public 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() {
            @Override
            public void onClick(View v) {
                if (adapter != null){
                    adapter.notifyUpdate(getNewData());
                }
            }
        });

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


diff 更新调用测试:

		// diff 更新
        btn_update_diff.setOnClickListener(new View.OnClickListener() {
            @Override
            public 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 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
        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
        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;
        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);
                        }
                    }
                });
            }
        });
    }

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

public final class AsyncDifferConfig<T> {
	...
	@NonNull
    private final Executor mBackgroundThreadExecutor;

	@SuppressWarnings("WeakerAccess")
    @NonNull
    public Executor getBackgroundThreadExecutor() {
        return mBackgroundThreadExecutor;
    }
}


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

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

public final class AsyncDifferConfig<T> {
	...
	@NonNull
    private final Executor mBackgroundThreadExecutor;

	/**
         * Creates a {@link AsyncListDiffer} with the given parameters.
         *
         * @return A new AsyncDifferConfig.
         */
        @NonNull
        public 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() {
            @Override
            public 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() {}
        @Override
        public void execute(@NonNull Runnable command) {
            mHandler.post(command);
        }
    }

	// TODO: use MainThreadExecutor from supportlib once one exists
    private 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() {
            @Override
            public void run() {
                ...
                mMainThreadExecutor.execute(new Runnable() {
                    @Override
                    public 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() {}
        @Override
        public void execute(@NonNull Runnable command) {
            mHandler.post(command);
        }
    }

使用如下:

new MainThreadExecutor().execute(new Runnable() {
                    @Override
                    public 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() {
            @Override
            public void run() {
                final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffUtilCallback);

                new DiffMainThreadExecutor().execute(new Runnable() {
                    @Override
                    public 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(){}

    @Override
    public void execute(Runnable command) {
        try {
            handler.post(command);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

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


相关参考

Android高性能列表:RecyclerView + DiffUtil

AsyncListDiffer-RecyclerView最好的伙伴


技术永不眠!下期见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366482.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring的一些知识点

什么是Spring&#xff1f; Spring是一种轻量级的开发框架&#xff0c;旨在提高开发人员的开发效率以及系统的可维护性。 Spring的核心模块 Spring Core是基础模块&#xff0c;可以说Spring的其他功能都要依赖于该类库&#xff0c;主要提供IOC的依赖注入功能&#xff1b; Spri…

动手学深度学习v2—01数据操作+数据预处理

此次用到的虚拟环境&#xff1a;pytorchmwy项目名称&#xff1a;limuAI所需框架和工具&#xff1a;pytorch&#xff0c;pandas一、创建CSV文件所需工具&#xff1a;pandas在与项目同等目录下创建一个文件夹名为data&#xff0c;其中文件名称为house_tiny.csv。代码如下&#xf…

Java基础:拼图小游戏

涉及到的知识: 1.图形用户接口GUI(Graphical User Interface)用图形化的方式显示操作界面 两个体系: AWT包和Swing包 2.界面会用到JFrame类 3.界面中的菜单会用到JMenuBar, JMenu, JMenuItem 4.添加图片 在设置完JLabel的location之后还需要获得展示内容的窗体, 通过setLay…

吃鸡用什么蓝牙耳机效果好?手游吃鸡公认最好的几款蓝牙耳机

蓝牙耳机的作用很多&#xff0c;几乎每个人都需要一副很棒的耳机在通勤或锻炼途中使用&#xff0c;并且玩游戏也少不了它&#xff0c;手游近几年十分的流行&#xff0c;下面整理了几款性能不错的蓝牙耳机。 第一款&#xff1a;南卡小音舱蓝牙耳机 蓝牙版本&#xff1a;5.3 发…

【Linux】如何将ntfs硬盘挂载到home目录下并具有读写权限

步骤1. 查看当前挂载的硬盘及其挂载点2. 查看需要挂载到home下的磁盘类型信息3. 在home下新建一个空的文件夹作为该磁盘的新挂载点4. 以ntfs类型的硬盘为例&#xff0c;使用mount命令进行挂载5. 问题1&#xff1a;进程占用了磁盘6. 问题2&#xff1a;磁盘权限为只读的7. 永久挂…

[AI助力] 2022.2.23 考研英语学习 2010 英语二翻译

[AI助力] 2022.2.23 考研英语学习 2010 英语二翻译 文章目录[AI助力] 2022.2.23 考研英语学习 2010 英语二翻译2010年英语二翻译真题自己写的积累&#x1f9d0;看看AI的翻译&#xff0c;学习学习&#xff08;把自己当成阅卷老师来康康hhh&#x1f920;DeepL谷歌翻译ReadPaper里…

JavaUDP通信程序

2 UDP通信程序 2.1 UDP通信原理 UDP协议是一种不可靠的网络协议&#xff0c;它在通信的两端各建立一个Socket对象, 但是这两个Socket只是发送&#xff0c;接收数据的对象因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念 Java提供了DatagramSocket类作为基…

低代码选型,论协同开发的重要性

Git是一款用于分布式版本控制的免费开源软件: 它可以跟踪到所有文件集中任意的变更&#xff0c;通常用于在软件开发期间&#xff0c;协调配合程序员之间的代码程序开发工作。 Git 最初诞生的原因源于Linux 内核的开发&#xff0c;2005年Linus Torvalds 编写出了Git。其他内核开…

AI作画—中国画之山水画

山水画&#xff0c;简称“山水”&#xff0c;中国画的一种&#xff0c;描写山川自然景色为主体的绘画。山水画在我国绘画史中占有重要的地位。 山水画形成于魏晋南北朝时期&#xff0c;但尚未从人物画中完全分离。隋唐时始终独立&#xff0c;五代、北宋时趋于成熟&#xff0c;…

Solon2 之基础:四、应用启动过程与完整生命周期

串行的处理过程&#xff08;含六个事件扩展点 两个函数扩展点&#xff09;&#xff0c;代码直接、没有什么模式。易明 提醒&#xff1a; 启动过程完成后&#xff0c;项目才能正常运行&#xff08;启动过程中&#xff0c;不能把线程卡死了&#xff09;AppBeanLoadEndEvent 之前…

【C++】类和对象(完结篇)

文章目录1. 再谈构造函数1.1 初始化列表1.2 explicit关键字2. static 成员2.1 静态成员变量2.1 静态成员函数2.3 练习2.4 总结3. 匿名对象4. 友元4.1 友元函数4.2 友元类5. 内部类6. 拷贝对象时编译器的一些优化7. 再次理解类和对象这篇文章呢&#xff0c;我们来再来对类和对象…

TypeScript学习笔记(一)编译环境、数据类型、函数类型、联合类型

文章目录编译环境基本类型函数类型函数重载联合类型和函数重载编译环境 TypeScript最终会被编译成JavaScript来运行&#xff0c;所以我们需要搭建对应的环境。 首先我们要全局安装typescript # 安装命令 npm install typescript -g # 查看版本 tsc --version⭐️ 方式一&…

【2023-2-23】FastDeploy 安装教程

【2023-2-22】FastDeploy 安装编译教程 该测试 FastDeploy CPU版本。 1. fastDeploy库编译 1.1 官方预编译库下载 预编译库下载安装 1.2 自定义CPU版本库编译 官方编译FastDeploy教程 CMakeGUI VS 2019 IDE编译FastDeploy 本人编译教程 CMAKE_CONFIGURATION_TYPES 属性设…

(三十一)大白话MySQL如果事务执行到一半要回滚怎么办?再探undo log回滚日志原理

之前我们已经给大家深入讲解了在执行增删改操作时候的redo log的重做日志原理&#xff0c;其实说白了&#xff0c;就是你对buffer pool里的缓存页执行增删改操作的时候&#xff0c;必须要写对应的redo log记录下来你做了哪些修改 如下图所示&#xff1a; 这样万一要是你提交事…

渗透测试之DNS域名信息探测实验

渗透测试之DNS域名信息探测实验实验目的一、实验原理1.1 域名1.2 .域名的构成1.3 域名的基本类型1.4 域名级别二、实验环境2.1 操作机器三、实验步骤1. 使用sp查询域名信息2. 进行探测实验实验目的 掌握使用nslookup进行DNS域名信息探测的原理和方式了解子域名查询网站 一、实…

PCB封装孔小,元器件无法插入,如何解决?

DIP就是插件&#xff0c;采用这种封装方式的芯片有两排引脚&#xff0c;可以直接焊在有DIP结构的芯片插座上或焊在有相同焊孔数的焊位中。其特点是可以很方便地实现PCB板的穿孔焊接&#xff0c;和主板有很好的兼容性。但是由于其封装面积和厚度都比较大&#xff0c;而且引脚在插…

Allegro如何打开格点显示效果操作指导

Allegro如何打开格点显示效果操作指导 Allegro可以设置格点显示效果,以格点来判定走线等等是否都处于格点上,如下图 如何打开格点显示效果,具体操作如下 点击Setup点击Grids

KUKA机器人_基础编程中的变量和协定

KUKA机器人_基础编程中的变量和协定 KUKA机器人KRL中的数据保存:  每个变量都在计算机的存储器中有一个专门指定的地址  一个变量用非KUKA关键词的名称来表示  每个变量都属于一个专门的数据类型  在应用前必须声明变量的数据类型  在KRL中有局部变量和全局变量之分…

winapi获取和修改camera raw界面元素数据

camera raw 界面如下&#xff1a; 需求就是根据 windows api 来操作界面右边的色温、色调、曝光等属性&#xff0c;进而对图片进行调色。根据 spy 捕获的窗口信息&#xff0c;理论上是可以拿到并修改值的。 根据 class 可以先拿到窗口句柄&#xff1a; #define CAMERA_RAW_CLA…

2022年工程机械出口专题研究【重工】

文章目录2022年工程机械出口专题研究1、中国是全球工程机械第一大市场&#xff0c;竞争力逐步提升2、工程机械出口高增&#xff0c;市场分布趋于多元&#xff0c;企业营收获益3、海外市场高速增长原因为何&#xff1f;4、海外市场增长动能预测附件&#xff1a;2022年工程机械出…