preface
The emergence of RecyclerView allows us to implement more and more complex sliding layouts, including different layout types and different data types. However, the more complex the layout, the more obvious the phenomenon of Caton.
Among them, there are the following points:
- Invalid survey layout drawing
- Duplicate initialization of template
The first mock exam is the first mock exam. We can find that the same template will slide back and forth with onBindView method, even if the template content is unchanged. If there is a lot of logic to be executed in this method, it will lead to the emergence of Caton.
principle
So why do you go back to the onBindView method? You may say you can see the source code. Yes, when you don't know how to implement it, looking at the source code is often the most direct and effective. However, this is not the focus of this article today. There are many source code analysis on the recycling and recycling of RecyclerView on the Internet. Here we will not post the source code explanation one by one, but just make some brief introduction.
[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-ybfrlh9k-1640143106872)( https://github.com/lennyup/im...)]
- The RecyclerView recycles and reuses the ViewHolder instead of the View.
- RecyclerView is just a ViewGroup, and its real sliding is in LayoutManager.
- Recycling: when an itemView is not visible, it will be put into memory for reuse.
- Reuse: there is no onCreatViewHolder in the Quartet, mchangedsrap, mCacheViews, developer customization and RecycledViewPool.
- The storage method in RecyclerViewPool is viewtype array, that is, a maximum of 5 for each type.
Most of the cache is taken from the recyclerViewPool. The recyclerViewPool must use the onBindViewHolder method. This is the answer to our above questions, so our idea comes. We can control the execution of corresponding logic in onBindView by judging the change of data to improve performance.
DiffUtil is mainly used with RecyclerView or ListView. DiffUtil finds out the changes of each item and RecyclerView. The Adapter updates the UI.
The idea of this optimization is to judge the changes of old and new item s in onBindviewHolder to achieve accurate update.
realization
Judge the difference between old and new data. If the data is complex, how to judge it? We can summarize this data with several main fields.
public interface IElement { /** * Data content * @return Returns the contents of this data body different from other data bodies */ String diffContent(); }
All data bean s should implement this interface, and then define their own main fields in diffContent. It is meaningless to use DiffUtils without implementing this interface.
We explain step by step from the steps of setting data for easy understanding.
When the data is returned from the network request, go to the refreshDataSource method.
/** * Refresh List * * @param pagedList New list data */ public final void refreshDataSource(List<DATA> pagedList) { mDiffer.submitList(pagedList); }
Compare the old and new data in the submittlist and provide the comparison results to the Adapter.
/** * Compare the data differences, distribute the difference results, call the local refresh API, and increase the version number every time you request the interface * @param newList New data source */ public void submitList(final List<T> newList) { if (newList == mList) { // Try to notify when rendering is complete return; } final int runGeneration = ++mMaxScheduledGeneration; // If the new collection is empty, remove all the old collection if (newList == null) { int countRemoved = mList.size(); mList = null; mUpdateCallback.onRemoved(0, countRemoved); return; } // If the old set is empty, insert all of the new set 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 can be understood as the key data, that is, where the data of new and old items has changed, and locally refresh an item -- null is returned by default @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) { //Refresh layout diffResult.dispatchUpdatesTo(mUpdateCallback); } } }); } }); }
updateDataSource is used to update the data source to ensure that it is up-to-date.
The dataElementCache stores:
private volatile ConcurrentMap<IElement,ElementRecord> elementRecords = new ConcurrentHashMap<>();
ElementRecord records the current data and unique identifier UniqueId, and presents the main fields in md5 to reduce time-consuming.
The logic for asynchronous data comparison is provided here. mUpdateCallback implements the ListUpdateCallback interface and the refresh function of the adapter.
@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++; } }
Updating the UI must be in the main thread, but DiffUtil is a time-consuming operation, so its encapsulated class AsyncListDifferConfig is used this time
First, create a new difference object in initialization.
/** * Data item comparison tool */ 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); }
Asynclistdiffererconfig requires three parameters: the internal class ItemCallback of diffutil, the item comparison thread of diffutil, and the main thread.
ItemCallback is its abstract inner class, that is, mconfig Getdiffcallback(), let's take a look at several methods to be implemented:
@Override public boolean areItemsTheSame(IElement oldItem, IElement newItem) { return areContentsTheSame(oldItem, newItem); } /** * The overall idea is to compare the object address first, compare the content, and improve the comparison efficiency * * @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()); }
areItemTheSame and areContentsTheSame are used to judge whether the old and new data are the same, so the same logic is used here. diffContent stores strings spliced by several fields that affect the data.
The collection type used by dataElementCache to store all data is Array of IElement ElementRecord. IElement is the data itself, and ElementRecord is the record set of the data, including the data and the unique identification of the data.
Mdifer will be discussed later.
Let's take a look at what is done in the key onBindViewHolder:
@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 new"+ holder.getItemViewType()); }else if(needBind = hasDataContentChanged(oldDataRecord,newData)){ Log.d(getClass().getName(),"adapter onBindData Sliding content change"+ holder.getItemViewType()); } if(needBind){ refreshAndBind(holder, position, newData); }else { Log.d(getClass().getName(),"adapter onBindData Reuse without refresh"+ holder.getItemViewType()); } }
First judge whether it is a refresh change, and then judge whether it is a sliding change. If there is a change, refresh the layout, 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); }
It can be seen that mDataDiff is mainly used to judge whether the 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) { return oldElementRecord !=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()); }
The comparison idea is: first judge whether the viewHolder is in changedPositions, which is provided and implemented by ChangeListCallback. Secondly, judge the two objects and the unique identifier.
Two comparison classes are used here: one is the comparison class of ItemCallback and the comparison class of mDataDiff, which is easy to be confused here.
The most critical code is in this sentence:
diffResult.dispatchUpdatesTo(mUpdateCallback);
diffResult will provide the minimum amount of change to the adapter for local refresh.
summary
At this point, what I want to say is almost over. I hope it will be helpful to you. Thank you for seeing here.
Related tutorials
Android Foundation Series tutorials:
Android foundation course U-summary_ Beep beep beep_ bilibili
Android foundation course UI layout_ Beep beep beep_ bilibili
Android basic course UI control_ Beep beep beep_ bilibili
Android foundation course UI animation_ Beep beep beep_ bilibili
Android basic course - use of activity_ Beep beep beep_ bilibili
Android basic course - Fragment usage_ Beep beep beep_ bilibili
Android basic course - Principles of hot repair / hot update technology_ Beep beep beep_ bilibili
This article is transferred from https://juejin.cn/post/6844903975796342798 , in case of infringement, please contact to delete.