The environment

Android SDK version: 30

Rely on:

Implementation 'androidx. Core: the core - KTX: 1.3.2' implementation 'androidx. Appcompat: appcompat: 1.2.0' implementation "Androidx. Recyclerview: recyclerview: 1.2.1."Copy the code

Case Study:

RecyclerView width and height fixed; LayoutManager is LienarLayoutManager, vertical; Twenty pieces of data, enough to cover the entire screen.

Phenomenon:

① Create an Adapter and set 20 pieces of data.

RecyclerView#Adapter#notifyDataSetChanged The rest of the ViewHolder will create a new ViewHolder using the Adapter#createViewHolder method.

Principle:

To see how this works, take a look at the call stack of RecyclerView#Adapter#onCreateViewHolder when entering the page.

RecyclerView#Adapter#onCreateViewHolder call stack

RecyclerView# onLayout () : line 4578 RecyclerView# dispatchLayout () : 4012 row RecyclerView#dispatchLayoutStep2():4309 row LinearLayoutManager#onLayoutChildren(): 668 row LinearLayoutManager#fill(): 1591 row LinearLayoutManager#layoutChunk(): 1631 row LinearLayoutManager#LayoutState#next(): 2330 lines RecyclerView# Recycler# getViewForPosition (int position) : RecyclerView#Recycler#getViewForPosition(position, Boolean dryRun): 6300 lines RecyclerView# Recycler# tryGetViewHolderForPositionByDeadline (int position, Boolean dryRun, long deadlineNs) : RecyclerView#Adapter#createViewHolder(@nonnull ViewGroup parent, int viewType) 7295行. RecyclerView#Adapter#onCreateViewHolder(@nonnull ViewGroup parent, int viewType)Copy the code

Its core is RecyclerView# Recycler# tryGetViewHolderForPositionByDeadline method, which have two main functions, one is to obtain ViewHolder; The other binds data to the ViewHolder.

To obtain a ViewHolder, try to obtain the Recycler scrap, cache, and RecycledViewPool in order. If neither of these can be obtained, create a ViewHolder directly.

RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline

Attempts to get the ViewHolder for the given position, either from the Recycler scrap, cache, the RecycledViewPool, or creating it directly. Gets the ViewHolder for the given position. Recycle scrap, Cache, and RecycledViewPool. If neither can be recycled, create a ViewHolder core. Bind data to viewHolder.Copy the code
@Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { ... ViewHolder holder = null; If there is a changed scrap, try to find from there // If there is a changed scrap, try to get from mChangedScrap. if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); . } // 1) Find by position from scrap/hidden list/cache // Getviewholder if (holder == null) {holder = getViewholder if (holder = null) {holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); Vh if (holder! = null) { if (! validateViewHolderForOffsetPosition(holder)) { ... holder = null; } else { fromScrapOrHiddenOrCache = true; } } } if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); . // get the data type corresponding to this location by overriding the Adapter#getItemViewType method. final int type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap/cache via stable ids, if exists // If (madapt.hasstableids ()) {holder = madapt.hasstableids () getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); . } // try to fetch VH from mViewCacheExtension if (holder == null && mViewCacheExtension! = null) { final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); if (view ! = null) { holder = getChildViewHolder(view); . If (holder == null) {// Fallback to pool... holder = getRecycledViewPool().getRecycledView(type); . } // call RecyclerView#Adapter#onCreateViewHolder to generate ViewHolder if (holder == null) {... holder = mAdapter.createViewHolder(RecyclerView.this, type); . }}... // Bind data to viewholder Boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { ... } else if (! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { ... / / to the viewholder binding data bound = tryBindViewHolderByDeadline (holder, offsetPosition, position, deadlineNs); } // create RecyclerView#LayoutParams for holder.itemview. And bind them to each other. . return holder; }Copy the code

Case study.

Here, after calling RecyclerView#Adapter#notifyDataSetChanged, there are both reusable ViewHolder and new ViewHolder. Where does the reusable ViewHolder come from? Why five? Why create a new ViewHolder?

With these problems in mind, let’s debug our scenario and look at the source of the ViewHolder.

Core is called RecyclerView# Recycler# tryGetViewHolderForPositionByDeadline method, the key lies in the following code:


holder = getRecycledViewPool().getRecycledView(type);

Copy the code

As we know, the RecycledViewPool is used to store different ViewHolder by viewType, and each type can store up to five.

So we’re RecyclerView# Recycler# tryGetViewHolderForPositionByDeadline, most from RecycledViewPool five reusable ViewHolder can be found, The rest must go through the New ViewHolder process.

RecycledViewPool

RecycledViewPool RecycledViewPool

RecycledViewPool lets you share Views between multiple RecyclerViews. If you want to recycle views across RecyclerViews,  create an instance of RecycledViewPool and use setRecycledViewPool(RecyclerView.RecycledViewPool). RecyclerView automatically creates a pool for itself if you don't provide one.Copy the code

RecycledViewPool RecycledViewPool allows you to share views among multiple RecyclerViews.

If you want to recycle in RecyclerViews view, you can create a RecycledViewPool instance and use setRecycledViewPool (RecyclerView. RecycledViewPool).

If you don’t provide a RecycledViewPool instance, RecyclerView will automatically create one for itself.

RecyclerView#Recycler#getRecycledViewPool: it does create one automatically.


RecycledViewPool getRecycledViewPool() {

if (mRecyclerPool == null) {

mRecyclerPool = new RecycledViewPool();

}

return mRecyclerPool;

}

Copy the code

RecycledViewPool RecycledViewPool

public static class RecycledViewPool { private static final int DEFAULT_MAX_SCRAP = 5; static class ScrapData { final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0; long mBindRunningAverageNs = 0; } SparseArray<ScrapData> mScrap = new SparseArray<>(); . }Copy the code

As shown above, there is a variable mScrap of type SparseArray

used to store ViewHolder of different types. The ArrayList

variable mScrapHeap is used to store a specific ViewHolder. It has a maximum capacity of 5(DEFAULT_MAX_SCRAP).

RecycledViewPool when data was added

Where did the RecycledViewPool data come from in this example?

In this example, the call chain to add ViewHolder data to ScrapData#mScrapHeap is as follows:

RecyclerView# onLayout () : line 4578 RecyclerView# dispatchLayout () : 4012 row RecyclerView#dispatchLayoutStep2():4309 row LinearLayoutManager#onLayoutChildren(): 633 lines RecyclerView# LayoutManager# detachAndScrapAttachedViews () : line 9493 RecyclerView# LayoutManager# scrapOrRecycleView () : 9508 lines RecyclerView# Recycler# recycleViewHolderInternal () : line 6671 RecyclerView# Recycler# addViewHolderToRecycledViewPool () : Add (scrap) scrapHeap. Add (scrap);Copy the code

The core is the LinearLayoutManager#onLayoutChildren() method, which looks like this:


detachAndScrapAttachedViews(recycler);

Copy the code

That is, the draw process is triggered after the RecyclerView#Adapter#notifyDataSetChanged method is called. In the Linearlayout#layoutChildren method, the ViewHolder is cached and then reused.

The relevant data

demo-AdapterOnCreateViewHolderTestActivity