DiffUtils让你的RecyclerView如斯顺滑,如何让拉锁顺滑,但是,越是复杂的布局,出
DiffUtils让你的RecyclerView如斯顺滑,如何让拉锁顺滑,但是,越是复杂的布局,出
前言
RecyclerView的出现让我们可以实现更多更复杂的滑动布局,包括不同的布局类型,不同的数据类型。但是,越是复杂的布局,出现卡顿的现象就会越发的明显。
这其中不乏有以下几点:
无效的测量布局绘制
模版的重复初始化
通过滑动的日志分析,我们可以发现同一模版在上滑下滑的同时,会重新走onBindView
方法,即使这一模版内容没有任何变化的情况下。如果在这个方法中所要执行的逻辑很多,这将会导致卡顿的出现。
原理
那么为何会重新走onBindView
方法呢,你可能会说去看源码就知道了呀。没错,当你不知道它是如何实现的时候,去看源码往往是最直接有效的。但是今天这个并不是这篇文章的重点,关于RecyclerView的复用和回收网上有很多源码的解析,这里就不一一贴源码解释了,只是做一些简单的介绍。
RecyclerView的回收以及复用的都是
ViewHolder
而不是View。RecyclerView只是一个ViewGroup,其真正实现滑动的是在LayoutManager中。
回收:当一个itemView不可见时,会将其放到内存中,以便实现复用。
复用:四重奏,
mChangedScrap
、mCacheViews
、开发者自定义以及RecycledViewPool
中,都没有才会onCreatViewHolder
。RecyclerViewPool
中的存储方式是 viewType-Array,也就是对对于每种类型最多存5个。
大部分的缓存是从recyclerViewPool中拿的,recyclerViewPool一定会走onBindViewHolder
方法。这也就是回答了我们上面的提问,所以我们的思路就来了,可以通过判断数据的变化来控制onBindView中相应逻辑的执行,来提升性能。
DiffUtil
主要是和RecyclerView或者ListView配合使用,由DiffUtil找出每个item的变化,再由RecyclerView。Adapter来更新UI。
这次优化的思路就是在onBindviewHolder中判断新旧item的变化,来做到精准更新。
实现
更新UI必须要在主线程中,但是DiffUtil是一个耗时的操作,所以这次用的是它的一个封装类AsyncListDifferConfig
首先,在初始化中新建Differ对象。
private final IDataDiff mDataDiff; private final AsyncListDifferDelegate<DATA> mDiffer; private final IDataCache<DATA> dataElementCache; public BaseSwiftAdapter(Context mContext) { this.mContext = mContext; dataElementCache = new ElementCache<>(); final DiffCallBack diffCallBack = new DiffCallBack(dataElementCache); @SuppressLint("RestrictedApi") AsyncDifferConfig config = new AsyncDifferConfig.Builder<>(diffCallBack) .setBackgroundThreadExecutor(AppExecutors.backGroudExecutors) .setMainThreadExecutor(AppExecutors.mainExecutors) .build(); ChangeListCallback changedPositionCallback = new ChangeListCallback(this); mDataDiff = new DataDiffImpl<>(changedPositionCallback, dataElementCache); mDiffer = new AsyncListDifferDelegate(changedPositionCallback, config, dataElementCache); }
AsyncListDifferConfig
需要三个参数:DiffUtil的内部类ItemCallback、diffUtil的item比较线程、主线程。
ItemCallback
是它的抽象内部类,看下它要实现的几个方法:
@Override public boolean areItemsTheSame(IElement oldItem, IElement newItem) { return areContentsTheSame(oldItem, newItem); } @Override public boolean areContentsTheSame(IElement oldItem, IElement newItem) { if (newItem == null) { return true; } if (oldItem == newItem) { return true; } recordNewElement(newItem); final String newContent = newItem.diffContent(); if(newContent == null || "".equals(newContent)){ return false; } return newContent.equals(oldItem.diffContent()); }
areItemTheSame和areContentsTheSame,都是用来判断新旧数据是否相同,所以这里用了同一个逻辑:先比较对象再比较关键字段,diffContent中存放该数据具有影响的几个字段相拼接的字符串。
dataElementCache用来存储所有数据的集合类型是IElement-ElementRecord的Array。IElement是数据本身,ElementRecord是数据的记录集,包含数据以及数据的唯一标示。
mDataDiff以及mDiffer会在后续中讲到。
我们来看下关键的onBindViewHolder
中所做的事情:
@Override public final void onBindViewHolder(VH holder, int position) { if (null != holder && holder.itemView != null) { onBindData(holder, position, this.getItem(position)); } } private void onBindData(VH holder, int position, DATA newData) { final ElementRecord oldDataRecord = holder.content(); boolean needBind ; if(needBind = (hasPositionDataRefreshChanged(oldDataRecord == null ? null : (DATA) oldDataRecord.getElement(), newData, position) || oldDataRecord == null) ){ Log.d(getClass().getName(),"adapter onBindData 刷新或者新建"+ holder.getItemViewType()); }else if(needBind = hasDataContentChanged(oldDataRecord,newData)){ Log.d(getClass().getName(),"adapter onBindData 滑动内容改变"+ holder.getItemViewType()); } if(needBind){ refreshAndBind(holder, position, newData); }else { Log.d(getClass().getName(),"adapter onBindData 复用不刷新"+ holder.getItemViewType()); } }
先去判断是否是刷新变化,其次去判断是否是滑动变化,如果有变化就刷新布局,否则什么也不做。
private boolean hasPositionDataRefreshChanged(DATA oldItem, DATA newItem, int position){ return mDataDiff.areItemsChanged(oldItem, newItem, position); } private boolean hasDataContentChanged(ElementRecord oldItem, DATA newItem){ return mDataDiff.areContentsChanged(oldItem, newItem); }
可以看出mDataDiff
主要用来判断新旧数据是否相同。
其中比较思路为:先判断该viewHolder是否在changedPositions中,changedPositions由ChangeListCallback来提供并实现。其次判断两个对象以及唯一标示。
/** * 刷新列表 * * @param pagedList 新的列表数据 */ public final void refreshDataSource(List<DATA> pagedList) { mDiffer.submitList(pagedList); }
所有的刷新都要走这个方法。
/** * 比较数据差异,分发差异结果,调用局部刷新API。 * @param newList 新的数据源 */ public void submitList(final List<T> newList) { if (newList == mList) { tryNotity(); return; } final int runGeneration = ++mMaxScheduledGeneration; // 如果新集合是空 就把老集合所有都remove if (newList == null) { int countRemoved = mList.size(); mList = null; mUpdateCallback.onRemoved(0, countRemoved); return; } // 如果老集合是空 就把新集合所有都insert if (mList == null) { mList = newList; updateDataSource(Collections.unmodifiableList(newList)); mConfig.getBackgroundThreadExecutor() .execute( new Runnable() { @SuppressLint("RestrictedApi") @Override public void run() { for (int i = 0; i < newList.size(); i++) { final T t = newList.get(i); if(t!=null){ dataElementCache.putRecord(new ElementRecord(IDHelper.getUniqueId(t),t)); } } dataElementCache.copySelf(); } }); mUpdateCallback.onInserted(0, newList.size()); return; } final List<T> oldList = mList; mConfig.getBackgroundThreadExecutor().execute(new Runnable() { @SuppressLint("RestrictedApi") @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) { return mConfig.getDiffCallback().areItemsTheSame( oldList.get(oldItemPosition), newList.get(newItemPosition)); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { return mConfig.getDiffCallback().areContentsTheSame( oldList.get(oldItemPosition), newList.get(newItemPosition)); } // payload可以理解为关键的数据,就是新老item的数据中 到底哪里变化了,局部刷新某个item -- 默认返回null @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) { return mConfig.getDiffCallback().getChangePayload( oldList.get(oldItemPosition), newList.get(newItemPosition)); } }); mConfig.getMainThreadExecutor().execute(new Runnable() { @Override public void run() { if (mMaxScheduledGeneration == runGeneration) { //刷新布局 diffResult.dispatchUpdatesTo(mUpdateCallback); } } }); } }); }
这里提供了异步进行数据比较的逻辑,mUpdateCallback就是ChangeListCallback,实现ListAdapterListUpdateCallback接口,实现adpter的刷新功能。
最关键的代码在这句:
diffResult.dispatchUpdatesTo(mUpdateCallback);
diffResult会将最小变化量提供给adpter,让其实现局部刷新。
总结
到了这里,我要讲的就差不多要结束了,希望对你们有所帮助。谢谢你们看到了这里。
用户评论