In this article, we talk about the cache extraction and recycling of RecycleView, which is the core and essence of RecyclerView. It’s also called RecycleView because it can be recycled and reused. Q1: RecycleView is more powerful than ListView. Why is it so powerful? Q2: What do you mean by level 4 caching? Why level 4 cache? Q3: What is the timing of recycling and reuse? Q4: What is the logic of recycling and reuse?

In this article we take a top-down approach, looking at the overall process in general and then striking down the details one by one. You can also bring your own questions, welcome to discuss.

Cache data structure

RecycleView overall cache is 4 levels, logic are concentrated in RecyclerView.Recycle class.

The cache function
AttachedScrap Cache the ViewHolder for detach
CachedViews The cache can be used directly without the need to rebind the ViewHolder
ViewCacheExtension External custom cache plug-in that can handle itself
RecycledViewPool Cache the ViewHolder that needs to be rebind

MScrapList, mChangedScrap and mHiddenViews are all related to animation. We will talk about animation in detail in the future. Cache extraction is done by extracting ViewHolder from each part in a top-down order. The ViewHolder state is used to determine whether the ViewHolder is available or not. If the ViewHolder state is not available, continue to look down. Again, recycling is top-down, so if you can’t save it from the top, you save it from the bottom.

The following analysis will ignore the animation part, which is mixed with the normal cache logic and can affect the normal cache logic analysis.

Preliminary knowledge

There are a few small details we need to know before we get into the actual code. Or there are important judgments that don’t make sense.

ViewHolder state

There are many ViewHolder states that we use. We need to understand the meaning of each state first, and then we can understand the scene when we look into the code. The following table shows what each state means and how representative it is used.

state meaning
isBound This ViewHolder has already bound a position; MPosition, mItemId, and mItemViewType are all valid. OnBindViewHolder will be marked.
needsUpdate The data reflected in the ViewHolder view is stale and needs to be rebind. MPosition and mItemId are valid. NotifyItemChanged is flagged.
isInvalid The data for this ViewHolder is invalid. MPosition and mItemId are invalid. This ViewHolder must be rebound. After resetting the setAdapter/notifyDataSetChanged, all ViewHolder are marked. Or when the cache is extracted and found to be illegal, it will also be marked.
isRemoved The ViewHolder represents data for items previously removed from the dataset, and notifyItemRemoved is flagged
wasReturnedFromScrap It’s from mAttachedScrap, it’s going to have this tag
isTmpDetached Set this flag when the view is temporarily detached from the parent view.

Detach the concept of

The above state is well understood in combination with ordinary use, but isTmpDetached may not be heard of in the process of RecyclerView. Here’s the detach concept. We usually use remove operation, that is, call the removeView of the ViewGroup to completely separate the child View from the parent View. But there is also a detach operation, which is a lightweight detach, that simply sets the View’s reference to the Children array of the ViewGroup to NULL and sets its parent to NULL. When a view is detached, its parent is NULL and cannot be retrieved by calling getChildAt(int). Calling ViewGroup#detachViewFromParent calls the following method to remove it.

private void removeFromArray(int index) {
        final View[] children = mChildren;
        final int count = mChildrenCount;
        if(! (mTransitioningViews ! =null && mTransitioningViews.contains(children[index]))) {
            children[index].mParent = null;
        }
        if (index == count - 1) {
            children[--mChildrenCount] = null;
        } else if (index >= 0 && index < count) {
            children[--mChildrenCount] = null; }}Copy the code

The detached View should be reconnected by calling attachViewToParent(View, int, ViewGroup.layoutParams). Detach should only be temporary It is important to note that reconnection or detach should take place within the same drawing cycle as detach. Detach is much more efficient if, during a drawing cycle, the separation is temporary and there is a high probability that it will be rejoined later.

Knowing the basic concepts above, it will be much easier to analyze the source code. Let’s first analyze the timing of the cache, that is, when the cache will be used.

Cache reclamation timing

Recycle at layout time

Detach all sub-views globally, that is, detach all sub-views. If the layout is still displayed in the display area after completion, then attach it again. Anything else that is not displayed will be removed.

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {... detachAndScrapAttachedViews(recycler); . }Copy the code

DetachAndScrapAttachedViews internal calls scrapOrRecycleView recycle. This step is mainly to reclaim into the level 1 cache.

    private void scrapOrRecycleView(Recycler recycler, int index, View view) {
            finalViewHolder viewHolder = getChildViewHolderInt(view); .if(viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); }else{ detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); }}Copy the code

The above logic will be examined in detail below.

It will be recycled as it slides

We analyzed that in the last video when we talked about sliding. Sliding process will eventually call recycleViewHolderInternal () to recycle, when a View is beyond the boundaries of RecyclerView, can recycle. The internal is mostly reclaimed in level 2 and level 4 caches. There is no collection of the third level mViewCacheExtension, so Recycle does not populate our custom ViewCacheExtension by default.

public void recycleViewHolderInternal(@NonNull View child, @NonNull Recycler recycler) {...if(...) {// CachedViews
                mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if(! cached) {RecycledViewPool RecycledViewPool
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true; }}... }Copy the code

We found in layout operations, internal recycling will judge, is to perform recycleViewHolderInternal () or scrapView (). Detach operation will be scrapView () and remove operation will be recycleViewHolderInternal (). In recycleViewHolderInternal (), we didn’t find the first level cache populated location, actually in scrapView (). As you can see, padding during the sliding process does not involve the first level cache. And the first level of populating is to reclaim only the views that were detach. Here you can see that there are two types of recycling

  1. Perform SCAP to reclaim detach’s View. Corresponding to level 1 cache.
  2. View that executes the recycle remove. Corresponding to two, four level cache.

Cache extraction timing

When is the cache volume fetched, it must be when it is used. The first population, slide population, and refresh data population are all extracted from the cache. Fill logic can be used to call LayoutManager fill and RecyclerView.Recycler’s getViewForPosition. The final call tryGetViewHolderForPositionByDeadline method to extract. Let’s take a look at the internal logic.


    // dryRun: True if ViewHolder should not be removed from scrap/cache
    ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {...if (holder == null) {
                // Fetch the first and second cachesholder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); . }if (holder == null) {
                if (mAdapter.hasStableIds()) {
                    //hasStableIds is true to extract the primary and secondary cachesholder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); . }if (holder == null&& mViewCacheExtension ! =null) 
                    // Extract level 3 cache
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type); . }if (holder == null) { 
                    // Extract level 4 cacheholder = getRecycledViewPool().getRecycledView(type); . }if (holder == null) {... holder = mAdapter.createViewHolder(RecyclerView.this, type); . }}...if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {/ / bindViewbound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); }...return holder;
        }
Copy the code

The above code omits a lot of logic, but retains the general framework for cache extraction. You can clearly see the cache extraction process

  1. Through getScrapOrHiddenOrCachedHolderForPosition mAttachedScrap and mCachedViews cache,
  2. MViewCacheExtension. GetViewForPositionAndType CacheExtension for caching
  3. GetRecycledViewPool ().getRecycledView to retrieve the cache from item type
  4. If none of the above steps is available, create one using our createViewHolder method.

After judgment if need data binding, it calls for bindView tryBindViewHolderByDeadline. 5. After passing the above steps and getting the holder, I will try to conduct a bindView. Under what circumstances do not need bind? The holder. IsBound () &&! Holder. NeedsUpdate () &&! Holder. IsInvalid (). This can be analyzed in conjunction with the state of the ViewHolder above. That is, it has already been bind and no update is required (notifyUpdate is called), not invalid (notifyDataSetChanged is called, etc.).

Resolved the issue of when to use the cache. Let’s take a detailed look at level 4 caches in turn, and discuss the two important aspects of population and extraction.

Level 1 cache: AttachedScrap

recycling

As you already know from the above analysis of the overall flow, the primary filling of the level 1 cache is done in scrapView().

    void scrapView(View view) {
            finalViewHolder holder = getChildViewHolderInt(view); . holder.setScrapContainer(this.false); mAttachedScrap.add(holder); . }Copy the code

Let’s look at the simple logic of filling. Fill the timing of the above said is in the implementation of LayoutManager onLayoutChildren (), will be called detachAndScrapAttachedViews call scrapOrRecycleView recycle all the View, At this point, scrapView is invoked to fill the level 1 cache. But not all views go into level 1 cache. There’s a condition to be met. Let’s look at the code for scrapOrRecycleView.

    private void scrapOrRecycleView(Recycler recycler, int index, View view) {
            finalViewHolder viewHolder = getChildViewHolderInt(view); .if(viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); }else{ detachViewAt(index); recycler.scrapView(view); }}Copy the code

If the ViewHolder is not valid and the data has not been removed and is not in hasStableIds mode, remove is called for the second and fourth levels of population. You can study the specific case if you are interested.

extract

The extraction of level 1 and level 2 caches are in one function, so the extraction logic and usage logic are also consistent for level 2 caches. Level 1 cache to extract logic mainly in getScrapOrHiddenOrCachedHolderForPosition and getScrapOrCachedViewForId. GetScrapOrHiddenOrCachedHolderForPosition is conventional extraction operation, whereas getScrapOrCachedViewForId corresponding hasStableIds to true.

    ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
            final int scrapCount = mAttachedScrap.size();
            for (int i = 0; i < scrapCount; i++) {
                final ViewHolder holder = mAttachedScrap.get(i);
                if(! holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position && ! holder.isInvalid() && (mState.mInPreLayout || ! holder.isRemoved())) { holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);returnholder; }}...// Obtain from CachedViews
     }
Copy the code

Similar getScrapOrCachedViewForId ditto below logic, the difference is extracted through getItemId and itemViewType marks.

ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
            final int count = mAttachedScrap.size();
            for (int i = count - 1; i >= 0; i--) {
                final ViewHolder holder = mAttachedScrap.get(i);
                if(holder.getItemId() == id && ! holder.wasReturnedFromScrap()) {if (type == holder.getItemViewType()) {
                        holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                        returnholder; }}}...// Obtain from CachedViews
        }
Copy the code

use

Once we have extracted the appropriate ViewHolder, we need to see how it is used.

Check the legality

Mainly through validateViewHolderForOffsetPosition legitimacy examination,

holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if(holder ! =null) {
    if(! validateViewHolderForOffsetPosition(holder)) {// Check illegal
        if(! dryRun) {// if dryRun is false, FLAG_INVALID is set and recycle is dropped
            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; }}boolean validateViewHolderForOffsetPosition(ViewHolder holder) {...if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
                        + "adapter position" + holder + exceptionLabel());
            }
            
            if(! mState.isPreLayout()) {final int type = mAdapter.getItemViewType(holder.mPosition);
                if(type ! = holder.getItemViewType()) {return false; }}if (mAdapter.hasStableIds()) {
                return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
            }
            return true;
        }
Copy the code

If the holder is illegal, then the holder is empty to indicate that the appropriate holder has not been picked up, and if dryRun is false to indicate that the scrap needs to be removed, the holder is internally dropped to the cache downstream. ValidateViewHolderForOffsetPosition check legal logic is simple.

  1. The holder’s position should not exceed the total value returned by getItemCount(). Here we see a familiar exception: Inconsistency detected. Invalid View Holder Adapter Position. If we make itemCount smaller in the population, we might get a crash here. Big is nothing. Indicates that we need to synchronize internal and external data. Inconsistency can break down.
  2. ItemViewType should agree
  3. Check hasStableIds() for itemId consistency.

Continue to find the appropriate cache from the downstream cache. If checking the holder is legal, it is back to the next formal layout.

layout

Take the holder and go back inside the LayoutManager’s fill method and call addView, which in turn calls addViewInt to fill it.

    private void addViewInt(View child, int index, boolean disappearing) {
            finalViewHolder holder = getChildViewHolderInt(child); .final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (holder.wasReturnedFromScrap() || holder.isScrap()) {
                if (holder.isScrap()) {
                    holder.unScrap();
                } else {
                    holder.clearReturnedFromScrapFlag();
                }
                mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false); }... }void unscrapView(ViewHolder holder) {
            mAttachedScrap.remove(holder);
            holder.mScrapContainer = null;
            holder.mInChangeScrap = false;
            holder.clearReturnedFromScrapFlag();
        }
Copy the code

Returnedfromscrap () will return true, isScrap() will determine if setScrapContainer is NUL, and each holder will execute setScrapContainer() when it is filled. So it will also return true. UnScrap () and attachViewToParent(). The unScrap() interior removes the ViewHolder from the level 1 cache and clears the various tag bits for the fill. AttachViewToParent () is the reverse of detach. After these two operations are completed, the View is detach to RecycleView again.

Through the above analysis, we know the filling, recycling and use of level 1 cache. The overall process is now clear and will be talked about in the same way as other levels of caching are analyzed. But we still don’t know why or what the features are. We also summarize the purpose and characteristics of the next level of caching.

Level 1 Cache features

Level 1 caches only detach views and this operation is temporary. Reclamation and extraction during sliding do not involve this level of cache. And by the matching of postion extraction. It is mainly used in the empty operation at the beginning of the layout, and then immediately removed for filling in the layout. We know that View will conduct Layout twice, RecycleView in order to optimize this process, directly use the first level cache for direct recycling. The point is to quickly reuse the list item ItemView visible on the screen, in this case without the need to recreate the View and bindView.

Level 2 cache: CachedViews

recycling

CachedViews recycling populate logic mainly in recycleViewHolderInternal ()

    DEFAULT_CACHE_SIZE = 2;int mViewCacheMax = DEFAULT_CACHE_SIZE;

    void recycleViewHolderInternal(ViewHolder holder) {...if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    int cachedViewSize = mCachedViews.size();
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }

                    int targetCacheIndex = cachedViewSize;
                    if (ALLOW_THREAD_GAP_WORK
                            && cachedViewSize > 0
                            && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                        // When adding a view, skip the most recently prefetched view
                        int cacheIndex = cachedViewSize - 1;
                        while (cacheIndex >= 0) {
                            int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                            if(! mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {break;
                            }
                            cacheIndex--;
                        }
                        targetCacheIndex = cacheIndex + 1;
                    }
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true; }...// If cached is false, RecycledViewPool is used
        }
Copy the code

MViewCacheMax is the maximum capacity of the level-2 cache, calculated using updateViewCacheSize(). The external setViewCacheSize() is called when setting the level-2 cache capacity. MViewCacheMax consists of the sum of the cache capacity mRequestedCacheMax and the prefetch capacity extraCache. MRequestedCacheMax can be set with setViewCacheSize() to indicate the maximum capacity of the cache. The extraCache is adaptive controlled by the preloading control GapWorker. So we can resize the secondary cache using setViewCacheSize().

    void updateViewCacheSize(a) {
            intextraCache = mLayout ! =null ? mLayout.mPrefetchMaxCountObserved : 0;
            mViewCacheMax = mRequestedCacheMax + extraCache;

            for (int i = mCachedViews.size() - 1;
                    i >= 0&& mCachedViews.size() > mViewCacheMax; i--) { recycleCachedViewAt(i); }}Copy the code

We see that we can only add to the secondary cache if the maximum capacity is greater than zero and the ViewHolder to be added must not have one of the four states in the code. Fill it when the conditions are met. If the capacity is full, you need to recycle the first cache, which is dropped to the next three or four caches. The default add location is the last one in CachedViews, but prefetch postion is skipped. And we’ll talk about this in more detail when we talk about prefetching. Finally, the cache is populated in mCachedViews.

extract

The extraction logic is attached to the AttachedScrap extraction of the level-1 cache. The logic is similar. GetScrapOrHiddenOrCachedHolderForPosition and getScrapOrCachedViewForId have similar logic. I won’t repeat it here.

        ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {...// Fetch level 1 cache, no further fetch level 2 cache
            final int cacheSize = mCachedViews.size();
            for (int i = 0; i < cacheSize; i++) {
                final ViewHolder holder = mCachedViews.get(i);
                if(! holder.isInvalid() && holder.getLayoutPosition() == position && ! holder.isAttachedToTransitionOverlay()) {returnholder; }}...Copy the code

use

The logic used is similar to AttachedScrap level-1 cache. Then fill the layout. The fill logic is different. Let’s look at the fill logic separately. Again, addViewInt is called to fill in.

private void addViewInt(View child, int index, boolean disappearing){ final ViewHolder holder = getChildViewHolderInt(child); .if (holder.wasReturnedFromScrap() || holder.isScrap()) {
                // Populates the corresponding level 1 cache. mChildHelper.attachViewToParent(child, index, child.getLayoutParams(),false);
            } else if (child.getParent() == mRecyclerView) {
                // addView already has an existing View, which will be swapped
                if (currentIndex != index) {
                    mRecyclerView.mLayout.moveView(currentIndex, index);
                }
            } else {
                // Corresponding to the use of all caches except level 1 caches
                mChildHelper.addView(child, index, false);
                lp.mInsetsDirty = true;
                if(mSmoothScroller ! =null&& mSmoothScroller.isRunning()) { mSmoothScroller.onChildAttachedToWindow(child); }}... }Copy the code

For the third condition, McHildhelper.addview () is called directly. Not attachViewToParent like level 1 cache. McHildhelper.addview () directly calls the addView operation of RecycleView. The logic is simple

Level 2 Cache features

This level of cache is populated and reclaimed as the screen slides, so the idea is to cache itemViews that leave the screen for reuse by itemViews that are about to enter the screen.

Level 3 cache: ViewCacheExtension

ViewCacheExtension is an interface that has only one abstract method: extract the View

public abstract static class ViewCacheExtension {,@Nullable
        public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
                int type);
    }
Copy the code

recycling

As mentioned above, RecycleView does not populate ViewCacheExtension by default. It is the developer’s responsibility to decide whether they want to keep their view in this custom cache or let the default recycle policy handle it.

extract

Extract the logic is simple, direct call getViewForPositionAndType abstraction method, how is our own to achieve.

Level 4 cache: RecycledViewPool

First, RecycledViewPool, which mainly uses SparseArray data structure. This structure is unfamiliar and can be compared to HashMap, a data structure provided by Android that has been optimized compared to HashMap. Have interest can understand by oneself. The Key that stores SparseArray is the itemType of each ItemView, and the value is an ArrayList that holds the ViewHolder inside. The default maximum size of each value is 5. SetMaxRecycledViews (int viewType, int Max)

recycling

RecycledViewPool filled with logic and the second level cache, in recycleViewHolderInternal ().

void recycleViewHolderInternal(ViewHolder holder) {...// Populates the second-level cache. If cached is true, the cache is successfully populated
            if(! cached) { addViewHolderToRecycledViewPool(holder,true);
                    recycled = true; }}}void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) { clearNestedRecyclerViewIfNotNested(holder); View itemView = holder.itemView; .if (dispatchRecycled) {
                / / callback onViewRecycled
                dispatchViewRecycled(holder);
            }
            holder.mOwnerRecyclerView = null;
            getRecycledViewPool().putRecycledView(holder);
        }
        
        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }
Copy the code

The filling logic is relatively simple. Combined with the internal data structure of RecycledViewPool, it can be seen that the List of corresponding ItemType is taken out first and then filled. Notice here that if the corresponding List is equal to the maximum length. It won’t fill in, unlike the second level cache, CachedViews, which will empty the first one and continue to fill in. The important thing to note here is that before joining the cache, resetInternal() is called, which rewrites all the internal states. This extraction will require a new bind.

extract

The extraction of logic in tryGetViewHolderForPositionByDeadline () inside.

 ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
                // Fetch from level 1 and level 2 caches
    if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                final int type = mAdapter.getItemViewType(offsetPosition);
                    holder = getRecycledViewPool().getRecycledView(type);
                    if(holder ! =null) {
                        holder.resetInternal();
                        if(FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); }}}// Not yet, createView directly
}

public ViewHolder getRecycledView(int viewType) {
            final ScrapData scrapData = mScrap.get(viewType);
            if(scrapData ! =null && !scrapData.mScrapHeap.isEmpty()) {
                final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
                for (int i = scrapHeap.size() - 1; i >= 0; i--) {
                    if(! scrapHeap.get(i).isAttachedToTransitionOverlay()) {returnscrapHeap.remove(i); }}}return null;
        }
Copy the code

The itemViewType is retrieved internally by getItemViewType, and then the appropriate ViewHolder is matched internally. ResetInternal will be called to the initial state of the holder, so the holder must be rebind. We see that instead of using position directly, we recalculate a findPositionOffset. Use this for matching. FindPositionOffset internally uses notify to refresh the relevant data locally to correct the non-position. For example, if an element is deleted or added to a list, the correct position will be different.

Why you need to consider locally flushed data here. And you can get it from the first and second level cache and you don’t need to think about the local flush, you just use position?

Since the first and second caches are obtained directly through position, the external View corresponding to position needs to be given. Don’t worry about the latest location, the given location is definitely accurate. However, to obtain the RecycledViewPool, we use itemViewType. If the external data changes, the itemViewType we get through the initial position may be wrong, so we need to use the latest position. This logic is consistent with the difference between getLayoutPosition(), which gets the position in the layout, and getAdapterPosition(), which gets the position in the latest data.

The characteristics of

A ViewHolder taken from the level 4 cache must be rebind.

After the above four level cache, from the upstream to the downstream cache after extraction. I’ll try to do a bindView

Cache clearing, migration

There are also some cases of cache clearing and migration. Migration is a special problem, which is a kind of sinking. The upper layer deletes the cache to the lower layer to continue the cache. Occurs when an adapter is changed through setAdapter. The adapter cache is definitely invalidated, so we will delete all caches at this point. A series of operations are triggered within RecycleView to delete.

  1. Through removeAndRecycleScrapInt () and recycleAndClearCachedViews () method, the primary/secondary cache migrated to the four levels of cache.
  2. Clear level 1 / level 2 cache
  3. Clearing the level 4 cache is triggered by getRecycledViewPool().onadapterChanged ()

So that clears all the caches, if you’re interested. Sometimes we want to change the adapter, but still want to reuse the original cache, we can use the swapAdapter, which only does one or two parts. There will be no part III.

There are also operations that clear the cache

Flush level 1 cache

There are several cases where the level-1 cache is cleared by calling the Clear method of mAttachedScrap. It will be cleared at dispatchLayoutStep3. We find that the population of the level 1 cache is done in onLayoutChildren, which is dispatchLayoutStep2. At dispatchLayoutStep3 it clears. So its life cycle is between these two methods.

The next article covers the principles and code details of local flushing.