preface
The advent of RecyclerView allows us to implement more and more complex sliding layouts, including different layout types, different data types. However, the more complex the layout, the more obvious it will be.
These include the following points:
- Invalid measurement layout drawing
- Repeat initialization of the template
Through the sliding log analysis, we can find that the same template slides up and down at the same time, the onBindView method will be restarted, even if the content of the template does not change. If there is a lot of logic to perform in this method, this will result in stuttering.
The principle of
So why did you go back to the onBindView method, you might say look at the source code to see. Yes, when you don’t know how it is implemented, it is often most straightforward to look at the source code. But today this is not the focus of this article, about RecyclerView reuse and recycling online there are a lot of source code analysis, here is not a source code explanation, just do some simple introduction.
- RecyclerView recycling and reuse are
ViewHolder
Not View. - RecyclerView is just a ViewGroup, where the real sliding is done in LayoutManager.
- Recycle: When an itemView is not visible, it is put into memory for reuse.
- Reuse: quartet,
mChangedScrap
,mCacheViews
, developer customization, andRecycledViewPool
If you don’t have anyonCreatViewHolder
. RecyclerViewPool
The viewtype-array storage mode is viewtype-array, which means that a maximum of five pairs of each type can be stored.
Most of the cache is taken from recyclerViewPool, which must go onBindViewHolder. This is the answer to our question above, so our idea is to control the execution of the corresponding logic in onBindView by judging the change of data, to improve performance.
DiffUtil is mainly used with RecyclerView or ListView, by DiffUtil to find out the change of each item, by RecyclerView. Adapter updates the UI.
If you are not familiar with DiffUtil, you can read this article RecyclerView data update artifact – DiffUtil.
The idea of this optimization is to determine the change of old and new items in onBindviewHolder to achieve accurate update.
implementation
Determine the difference between old data and new data. If the data is complex, how can you determine it? We can summarize this data with a few major fields.
Public interface IElement {/** * Data content * @return*/ String diffContent(); }Copy the code
All data beans implement this interface and then define their main fields in diffContent. Without implementing this interface, it makes no sense to use DiffUtils.
Let’s set up the data step by step to explain, easy to understand.
When the data is requested from the network, the refreshDataSource method is used.
*/ public final void refreshDataSource(List<DATA> pagedList) { mDiffer.submitList(pagedList); }Copy the code
The submitList compares old and new data and provides the comparison result to the Adapter.
Public void submitList(final List<T> newList) {public void submitList(final List<T> newList) {if(newList == mList) {// Try to notify when rendering is completereturn; } final int runGeneration = ++mMaxScheduledGeneration; // If the new collection is empty, remove all of the old collectionif (newList == null) {
int countRemoved = mList.size();
mList = null;
mUpdateCallback.onRemoved(0, countRemoved);
return; } // If the old set is empty, insert all of the new setif (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) {
returnmConfig.getDiffCallback().areContentsTheSame( oldList.get(oldItemPosition), newList.get(newItemPosition)); @override public Object getChangePayload(int oldItemPosition, int newItemPosition) {returnmConfig.getDiffCallback().getChangePayload( oldList.get(oldItemPosition), newList.get(newItemPosition)); }}); mConfig.getMainThreadExecutor().execute(newRunnable() {
@Override
public void run() {
if(mMaxScheduledGeneration = = runGeneration) {/ / refresh layout diffResult dispatchUpdatesTo (mUpdateCallback); }}}); }}); }Copy the code
The updateDataSource is used to update the data source and make sure it is the latest.
DataElementCache holds:
private volatile ConcurrentMap<IElement,ElementRecord> elementRecords = new ConcurrentHashMap<>();
Copy the code
ElementRecord records the current data and uniquely identifies the UniqueId. Major fields are presented in md5 mode to reduce time consumption.
This provides asynchronous data comparison logic. MUpdateCallback implements ListUpdateCallback interface to realize the refresh function of ADPter.
@Override
public void onChanged(int position, int count, Object payload) {
recordChanged(position,count);
super.onChanged(position, count, payload);
}
@Override
public void onInserted(int position, int count) {
recordChanged(position, count);
super.onInserted(position, count);
}
private void recordChanged(int position, int count) {
int tempPosition = position;
for(int i = 0; i <count; i++) { // SparseArray changedPositions.put(tempPosition,tempPosition); tempPosition++; }}Copy the code
The UI update must be done in the main thread, but DiffUtil is a time-consuming operation, so one of its wrapper classes AsyncListDifferConfig is used this time
First, create a Differ object in initialization.
/** * private final IDataDiff mDataDiff; /** DATA comparison tool */ 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);
}
Copy the code
AsyncListDifferConfig takes three arguments: DiffUtil’s inner class ItemCallback, DiffUtil’s Item comparison thread, and the main thread.
ItemCallback is its abstract inner class, McOnfig.getdiffcallback (). Let’s look at some of the methods it implements:
@Override
public boolean areItemsTheSame(IElement oldItem, IElement newItem) {
returnareContentsTheSame(oldItem, newItem); } /** * The general idea is to compare the object address first, then compare the content, improve the efficiency of comparison ** @param oldItem * @param newItem * @return
*/
@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());
}
Copy the code
AreItemTheSame and areContentsTheSame are both used to determine whether the old and new data are the same, so the same logic is used here. DiffContent stores the string concatenated with several fields that are affected by the data.
The set type used by dataElementCache to store all data is the Array of IElement-ElementRecord. IElement is the data itself, and ElementRecord is a record set of data, which contains data and the unique identifier of data.
MDiffer will be covered later.
Let’s take a look at what the key onBindViewHolder does:
@Override
public final void onBindViewHolder(VH holder, int position) {
if(null ! = holder && holder.itemView ! = null) { tryBindData(holder, position, this.getItem(position)); } } private void tryBindData(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 refresh or create"+ holder.getItemViewType());
}else if(needBind = hasDataContentChanged(oldDataRecord,newData)){
Log.d(getClass().getName(),"Adapter onBindData slides content change"+ holder.getItemViewType());
}
if(needBind){
refreshAndBind(holder, position, newData);
}else {
Log.d(getClass().getName(),"Adapter onBindData reuse does not refresh"+ holder.getItemViewType()); }}Copy the code
Check if it’s a refresh change, check if it’s a slide change, and refresh the layout if it’s a change, otherwise do nothing.
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);
}
Copy the code
It can be seen that mDataDiff is mainly used to determine whether old and new data are the same. Let’s implement the comparison in mDataDiff:
@Override
public boolean areItemsChanged(T oldItem, T newItem, int position) {
boolean changed = changedPositionCallback.hasPositionChanged(position);
if(changed){
changedPositionCallback.removeChangedPosition(position);
}
return changed;
}
@Override
public boolean areContentsChanged(ElementRecord oldElementRecord, T newItem) {
returnoldElementRecord ! =null && oldElementRecord.getElement() ! = newItem && newItem! =null && ! sameContent(oldElementRecord,newItem); } private boolean sameContent(ElementRecord oldElementRecord, T newItem){ final ElementRecord newElementRecord = dataCache.getRecord(newItem);if(newElementRecord == null){
return false;
}
if(IDHelper.forceRefresh(newElementRecord) || IDHelper.forceRefresh(oldElementRecord)){
return false;
}
return newElementRecord.getUniqueId().equals(oldElementRecord.getUniqueId());
}
Copy the code
The comparison idea is as follows: First determine whether the viewHolder is in changedPositions, which are provided and implemented by ChangeListCallback. Second, judge the two objects and the unique identifier.
There are two comparison classes used: the ItemCallback comparison class and the mDataDiff comparison class, which is easy to confuse.
The most important code is in this sentence:
diffResult.dispatchUpdatesTo(mUpdateCallback);
Copy the code
DiffResult provides the minimum change to the ADPTER for local flushing.
conclusion
So that’s pretty much the end of what I want to talk about, and HOPEFULLY it’s helpful. Thank you for seeing this.
The resources
RecyclerView data update artifact – DiffUtil