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

Why iOS consumes more power when background apps are turned off manually?

Android phone users should have a habit: when the...

China Automobile Dealers Association: US auto market bounced in May 2020

“America’s auto dealers are getting people back t...

Look! There’s an “ice cream” on the tree!

Review expert: Ran Hao, a well-known popular scie...

iOS 18 is about to be released, get to know the new features first

The May Day holiday is approaching, which means i...

Low-cost promotion optimization techniques for medical promotion accounts!

This article will share with you the promotion ex...

Daily Fresh’s private domain growth strategy

Recently, fresh food e-commerce has ushered in a ...

How can financial loans acquire customers through Tencent social advertising?

Under the influence of new consumption concepts, ...

Big iPhone screen: Accelerating the demise of one-handed gaming

The new generation of iPhone will have two screen...

"The most mysterious bird in the world" actually looks like this!

Your browser does not support the video tag Septe...

How do you write copy that will inspire users to place orders after reading it?

Copywriting with sufficient promotional power wil...