Android architect's in-depth understanding of RecyclerView reuse and caching mechanism

Android architect's in-depth understanding of RecyclerView reuse and caching mechanism

This article is reprinted from the WeChat public account "Android Development Programming", the author is Android Development Programming. Please contact the Android Development Programming public account for reprinting this article.

Preface

Learning source code and studying source code programming ideas are the only way for programmers to advance

Everyone knows that RecyclerView has a recycling and reuse mechanism, so how does the recycling and reuse mechanism work?

Today we will use source code to explain and learn together

1. Recycler Introduction

RecyclerView is a cache managed by the internal class Recycler, so what is cached in Recycler? We know that RecyclerView can still slide smoothly when there is a lot of data, and RecyclerView itself is a ViewGroup, so it is inevitable to add or remove sub-Views when sliding (sub-Views are created through onCreateViewHolder in RecyclerView#Adapter). If the sub-Views have to be recreated every time they are used, it will definitely affect the smoothness of sliding. Therefore, RecyclerView caches ViewHolder (which contains sub-Views) through Recycler, so that sub-Views can be reused when sliding, and under certain conditions, the data bound to sub-Views can also be reused. So in essence, caching is to reduce the time of repeatedly drawing Views and binding data, thereby improving the performance of sliding.

  1. public final class Recycler {
  2. final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
  3. ArrayList<ViewHolder> mChangedScrap = null ;
  4. final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
  5. private final List<ViewHolder>
  6. mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
  7. private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
  8. int mViewCacheMax = DEFAULT_CACHE_SIZE;
  9. RecycledViewPool mRecyclerPool;
  10. private ViewCacheExtension mViewCacheExtension;
  11. static final int DEFAULT_CACHE_SIZE = 2;

Recycler caches ViewHolder objects at four levels, with the priorities from high to low being:

1. ArrayList mAttachedScrap --- Cache the ViewHolder of the visible range on the screen

2. ArrayList mCachedViews ---- caches the ViewHolder that will be separated from the RecyclerView when sliding, cached by the position or id of the child View, and stores up to 2 by default

3. ViewCacheExtension mViewCacheExtension --- Cache implemented by the developer

4. RecycledViewPool mRecyclerPool --- ViewHolder cache pool, which is essentially a SparseArray, where the key is ViewType (int type) and the value is ArrayList< ViewHolder>. By default, each ArrayList can store up to 5 ViewHolders.

2. Detailed Analysis of Cache Mechanism

When RecyclerView slides, onTouchEvent#onMove is triggered, and the recycling and reuse of ViewHolder will begin here. We know that when setting RecyclerView, we need to set LayoutManager. LayoutManager is responsible for the layout of RecyclerView, including the acquisition and reuse of ItemView. Taking LinearLayoutManager as an example, when RecyclerView is rearranged, the following methods will be executed in sequence:

onLayoutChildren(): Entry method for layout of RecyclerView

fill(): Responsible for continuously filling the remaining space, the calling method is layoutChunk()

layoutChunk(): Responsible for filling the View, which is ultimately found by finding a suitable View in the cache class Recycler

The entire call chain above: onLayoutChildren()->fill()->layoutChunk()->next()->getViewForPosition(), getViewForPosition() is to get the appropriate View from the RecyclerView recycling mechanism implementation class Recycler,

The following mainly looks at the implementation of Recycler#getViewForPosition().

  1. @NonNull
  2. public   View getViewForPosition( int position) {
  3. return getViewForPosition(position, false );
  4. }
  5. View getViewForPosition( int position, boolean dryRun) {
  6. return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
  7. }

They will all execute the tryGetViewHolderForPositionByDeadline function and continue to follow it:

//Get ViewHolder according to the passed position

  1. ViewHolder tryGetViewHolderForPositionByDeadline( int position,
  2. boolean dryRun, long deadlineNs) {
  3. ...omitted
  4. boolean fromScrapOrHiddenOrCache = false ;
  5. ViewHolder holder = null ;
  6. //Pre-layout is a special case. Get ViewHolder from mChangedScrap
  7. if (mState.isPreLayout()) {
  8. holder = getChangedScrapViewForPosition(position);
  9. fromScrapOrHiddenOrCache = holder != null ;
  10. }
  11. if (holder == null ) {
  12. //1. Try to get the ViewHolder from mAttachedScrap. At this time, the ViewHolder in the visible range of the screen is obtained.
  13. //2. If there is no ViewHolder in the mAttachedScrap cache, continue to try to get the ViewHolder from mCachedViews
  14. holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
  15. ...omitted
  16. }
  17. if (holder == null ) {
  18. final int offsetPosition = mAdapterHelper.findPositionOffset(position);
  19. ...omitted
  20. final int type = mAdapter.getItemViewType(offsetPosition);
  21. //If the Adapter declares an ID, try to get it from the ID, which is not cached here
  22. if (mAdapter.hasStableIds()) {
  23. holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
  24. type, dryRun);
  25. }
  26. if (holder == null && mViewCacheExtension != null ) {
  27. 3. Try to get ViewHolder from the custom cache mViewCacheExtension, which needs to be implemented by the developer
  28. final View   view = mViewCacheExtension
  29. .getViewForPositionAndType(this, position, type);
  30. if ( view != null ) {
  31. holder = getChildViewHolder( view );
  32. }
  33. }
  34. if (holder == null ) { // fallback to pool
  35. //4. Try to get ViewHolder from the cache pool mRecyclerPool
  36. holder = getRecycledViewPool().getRecycledView(type);
  37. if (holder != null ) {
  38. //If the acquisition is successful, the ViewHolder state will be reset, so you need to re-execute Adapter#onBindViewHolder to bind the data
  39. holder.resetInternal();
  40. if (FORCE_INVALIDATE_DISPLAY_LIST) {
  41. invalidateDisplayListInt(holder);
  42. }
  43. }
  44. }
  45. if (holder == null ) {
  46. ...omitted
  47. //5. If no corresponding ViewHolder is found in the above cache, onCreateViewHolder in Adapter will be called to create one
  48. holder = mAdapter.createViewHolder(RecyclerView.this, type);
  49. }
  50. }
  51. boolean bound = false ;
  52. if (mState.isPreLayout() && holder.isBound()) {
  53. holder.mPreLayoutPosition = position;
  54. } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
  55. final int offsetPosition = mAdapterHelper.findPositionOffset(position);
  56. //6. If data needs to be bound, Adapter#onBindViewHolder will be called to bind the data
  57. bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
  58. }
  59. ...omitted
  60. return holder;
  61. }

The ViewHolder obtained through mAttachedScrap, mCachedViews and mViewCacheExtension does not need to recreate the layout and bind data; the ViewHolder obtained through the cache pool mRecyclerPool does not need to recreate the layout, but needs to rebind the data; if the target ViewHolder is not obtained in the above cache, Adapter#onCreateViewHolder will be called back to create the layout, and Adapter#onBindViewHolder will be called back to bind the data

Summarize

There are two structures involved in the recycling and reuse of RecyclerView in the sliding scenario:

  • mCachedViews and RecyclerViewPool
  • mCachedViews has a higher priority than RecyclerViewPool. When recycling, the latest ViewHolder is placed in mCachedViews. If it is full, one is removed and thrown into ViewPool to make room to cache the latest ViewHolder.
  • When reusing, the ViewHolder is also first found in mCachedViews, but various matching conditions are required. In summary, only the card position in the original position can reuse the ViewHolder in mCachedViews. If there is no ViewHolder in mCachedViews, then it will be found in ViewPool.
  • The ViewHolders in the ViewPool are the same as the new ViewHolders. As long as the type is the same and it is found, it can be reused and the data can be re-bound.

<<:  Google's Android beta device search feature exposed: supports network and car search platforms

>>:  After upgrading to Apple iOS 14.7.1, some iPhone 11/8/7/6s users complained about the signal "No Service" error

Recommend

Advanced checklist for private domain operators

What is a private domain operator ? As the name s...

Why developers need free B2D services

Recently, the mobile game industry event GMGDC wa...

Does it really not matter whether Google Glass wins or loses?

It is undeniable that there have been many debate...

Global tablet shipments expected to fall 31% in first quarter of this year

Global tablet shipments are expected to decline i...

How much does it cost for Dehong to join a lottery app?

What is the price for joining the Dehong Lottery ...

Alibaba's 2016 foreign trade strategy in one chart

[[162446]] CEO Zhang Yong said at the Global B2B ...

Why are mobile phones getting heavier nowadays?

Nowadays, we use mobile phones more and more freq...

Which floor is the best for the Feng Shui ornament Wenchang Tower?

Wenchang Tower, also known as Wenfeng Tower, Wenf...

iOS: How to draw a 1 pixel line correctly

[[140291]] 1. Point Vs Pixel In iOS, when we use ...

[Smart Farmers] New technology helps microorganisms settle in water

China is the world's largest aquaculture coun...

Community property WeChat applet development solution

When the development of WeChat mini-programs firs...