RecyclerView is a powerful and flexible View, can use limited View to show a large number of data. Today we look at the RecyclerView is through what cache reuse mechanism to achieve this function.

Recycler

Recycler is the inner class of RecyclerView, and it’s the core of this mechanism. Obviously, the main variables of Recycler are also used to cache and reuse ViewHolder:

public final class Recycler {
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;

    final ArrayList<ViewHolder> mCachedViews = new ArrayList<>();
    
    RecycledViewPool mRecyclerPool;
    
    private ViewCacheExtension mViewCacheExtension;
}
Copy the code

These cache sets can be divided into four levels, in order of priority:

  • Level 1 cache: MathedScrap and mChangedScrap to cache ViewHolder that is still in the screen

    • MAttachedScrap stores the ViewHolder currently on the screen; Find the ViewHolder by ID and position
    • MChangedScrap represents a list of ViewHolder that the data has changed, ViewHolder that need to be changed when storing notifyXXX
  • Level 2 cache: mCachedViews, used to cache ViewHolder that is off screen. The default cache size is 2. You can change the size of the cache by using the setViewCacheSize method. If the Capacity of mCachedViews is full, the old ViewHolder is removed according to the RULES of the FIFO

  • Level 3 cache: ViewCacheExtension, a custom extension cache developed for users, requires users to manage the creation and caching of views themselves. My personal feeling is that this extension is used outside of Adapter. CreateViewHolder and will cause View creation and data binding and other code to be too scattered to be maintained

    /* * Note that, Recycler never sends Views to this method to be cached. It is developers * responsibility to decide whether they want to  keep their Views in this custom cache or let * the default recycling policy handle it. */
    public abstract static class ViewCacheExtension {
    	public abstract View getViewForPositionAndType(...).;
    }
    Copy the code
  • 4 cache: RecycledViewPool, ViewHolder cache pool, in limited mCachedViews if there is no new ViewHolder, the ViewHolder will be stored in the RecyclerViewPool.

    • Look for a ViewHolder by Type
    • A maximum of 5 types are cached by default
    • You can share a RecyclerView in a RecycledViewPool

Now let’s look at how this four level cache works

reuse

RecyclerView: RecyclerView: RecyclerView: RecyclerView: RecyclerView: RecyclerView: RecyclerView: RecyclerView

   RecyclerView.onLayout(...)
-> RecyclerView.dispatchLayout()    
-> RecyclerView.dispatchLayoutStep2() // do the actual layout of the views for the final state.
-> mLayout.onLayoutChildren(mRecycler, mState) // The mLayout type is LayoutManager
-> LinearLayoutManager.onLayoutChildren(...) // Take the LinearLayoutManager as an example
-> LinearLayoutManager.fill(...) // The magic functions :) fill in The given layout. The annotation confidently states that The method is independent and can be used as a public method of The helper class with minor changes.
-> LinearLayoutManager.layoutChunk(recycler, layoutState) // Loop calls, each call populates one ItemView to the RV
-> LinearLayoutManager.LayoutState.next(recycler) 
-> RecyclerView.Recycler.getViewForPosition(int) // Return to the main role, use the Recycler to get the specified ItemView
-> Recycler.getViewForPosition(int.boolean) // Call the following method to get a ViewHolder and return the viewholder.ItemView required above
-> Recycler.tryGetViewHolderForPositionByDeadline(...) // Finally found you, fortunately did not give up ~
Copy the code

It can be seen that the final call tryGetViewHolderForPositionByDeadline, this method will show you how to get the corresponding location on the ViewHolder:

ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
    if (mState.isPreLayout()) {
        // 0) Get ViewHolder from mChangedScrap, animation related
        holder = getChangedScrapViewForPosition(position);
    }
    
    if (holder == null) {
        // 1) Obtain ViewHolder from mAttachedScrap, mHiddenViews and mCachedViews respectively
        // This mHiddenViews is used for reuse during animation
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    }
    
    if (holder == null) {
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) If the Adapter hasStableIds method returns true
        // Select mattachedViews and mCachedViews from ViewType and ItemId
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
        }
      
        if (holder == null&& mViewCacheExtension ! =null) {
            // We are NOT sending the offsetPosition because LayoutManager does not know it.
            // 3) Get it from the custom cache, don't ask, don't use it
            View view = mViewCacheExtension
                getViewForPositionAndType(this, position, type); holder = getChildViewHolder(view); }}if (holder == null) {
        // 4) Get a ViewHolder from RecycledViewPool
        holder = getRecycledViewPool().getRecycledView(type);
    }
  
    if (holder == null) {
        // Create one; // Create one
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
    }
    
    // This is very ugly but the only place we can grab this information
    // I laughed when I saw this remark in the middle of the night! It's like when I write code that makes a fool of myself.
    
    If (holder == null) if (holder == null) if (holder == null)
}
Copy the code

After analyzing the reuse part, let’s take a look at the cached part of the ViewHolder

The cache

The so-called cache is to see how to add data to the previously mentioned four level cache

  • MAttachedScrap and mChangedScrap
  • mCachedViews
  • ViewCacheExtensionAs mentioned earlier, the creation and caching of this is entirely under the developer’s control, and the system does not add data to it
  • RecycledViewPool

MAttachedScrap and mChangedScrap

If you call the Adapter notifyXXX, it calls back to the onLayoutChildren method of the LayoutManager, and in the onLayoutChildren method, All ViewHolder on the screen will be recycled to mathedScrap and mChangedScrap.

/ / call chainLinearLayoutManager.onLayoutChildren(...) -> LayoutManager.detachAndScrapAttachedViews(recycler) -> LayoutManager.scrapOrRecycleView(... , view) -> Recycler.scrapView(view);private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if(viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) {// Cache to mCacheViews and RecyclerViewPool
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        // Cache to Scraprecycler.scrapView(view); }}void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if(holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || ! holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {/ / marked as removed or failure of the | | no change | | item no animation or animation not reuse
        mAttachedScrap.add(holder);
    } else{ mChangedScrap.add(holder); }}Copy the code

If you want to get a ViewHolder from mHiddenViews, remove the ViewHolder from the mHiddenViews array, and then call:

   Recycler.tryGetViewHolderForPositionByDeadline(...)
-> Recycler.getScrapOrHiddenOrCachedHolderForPosition(...)
-> Recycler.scrapView(view)
Copy the code

MCacheViews and RecyclerViewPool

The code for both levels of cache is in this method called Recycler:

void recycleViewHolderInternal(ViewHolder holder) {
    if (forceRecycle || holder.isRecyclable()) {
        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) {
              // 1. When mCacheViews are full, the earliest users should not use RecyclerViewPool
              recycleCachedViewAt(0); 
          }
           mCachedViews.add(targetCacheIndex, holder);
           cached = true;
        }
        
        if(! cached) {// 2. Cannot be put into the RecyclerViewPool of mCacheViews
            addViewHolderToRecycledViewPool(holder, true); }}}// Recycles a cached view and removes the view from the list
void recycleCachedViewAt(int cachedViewIndex) {
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}
Copy the code

In this we know recycleViewHolderInternal will put the ViewHolder cache to mCacheViews, and are not content to mCacheViews caches to RecycledViewPool. What is that called when recycleViewHolderInternal? There are three cases:

  1. To layout, main is calling Adapter notifyDataSetChange and Adapter hasStableIds call method returns false. From here you can also see why notifyDataSetChange is generally less efficient than other notifyXXX methods (using a second-level cache and a lower priority cache), and also know that If we set adapter.sethasstableids (true) and other implementations related to this need, we can improve efficiency (using level 1 caching)
  2. In reuse, a ViewHolder is retrieved from the level-1 cache if the ViewHolder is no longer compatible with the level-1 cache (e.g., Position is invalid, ViewType is not aligned) and is removed from the level-1 cache. Add to both levels of cache
  3. When call removeAnimatingView method, if the current ViewHolder is marked as remove, corresponding ViewHolder will call recycleViewHolderInternal method to recycle. The time to call the removeAnimatingView method indicates that the current ItemAnimator has finished

conclusion

Here, RecyclerView cache reuse mechanism on the analysis finished, summarized:

  • RecyclerView RecyclerView can be used internally to recycle classes
  • There are four levels of caches, each of which has its own purpose and is used according to priority.
  • The ViewHolder moves from one cache level to another
  • MHideenViews exists to solve the problem of reuse during animation.
  • When the cache reuses a ViewHolder, different internal states (mFlags) are processed accordingly.

This article source code based on: Recyclerview :1.2.0-alpha03