This article analyzes RecyclerView data refresh, you will see the use of caching in the data refresh process, and the impact of different data refresh methods on performance.

Before reading this article, you need the foundation of the previous two articles

  1. RecyclerView source code analysis: basic display
  2. RecyclerView source code analysis: sliding, sub-view recycling

Setting the data observer

When creating a Adapter for RecyclerView, create a Data observer for RecyclerViewDataObserver mObserver. The observer refreshes the interface as the Adapter notifies it of data updates.

Global refresh

Let’s start with the simplest and most common data refresh method, Adapter#notifyDataSetChanged(), which indicates that the data has completely changed and the interface needs to be refreshed globally.

Adapter#notifyDataSetChanged() calls the recyclerviewdataserver #onChanged() method

private class RecyclerViewDataObserver extends AdapterDataObserver {
    @Override
    public void onChanged(a) {
        // Indicates that the structure of the data has been completely changed
        mState.mStructureChanged = true;
        // 1. Preprocessing to respond to data set changes
        processDataSetCompletelyChanged(true);
        // 2. If there is no action waiting for an update, request a relayout immediately
        if(! mAdapterHelper.hasPendingUpdates()) { requestLayout(); }}}Copy the code

When the RecyclerViewDataObserver receives a message that the data has completely changed, it first does some pre-processing in response to the data set change, and then requests a redesign to refresh the interface.

pretreatment

Let’s start by looking at what the pre-processing of data set changes actually does

void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
    // The parameter is passed with a value of true, indicating that the event of the data change needs to be distributed
    mDispatchItemsChangedEvent |= dispatchItemsChanged;
    // Indicates that the data set has changed completely after layout
    mDataSetHasChangedAfterLayout = true;
    // Mark known views as invalid
    markKnownViewsInvalid();
}
Copy the code

ProcessDataSetCompletelyChanged () first made some state tags, and then call markKnownViewsInvalid () to mark the View known as invalid.

void markKnownViewsInvalid(a) {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        if(holder ! =null && !holder.shouldIgnore()) {
            // 1. RecyclerView all child Views, set FLAG_UPDATE and FLAG_INVALID tagsholder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); }}// 2. Mark ItemDecoration as dirty
    markItemDecorInsetsDirty();
    // Set FLAG_UPDATE and FLAG_INVALID for mCachedViews, and recycle these views by RecyclerPool
    mRecycler.markKnownViewsInvalid();
}
Copy the code

MarkKnownViewsInvalid () handles not only RecyclerView children, but also views in the mCachedViews cache.

Some invisible sub-views were removed due to sliding, and mCachedViews was used to cache them in preference. When the child View is displayed again due to sliding, it is retrieved from mCachedViews.

MarkKnownViewsInvalid () does two things

  1. Mark the View toFLAG_UPDATEandFLAG_INVALID.
  2. Mark the ViewItemDecorationArea fordirty, which sets the layout parameters of the ViewmInsetsDirtyA value oftrueTo refresh the ViewItemDecorationArea.

Refresh the interface

Once the preprocessing is done, we call requestLayout() to rearrange the layout, and we focus on the Layout process.

The layout process is divided into three steps, dispatchLayoutStep1() handles updates and saves animation information, dispatchLayoutStep3() executes animation and does some cleanup, and dispatchLayoutStep2() completes data refresh.

DispatchLayoutStep2 () assigns the subview layout to LayoutManager#onLayoutChildren(). LinearLayoutManager#onLayoutChidren()

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    // ...
    
    // 1. Separate/discard and cache child views
    detachAndScrapAttachedViews(recycler);
    // 2. Populate the subview, implemented by layoutChunk()
    fill(recycler, mLayoutState, state, false);
    
    // ...
}
Copy the code

The LinearLayoutManager works roughly in two steps on the layout of subviews. The first step is to separate/remove the child View and cache it. Then get the child View and populate it with RecyclerView.

The cache

First we have to analyze separation/removal and cache this step, is called LayoutManager# detachAndScrapAttachedViews () method, it will traverse RecyclerView all child View, The LayoutManager#scrapOrRecycleView() method is then called to separate/remove and cache the child views

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.shouldIgnore()) {
        return;
    }
    // The following conditions are met
    // The child View ViewHolder is set to FLAG_INVALID
    // The child View's ViewHolder is not set to FLAG_REMOVED
    // The Adapter does not enable stable ID by default
    if(viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) {// 1. Remove child Views from RecyclerView
        removeViewAt(index);
        // 2. Recycler can be recycled
        recycler.recycleViewHolderInternal(viewHolder);
    } else{ detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); }}Copy the code

In the pre-process, flag the View as FLAG_INVALID, so remove the View from RecyclerView first, using the ViewGroup#removeViewAt() method. Then use Recycler to recycle those removed child views.

Now let’s look at the recycling process

        void recycleViewHolderInternal(ViewHolder holder) {
            // ...
            
            if (forceRecycle || holder.isRecyclable()) {
                // The mCachedViews cache cannot be used because the View is FLAG_INVALID
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // ...
                }
                // Can not use mCachedViews cache, RecyclerPool to recycle
                if(! cached) { addViewHolderToRecycledViewPool(holder,true);
                    recycled = true; }}else{}// ...
        }
Copy the code

Since the child View is FLAG_INVALID, it can only be recycled by RecyclerPool.

fill

Now all child views are recycled by RecyclerPool, so the next step is to analyze how to fill RecyclerView child Views. This step is implemented by LinearLayoutManager#layoutChunk()

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
    // ...
        
    // 1. Get views from Recycler
    View view = layoutState.next(recycler);
        
    // add child View to RecyclerView
    addView(view);
    
    // 3. Measure subview
    measureChildWithMargins(view, 0.0);
    
    // 4. Layout subview
    layoutDecoratedWithMargins(view, left, top, right, bottom);
        
    // ...
}
Copy the code

Fillable views go through these four steps, but let’s focus on how to retrieve views from Recycler. It is by the Recycler# tryGetViewHolderForPositionByDeadline () implementation

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
            
    // 1. First get from mChangedScrap
    // The LinearLayoutManager supports predictive Item animations in most cases
    if(mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder ! =null;
    }

    // 2. Obtain from mAttachedScrap, Hidden View, mCachedViews
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if(holder ! =null) {
            if(! validateViewHolderForOffsetPosition(holder)) {// recycle holder (and unscrap if relevant) since it can't be used
                if(! dryRun) {// we would like to recycle this but need to make sure it is not used by
                    // animation logic etc.
                    holder.addFlags(ViewHolder.FLAG_INVALID);
                    if (holder.isScrap()) {
                        removeDetachedView(holder.itemView, false);
                        holder.unScrap();
                    } else if (holder.wasReturnedFromScrap()) {
                        holder.clearReturnedFromScrapFlag();
                    }
                    recycleViewHolderInternal(holder);
                }
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true; }}}if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 3. Obtain from mAttachedScrap, mCachedViews by stable ID
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if(holder ! =null) {
                // update position
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true; }}if (holder == null&& mViewCacheExtension ! =null) {
            // 4. Obtain from custom cache
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if(view ! =null) { holder = getChildViewHolder(view); }}if (holder == null) { // fallback to pool
            // 5. Obtain the password from RecyclerPool
            holder = getRecycledViewPool().getRecycledView(type);
            if(holder ! =null) {
                // Reset all flag bits
                holder.resetInternal();
                // ...}}if (holder == null) {
            // 6. Create with Adapter
            holder = mAdapter.createViewHolder(RecyclerView.this, type); }}// ...

    // 7. Determine whether to perform the binding operation based on the conditions
    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        // If bound, only the location of predictive Item animations is updated
        holder.mPreLayoutPosition = position;
    } else if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {// If there is no binding, the binding operation needs to be performed
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }

    // 8. Calibrate the layout parameters and update it
    // ...

    return holder;
}
Copy the code

Here I have all the fetch paths marked, but since the View is FLAG_INVALID, it can only be retrieved from RecyclerPool (step 6). After the ViewHolder is retrieved from the RecylerPool, all of its flag bits are reset, so a binding is required (step 7).

Get the View, add it to RecyclerView, and then measure, layout, drawing, so complete the interface refresh process.

summary

Now let’s summarize the pros and cons of the Adapter#notifyDataSetChanged() method.

The advantage is simplicity and brainlessness. The disadvantage is that it affects drawing performance, because it removes, retrieves, retrieves, and binds all child views.

In practice, data often changes only a small part, such as a few items of data updated, a few items of data deleted and so on. At this point we want to refresh only the affected child views, rather than expecting all child views to refresh.

Adapter provides a number of methods for local refreshes, such as notifyItemChanged() for data updates, notifyItemInserted() for data increases, notifyItemRemoved() for data removal, NotifyItemMove () handles data movement and also provides the corresponding range operation method notifyRangXXX(). This way we don’t have to mindlessly use the notifyDataSetChanged() method, but we need to compare the data ourselves and decide which layout refresh method to call.

Local refresh

Global flushes affect drawing performance, so let’s look at how local flushes optimize drawing performance.

We pick the Adapter#notifyItemChanged() method for analysis, which calls the observer’s onItemRangeChanged() method

private class RecyclerViewDataObserver extends AdapterDataObserver {
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        // 1. Notify the AdapterHelper that data in a range has been updated
        if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
            // 2. Trigger the updatetriggerUpdateProcessor(); }}}Copy the code

The AdapterHelper is first notified that a range of data has changed

boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    if (itemCount < 1) {
        return false;
    }
    // Save the UPDATE operation
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
    // Save the operation type
    mExistingUpdateTypes |= UpdateOp.UPDATE;
    // If there is only one update operation to be processed, it needs to be processed immediately
    return mPendingUpdates.size() == 1;
}
Copy the code

AdapterHelper creates an UpdateOp object for the operation type and saves the operation type as well.

As you can see from the return value, if there is only one operation waiting for an update, you need to understand processing. We assume that there is now only one update operation, and triggerUpdateProcessor() is called to handle it

void triggerUpdateProcessor(a) {
    POST_UPDATES_ON_ANIMATION is true if the SDK is greater than 16
    // mHasFixedSize indicates whether there is a fixed size
    // mIsAttached indicates whether RecyclerView is added to the Window
    if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true; requestLayout(); }}Copy the code

Triggering the update operation is conditional. The constraint here is basically just mHasFixedSize, which is set by RecyclerView#setHasFixedSize(). If the width and height of RecyclerView is set to a fixed size, such as 100DP, or match_parent, then you can call setHasFixedSize(true) to set the width and height of RecyclerView to a fixed size. This can sometimes avoid the RecyclerView self-measurement step.

But no matter how you perform the update operation, you go through the Layout process. In dispatchLayoutStep1 () will be called processAdapterUpdatesAndSetAnimationFlags () to handle these updates, and determine whether to perform the animation.

This article does not analyze the animation part of the source.

private void processAdapterUpdatesAndSetAnimationFlags(a) {
    // The LinearLayoutManager supports predictable animation if it is not in state recovery
    if (predictiveItemAnimationsEnabled()) {
        mAdapterHelper.preProcess();
    } else {
        mAdapterHelper.consumeUpdatesInOnePass();
    }
    // omit animation-related code...
}
Copy the code

Predictive Item Animations Are treated in two ways, depending on whether LayoutManager supports Predictive Item animations, but they both end up working with the affected child views.

Predictive Item animations: For add, remove, and move actions (excluding changes), an animation is automatically created that shows where the View is coming from and where it is going.

For a data change operation, processAdapterUpdatesAndSetAnimationFlags () will eventually through AdapterHelper the following code to deal with the affected child View

mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
Copy the code

This mCallback is implemented in RecyclerView

void initAdapterManager(a) {
    mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
        public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
            // Handle range data change operations
            viewRangeUpdate(positionStart, itemCount, payload);
            // Indicates a data change operation
            mItemsChanged = true; }}}Copy the code

ViewRangeUpdate () handles the scope data change operation, which is relatively simple and summarized below

  1. RecyclerViewChild views that are in the range of data changes are marked asFLAG_UPDATEAnd mark itsItemDecorationArea fordirty.
  2. mCachedViewsView, which is cached in the data change range, is marked asFLAG_UPDATEAnd has beenRecyclerPoolRecycling.

DispatchLayoutStep1 () now flags all views affected by the data change (including those cached by mCachedView) as FLAG_UPDATE and then relayouts the child views in dispatchLayoutStep2(). It’s implemented by LayoutManager#onLayoutChildren(), which we’ll still use LinearLayoutManager#onLayoutChildren().

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    // ...
    
    // 1. Separate/discard and cache child views
    detachAndScrapAttachedViews(recycler);
    // 2. Populate the subview, implemented by layoutChunk()
    fill(recycler, mLayoutState, state, false);
    
    // ...
}
Copy the code

Does this code look familiar? Yes, we just analyzed it earlier, but this time it was a local data change, not a global refresh.

The cache

First let’s look at how to separate/discard and cache child views.

RecyclerView# detachAndScrapAttachedViews () will iterate through all the View, and then through ReyclerView# scrapOrRecycleView () to deal with

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.shouldIgnore()) {
        return;
    }
    // The current situation is that View is only FLAG_UPDATE
    if(viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) { }else {
        // 1. Separate child View from RecyclerView
        detachViewAt(index);
        // 2. Cache separated child views
        recycler.scrapView(view);
        // Save information for the animationmRecyclerView.mViewInfoStore.onViewDetached(viewHolder); }}Copy the code

The current situation is that the View is only flagged as FLAG_UPDATE, which is different from the global refresh situation, where the first step is to detach the child View from the RecyclerView instead of removing it. It is implemented through the following callback in RecyclerView

    private void initChildrenHelper(a) {
        mChildHelper = new ChildHelper(new ChildHelper.Callback() {
            public void detachViewFromParent(int offset) {
                final View view = getChildAt(offset);
                if(view ! =null) {
                    final ViewHolder vh = getChildViewHolderInt(view);
                    if(vh ! =null) {
                        // 1. Add FLAG_TMP_DETACHED flag bitvh.addFlags(ViewHolder.FLAG_TMP_DETACHED); }}// 2. Call ViewGroup#detachViewFromParent() to separate the child views
                RecyclerView.this.detachViewFromParent(offset); }}}Copy the code

First mark the child View as FLAG_TMP_DETACHED, and then separate the child View.

What is the difference between removing and detach?

  1. Removal leads to a rearrangement, which isrequestLayout().
  2. Separation is just fromViewGroup#mChildrenRemove references from the array, but separate views must be reattached or deleted within the same drawing cycle. Therefore, no relayout is triggered.

RecyclerView now RecyclerView is separated from RecyclerView and RecyclerView () is used to cache Recycler

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    // Cache the View in different ways depending on the state of the View
    if(holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || ! holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { holder.setScrapContainer(this.false);
        mAttachedScrap.add(holder);
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this.true); mChangedScrap.add(holder); }}Copy the code

All child views of RecyclerView are FLAG_UPDATE and are cached by mChangedScrap, while all other child views are cached by mAttachedScrap.

fill

Now that we’ve seen how the partial flush cache is used, let’s look at how the LinearLayoutManager implements the subView fill, which is implemented on the LinearLayoutManager#layoutChunk()

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
    // ...
        
    // 1. Get views from Recycler
    View view = layoutState.next(recycler);
        
    // 2. Add/attach child View to RecyclerView
    addView(view);
    
    // 3. Measure subview
    measureChildWithMargins(view, 0.0);
    
    // 4. Layout subview
    layoutDecoratedWithMargins(view, left, top, right, bottom);
        
    // ...
}
Copy the code

Is a familiar with the code, we are still focused on how to get the View of the process, from Recycler by Recycler# tryGetViewHolderForPositionByDeadline ()

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
            
    // 1. First get from mChangedScrap
    // The LinearLayoutManager supports predictive Item animations in most cases
    if(mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder ! =null;
    }

    // 2. Obtain from mAttachedScrap, Hidden View, mCachedViews
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if(holder ! =null) {
            if(! validateViewHolderForOffsetPosition(holder)) {// recycle holder (and unscrap if relevant) since it can't be used
                if(! dryRun) {// we would like to recycle this but need to make sure it is not used by
                    // animation logic etc.
                    holder.addFlags(ViewHolder.FLAG_INVALID);
                    if (holder.isScrap()) {
                        removeDetachedView(holder.itemView, false);
                        holder.unScrap();
                    } else if (holder.wasReturnedFromScrap()) {
                        holder.clearReturnedFromScrapFlag();
                    }
                    recycleViewHolderInternal(holder);
                }
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true; }}}if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 3. Obtain from mAttachedScrap, mCachedViews by stable ID
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if(holder ! =null) {
                // update position
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true; }}if (holder == null&& mViewCacheExtension ! =null) {
            // 4. Obtain from custom cache
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if(view ! =null) { holder = getChildViewHolder(view); }}if (holder == null) { // fallback to pool
            // 5. Obtain the password from RecyclerPool
            holder = getRecycledViewPool().getRecycledView(type);
            if(holder ! =null) {
                // Reset all flag bits
                holder.resetInternal();
                // ...}}if (holder == null) {
            // 6. Create with Adapter
            holder = mAdapter.createViewHolder(RecyclerView.this, type); }}// ...

    // 7. Determine whether to perform the binding operation based on the conditions
    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        // If bound, only the location of predictive Item animations is updated
        holder.mPreLayoutPosition = position;
    } else if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {// If there is no binding, the binding operation needs to be performed
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }

    // 8. Calibrate the layout parameters and update it
    // ...

    return holder;
}
Copy the code

For partial data change operations, the detached child views are only flagged as FLAG_UPDATE and FLAG_TMP_DETACHED, so the only available ways to get a View are steps 1 and 2, which are from mChangedScrap and mAttachedScrap. However, for other operations, such as add, remove, or move, views may be retrieved from different paths.

In the case of data changes, the condition for obtaining a ViewHolder from mChangedScrap and mAttachedScrap is basically that the position taken from the ViewHolder is equal to the position filled.

After the ViewHolder is retrieved from mChangedScrap and mAttachedScrap, it is still bound. However, the child View that is affected by the data change is marked as FALG_UPDATE, so the data needs to be bound again so that the data can be updated. For subviews that are not affected by data changes, no binding is required.

Now that we have recycled views from Recycler and recycled views have rebound data, we need to recycle them by attaching them to RecyclerView via LayoutManager#addViewInt()

private void addViewInt(View child, int index, boolean disappearing) {
    final ViewHolder holder = getChildViewHolderInt(child);
    // ...
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if (holder.wasReturnedFromScrap() || holder.isScrap()) {
        if (holder.isScrap()) {
            holder.unScrap();
        } else {
            holder.clearReturnedFromScrapFlag();
        }
        // Create a RecyclerView by attaching it to RecyclerView
        mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
        if(DISPATCH_TEMP_DETACH) { ViewCompat.dispatchFinishTemporaryDetach(child); }}else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
        // Handle child View movement
        
    } else {
        // In all other cases, add child Views to RecyclerView
        mChildHelper.addView(child, index, false);
        lp.mInsetsDirty = true;
        if(mSmoothScroller ! =null&& mSmoothScroller.isRunning()) { mSmoothScroller.onChildAttachedToWindow(child); }}// ...
}
Copy the code

For the child View separated because of data change, it will attach to RecyclerView. However, for move, add, there will be different operations, which will not be analyzed here.

Now the separated child View has been re-attached to RecyclerView, and the data changed part has been updated accordingly, the rest is the drawing work.

summary

Local refresh, separation and re-attachment of the way to deal with those not affected by the child View, and only affected by the child View, or re-binding and then attached, or directly created in add to RecyclerView. Overall, it improves drawing performance relative to global refresh.

DiffUtil

To use a partial refresh, you must compare the data differences before and after and decide which data refresh method to use. The comparison process is often complex, so Google later introduced a utility class called DiffUtil, which abstracts the comparison process by calculating the difference between the data before and after, and then invoking the precise local refresh method.

If you think my article is good, give me a thumbs up and follow me.