RecyclewView 性能优化: 局部数据刷新(DiffUtil )

RecyclewView 局部数据刷新(DiffUtil ) 

最近在项目中遇到一个需求,在加载首页列表信息时,先是要加载缓存内容[写在文件中],如果网络请求有数据,则替换缓存内容,并显示新的内容。需求本来很简单,直接一个RecyclerView解决了,写完代码就提交了,请原谅数据上的美女图,公司的项目就是如此。其实缓存中的数据与新数据,只有第三条发生了改变,其他的都一毛一样;而且你在刷新的时候,会发现一道白光闪过,有人抱怨体验不太好,那好,我改改,改完之后,变成这个样子了:

这里写图片描述

左边是不太讲究的直接RecyclewView.Adapter.notifyItemRangeChanged()的效果,右边的是使用了android.support.v7.util.DiffUtil这个帮助类实现的效果,可以说,差距还是比较大,那么今天就好好说一下这个DiffUtil的作用。

说明

这个DiffUtil存在的意义,是专门为RecyclerView更新item设计的。回想一下,我们当初从ListView中,使用BaseAdapter.notifyDataChanged()无脑全屏刷新,到RecyclerView.Adapter提供了notifyItem**系列,似的RecyclerView刷新时,可以精确到具体的目标item;最后到使用DiffUtil.DiffResult可以精确到某个Item上某个具体的TextView,或者ImageView的刷新。一步一步走来,你会发现,我们可以控制刷新的可视范围越来越精确,进而会使我们的app每次刷新消耗的资源越来越小,效果越来越nice。扯了这么多,还是还来看看传说中的DiffUtil的使用方法吧。

使用

首先我们定义User对象,作为每个RecyclerView 条目的数据载体:

public class User {
	// 用户Id
    public int id ;
    // 用户名称
    public String name;
    //用户图像
    public String url ;
    //用户年龄
    public int age;
}

定义一个Callback继承DiffUtil.Callback,这个比较重要:

public class UserItemDiffCallBack extends DiffUtil.Callback {

	//旧的数据集合
    private List<User> mOldUserList;
    //新的数据集合
    private List<User> mNewUserList;

	//构造方法 传入旧的数据结构和新的数据结构
    public UserItemDiffCallBack(List<User> oldUserList, List<User> newUserList) {
        this.mOldUserList = oldUserList;
        this.mNewUserList = newUserList;
    }

	//获取旧的数据量大小
    @Override
    public int getOldListSize() {
        return null == mOldUserList ? 0 : mOldUserList.size();
    }

	//获取新的数据量大小
    @Override
    public int getNewListSize() {
        return null == mNewUserList ? 0 : mNewUserList.size();
    }

	//判断两个条目是否是一致的
	//在真实的项目中,我们一般使用id或者index搜索来判断两条item是否一致
	//如果我们的id一样,在系统里面我就认为两个数据记录是一样的
    @Override
    public boolean areItemsTheSame(int oldPosition, int newPosition) {
        return mOldUserList.get(oldPosition).id == mNewUserList.get(newPosition).id;
    }

    //这个需要areItemsTheSame 返回true时才调用
    //即使我们的id是一致的,我们在系统中是同一个对象,但是的name可能更新,或者图像可能更新了
    //这里可以填写自己的逻辑,如果图像是一致的,我就认为内容没有变化
    @Override
    public boolean areContentsTheSame(int oldPosition, int newPosition) {
        return TextUtils.equals(mOldUserList.get(oldPosition).url, mNewUserList.get(newPosition).url);
    }

    //这个调用比较奇葩,要求也蛮多的,它需要areItemsTheSame()返回true,说明是同一条数据
    //但是又需要areContentsTheSame()返回false,告诉你虽然我们是同一条数据,但是我们也有不同的
    //它返回的是Object对象,我这里是返回的是Boolean对象,等会告诉你怎么用这个对象
    //当然了,你也可以返回任意的对象,到时候装换一下就可以了。
    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        User newUserEntity = mNewUserList.get(newItemPosition);
        return oldUserEntity.url.equals(newUserEntity.url);
    }
}
  • UserItemDiffCallBack完成之后,需要在RecyclerView.Adapter中调用含有三个参数的onBindViewHolder方法:
  // 这里的 payloads就是UserItemDiffCallBack中getChangePayload中返回的Object集合
  // 如果某个条目没有调用UserItemDiffCallBack#getChangePayload方法,那么那个条目对应的
  // onBindViewHolder中payloads就会为空数组对象
  
  //由于我返回的判断新旧数据的url是否相同,所以直接更新一个item的照片就可以了,对于Item其他的TextView对应的
  //name和age,数据没有变化,就没有必要更新了。
  //这里就是上面所说的,可以精确到某个View的更新了,比notifyItemChanged更加有效了。
  @Override
  public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            super.onBindViewHolder(holder, position, payloads);
            return;
        }
        
        ImageLoader.load(holder.iv, mList.get(position).url);
    }

最后,有新旧数据更新时,在RecyclerView.Adapter中添加方法

    public void setData(List<User> userList) {
	    // 获取DiffResut结果
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new UserItemDiffCallBack(mList, userList));
		
		//使用DiffResult分发给apdate热更新
        diffResult.dispatchUpdatesTo(this);
		
		//按照DiffeResult定义好的逻辑,更新数据
        mList.clear();
        mList.addAll(userList);
    }

使用时:

 List<User> tempList = getNewUserList();
 mCommonAdapter2.setData(tempList);

此时更新数据时,将不再会有满屏白光闪烁,只更新数据变换的项,这个比notifyItemChanged(int position)帅气多了。

对于DiffUtil,基本流程图可以看一下下图:

本篇算是一个小小的开端,下一篇我打算总结一下DiffUtil的基本原理。

 

使用DiffUtil遇到的 异常:

Inconsistency detected. Invalid view holder adapter positionViewHolder

描述:
        使用了DiffUtil之后,发现后台上报了一些异常,是关于RecyclerView滑动异常导致界面崩溃的,按照一些博客上的步骤也没有解决问题。这个问题以前是没有的,应该是DiffUtils使用不当造成的。后来是google发现了问题所在,地址为:https://android.jlelse.eu/smart-way-to-update-recyclerview-using-diffutil-345941a160e0

是我新旧数据添加时先后顺序存在问题,老的逻辑为先比较后数据更改:

  public void setData(List<User> userList) {
	// 获取DiffResut结果
    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new UserItemDiffCallBack(mList, userList));
		
	//使用DiffResult分发给apdate热更新
    diffResult.dispatchUpdatesTo(this);
		
	//按照DiffeResult定义好的逻辑,更新数据
    mList.clear();
    mList.addAll(userList);
    }

按照博客上更改为先更改数据后比较数据:

 public void setData(List<User> userList) {
	// 获取DiffResut结果
    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new UserItemDiffCallBack(mList, userList));
	
	//按照DiffeResult定义好的逻辑,更新数据
    mList.clear();
    mList.addAll(userList);		
        
	//使用DiffResult分发给apdate热更新
    diffResult.dispatchUpdatesTo(this);
    }

暂时没有出现问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值