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:

  1. Invalid measurement layout drawing
  2. 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.

  1. RecyclerView recycling and reuse areViewHolderNot View.
  2. RecyclerView is just a ViewGroup, where the real sliding is done in LayoutManager.
  3. Recycle: When an itemView is not visible, it is put into memory for reuse.
  4. Reuse: quartet,mChangedScrap,mCacheViews, developer customization, andRecycledViewPoolIf you don’t have anyonCreatViewHolder.
  5. RecyclerViewPoolThe 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