It is easiest to call notifyDataSetChanged() when the list data changes. Don’t worry about the details of the changes, just brush them all over. But it’s also the most expensive. Read this source code walk to see why it is so expensive.

Observer model

Adapter. NotifyDataSetChanged () will refresh operation entrusted to AdapterDataObservable

public class RecyclerView {
    public abstract static class Adapter<VH extends ViewHolder> {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        public final void notifyDataSetChanged(a) { mObservable.notifyChanged(); }}}Copy the code

AdapterDataObservable is a static internal class of RecyclerView that inherits from Observable:

public class RecyclerView {
    static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public void notifyChanged(a) {
            // Iterate over all observers and delegate
            for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); }}... }}Copy the code

An Observable is an abstract Observable:

// Observed. Generics represent the type of observer
public abstract class Observable<T> {
    // List of observers
    protected final ArrayList<T> mObservers = new ArrayList<T>();

    // Register the observer
    public void registerObserver(T observer) {...synchronized(mObservers) { ... mObservers.add(observer); }}// Unregister the observer
    public void unregisterObserver(T observer) {...synchronized(mObservers) {
            intindex = mObservers.indexOf(observer); . mObservers.remove(index); }}// Remove all observers
    public void unregisterAll(a) {
        synchronized(mObservers) { mObservers.clear(); }}}Copy the code

An Observable holds a set of observers, uses generics to represent the type of observer, and defines methods for registering and unregistering observers.

When are the observers of Adapter data registered?

public class RecyclerView {
    // An observer instance of list data changes
    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
    
    // Set Adapter for RecyclerView
    private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
        if(mAdapter ! =null) {
            // Remove the previous observer
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this); }...final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if(adapter ! =null) {
            // Register a new observer
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this); }... }public abstract static class Adapter<VH extends ViewHolder> {
        // Register the observer
        public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) { mObservable.registerObserver(observer); }}}Copy the code

When binding Adapter for RecyclerView, an observer instance RecyclerViewDataObserver is registered:

public class RecyclerView {
    private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {}

        @Override
        public void onChanged(a) {
            assertNotInLayoutOrScroll(null);
            mState.mStructureChanged = true;
            processDataSetCompletelyChanged(true);//
            if(! mAdapterHelper.hasPendingUpdates()) { requestLayout(); }}... }}Copy the code

It inherits from an abstract observer AdapterDataObserver:

public class RecyclerView {
    public abstract static class AdapterDataObserver {
        public void onChanged(a) {}
        public void onItemRangeChanged(int positionStart, int itemCount) {}
        public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
            onItemRangeChanged(positionStart, itemCount);
        }
        public void onItemRangeInserted(int positionStart, int itemCount) {}
        public void onItemRangeRemoved(int positionStart, int itemCount) {}
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {}}}Copy the code

AdapterDataObserver defines six methods for updating a list, of which the first is a full update and the next five are partial updates. This one focuses on analyzing full updates.

Before analyzing the specific update logic, a summary can be made:

RecyclerView refreshes itself using observer mode, which notifies all observers.

Observers are abstracted as AdapterDataObservers, which are maintained in an AdapterDataObservable.

While binding the Adapter for RecyclerView, a data observer instance is registered to the Adapter.

Nullify everything

Before you can actually refresh the list, do a few things to prepare:

public class RecyclerView {
    void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
        mDispatchItemsChangedEvent |= dispatchItemsChanged;
        mDataSetHasChangedAfterLayout = true;
        // Invalidate all current entries
        markKnownViewsInvalid();
    }
    
    // Invalidate all current entries
    void markKnownViewsInvalid(a) {
        // Iterate over all entries in the list
        final int childCount = mChildHelper.getUnfilteredChildCount();
        for (int i = 0; i < childCount; i++) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
            // Add the FLAG_UPDATE and FLAG_INVALID flags to ViewHolder for each entry in the list
            if(holder ! =null && !holder.shouldIgnore()) {
                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
            }
        }
        markItemDecorInsetsDirty();
        // Invalidate entries in the cachemRecycler.markKnownViewsInvalid(); }}Copy the code

RecyclerView iterates through all current loaded entries and adds FLAG_UPDATE and FLAG_INVALID bits to their ViewHolder. These flag bits determine whether to bind data to an entry during the upcoming Layout entry process. (Analysis in the next section)

In addition to all the current table entries are invalid, also calls the mRecycler. MarkKnownViewsInvalid () :

public class RecyclerView {
    public final class Recycler {
        void markKnownViewsInvalid(a) {
            // Iterate over all off-screen caches
            final int cachedCount = mCachedViews.size();
            for (int i = 0; i < cachedCount; i++) {
                final ViewHolder holder = mCachedViews.get(i);
                // Add the FLAG_UPDATE and FLAG_INVALID flags to each ViewHolder in the off-screen cache as well
                if(holder ! =null) {
                    holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
                    holder.addChangePayload(null); }}if (mAdapter == null| |! mAdapter.hasStableIds()) {// Store ViewHolder in the off-screen cache to the cache poolrecycleAndClearCachedViews(); }}}}Copy the code

RecyclerView invalidates ViewHolder in all off-screen cache. They are also reclaimed into the cache pool. (detailed introduction about RecyclerView multistage cache can click RecyclerView caching mechanism | how to reuse table?)

At this point, we can also make a phased summary:

RecyclerView invalidates everything before it actually refreshes the list. Includes all ViewHolder instances currently populated and in the off-screen cache. Invalidation is shown in the code by adding the FLAG_UPDATE and FLAG_INVALID flags to the ViewHolder.

Real refresh

Look back at the specific logic of the refresh list in onChange() :

public class RecyclerView {
    private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {}

        @Override
        public void onChanged(a) {
            assertNotInLayoutOrScroll(null);
            mState.mStructureChanged = true;
            // invalidate everything
            processDataSetCompletelyChanged(true);
            if(! mAdapterHelper.hasPendingUpdates()) {// True refreshrequestLayout(); }}... }}Copy the code

After invalidating everything, view.requestLayout () is called, which asks for a new layout, and the request is passed continuously to the parent control, all the way to the DecorView, which in turn passes the request to the ViewRootImpl, Use the Profiler to view the call chain as shown in the following figure :(for how to use the Profiler to check the source code can click RecyclerView scrolling is how to achieve? (a) | unlock reading source new position)

ScheduleTraversals () is called when ViewRootImpl receives a redraw request to trigger a redraw from the root view. The redraw task is packaged as a Runnable for Choreographer to hold temporarily. Choreographer then subscribes to the next vSYNC signal. When the next signal arrives, it sends a message to the main thread message queue, and when the main thread processes the message, the top-down redrawing from the root view starts. (about the details of these analysis can click to read the source code long knowledge | Android caton true because “frame”?

View.requestlayout () adds two flag bits to the control:

public class View {
    public void requestLayout(a) {...// Add two flag bits
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;
        // Pass the redraw request to the parent control
        if(mParent ! =null&&! mParent.isLayoutRequested()) { mParent.requestLayout(); }... }}Copy the code

Controls with the PFLAG_FORCE_LAYOUT and PFLAG_INVALIDATED flag bits added trigger the layout when redrawn, that is, onLayout() is called:

As confirmed in the Profiler’s call chain, rearranging the list means rearranging every entry in it, as shown in the codeLinearLayoutManager.onLayoutChildren(). inRecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cacheThis method is mentioned in the.

public class LinearLayoutManager {
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {...// detach and scrap table entriesdetachAndScrapAttachedViews(recycler); .// Fill in the entry
        fill()
}
Copy the code

RecyclerView before layout table item called detachAndScrapAttachedViews (recycler) empty existing table, and then populate the new table.

public class RecyclerView {
    public abstract static class LayoutManager {
        // Delete existing table entries and reclaim them
        public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
            // Walk through the existing entries and recycle them one by one
            final int childCount = getChildCount();
            for (int i = childCount - 1; i >= 0; i--) {
                finalView v = getChildAt(i); scrapOrRecycleView(recycler, i, v); }}// Retrieve the ViewHolder instance
        private void scrapOrRecycleView(Recycler recycler, int index, View view) {
            finalViewHolder viewHolder = getChildViewHolderInt(view); .// recycle to the cache pool
            if(viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); }// Recycle to the scrap cache
            else{ detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); }}}}Copy the code

All existing entries are iterated through one by one, and the corresponding ViewHolder instances are reclaimed one by one. Since the items were added with the FLAG_INVALID bit prior to the relayout, as long as the items are not removed, they are recycled into the RecyclerViewPool. (This is also confirmed by the Profiler call chain.)

Immediately after the existing entry is reclaimed, fill() is called to populate the entry:

public class LinearLayoutManager {
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {
        // Calculate the remaining space
        int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
        // Keep adding entries to the list until there is no space left
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            // Populate a single entrylayoutChunk(recycler, state, layoutState, layoutChunkResult); . }}// Populate a single entry
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {
        // Get the next view to be populatedView view = layoutState.next(recycler); .// Populates the viewaddView(view); . }}Copy the code

Populating an entry is a while loop that calls layoutstate.next () each time to get the next entry to be populated:

public class LinearLayoutManager {
    static class LayoutState {
        View next(RecyclerView.Recycler recycler) {...// Delegate Recycler to get the next table to be filled
            finalView view = recycler.getViewForPosition(mCurrentPosition); .returnview; }}}public class RecyclerView {
    public final class Recycler {
        public View getViewForPosition(int position) {
            return getViewForPosition(position, false); }}View getViewForPosition(int position, boolean dryRun) {
         / / call chain eventually passed to tryGetViewHolderForPositionByDeadline ()
         returntryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; }}Copy the code

Along the invocation chain, went to a key method of reusing table item tryGetViewHolderForPositionByDeadline (), the method according to the priority of trying to obtain ViewHolder from different cache instance. (about the details of the method can click RecyclerView caching mechanism | how to reuse table?)

So the entry that was just put into the cache pool is hit one by one here.

Once we have the ViewHolder instance, we need to determine whether we need to bind data to it:

public class RecyclerView {
    public final class Recycler {
        // Get the ViewHolder instance from the cache and bind the data
        ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {...if (mState.isPreLayout() && holder.isBound()) {
                ...
            } 
            // If the ViewHolder needs to be updated or is invalid, bind data to it again
            else if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                // Bind databound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); }... }private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, int position, long deadlineNs) {...// Bind datamAdapter.bindViewHolder(holder, offsetPosition); . }}public abstract static class Adapter<VH extends ViewHolder> {
        public final void bindViewHolder(@NonNull VH holder, int position) {...// Familiar binding data callbackonBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); . }}public abstract static class ViewHolder {
        // Update the flag bit
        static final int FLAG_UPDATE = 1 << 1;
        // Determine whether the ViewHolder needs to be updated
        boolean needsUpdate(a) {
            return(mFlags & FLAG_UPDATE) ! =0; }}}Copy the code

Since the ViewHolder was added with the FLAG_UPDATE and FLAG_INVALID flag bits during the “invalidate” phase in the previous section, you are satisfied! Holder. IsBound () | | holder. NeedsUpdate () | | holder. IsInvalid () this condition, from the buffer pool hit the ViewHolder rebind the data.

conclusion

  1. RecyclerView refreshes itself using observer mode, which notifies all observers.

  2. Observers are abstracted as AdapterDataObservers, which are maintained in an AdapterDataObservable.

  3. While binding the Adapter for RecyclerView, a data observer instance is registered to the Adapter.

  4. RecyclerView invalidates everything before it actually refreshes the list. Includes all ViewHolder instances currently populated and in the off-screen cache. Invalidation is shown in the code by adding the FLAG_UPDATE and FLAG_INVALID flags to the ViewHolder.

  5. RecyclerView. RequestLayout () is the origin of driver list to refresh. When this method is called, it is redrawn from the root view top to bottom. The redrawing of RecyclerView shows that all table entries are rearranged.

  6. RecyclerView Relayout entries works by recycling existing entries into the cache pool and then repopulating them. Because the ViewHolder instances of these entries are “invalidated” prior to redrawing, there is no escape from rebinding data even if the data has not changed.

See how expensive notifyDataSetChanged() is!

Recommended reading

RecyclerView series article directory is as follows:

  1. RecyclerView caching mechanism | how to reuse table?

  2. What RecyclerView caching mechanism | recycling?

  3. RecyclerView caching mechanism | recycling where?

  4. RecyclerView caching mechanism | scrap the view of life cycle

  5. Read the source code long knowledge better RecyclerView | click listener

  6. Proxy mode application | every time for the new type RecyclerView is crazy

  7. Better RecyclerView table sub control click listener

  8. More efficient refresh RecyclerView | DiffUtil secondary packaging

  9. Change an idea, super simple RecyclerView preloading

  10. RecyclerView animation principle | change the posture to see the source code (pre – layout)

  11. RecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cache

  12. RecyclerView animation principle | how to store and use animation attribute values?

  13. RecyclerView list of interview questions | scroll, how the list items are filled or recycled?

  14. RecyclerView interview question | what item in the table below is recycled to the cache pool?

  15. RecyclerView performance optimization | to halve load time table item (a)

  16. RecyclerView performance optimization | to halve load time table item (2)

  17. RecyclerView performance optimization | to halve load time table item (3)

  18. How does RecyclerView roll? (a) | unlock reading source new posture

  19. RecyclerView how to achieve the scrolling? (2) | Fling

  20. RecyclerView Refresh list data notifyDataSetChanged() why is it expensive?