[Android Framework Series] Chapter 12 RecycleView related principles and four-level cache strategy analysis

1 Introduction to RecyclerView

RecyclerViewis a very powerful tool widgetthat can help you flexibly display list data. When I started learning RecyclerView, I found that there are many resources available for complex list interface, but very few resources for simple list display. Although RecyclerViewthe structure of the system may seem complicated at first glance, you will find that it is actually very simple and clear after you understand it deeply.
RecyclerViewGenerally used as Androida display list control, it has many excellent performances. The recycling pool strategy can load hundreds of millions of data without lagging, and the adapter mode can display any display requirements.
RecyclerViewJust like a conveyor belt, making full use of the principle of the conveyor belt, only the data that the user sees will always be loaded into the memory, and the data that cannot be seen is waiting to be loaded. The conveyor belt can continuously transport billion-level goods, RecyclerViewand can also display loading billion-level goods Item.

1.3 Core components in the RecyclerView architecture

  1. 回收池: Can recycle any Itemcontrol and return Itema control that conforms to the type;
    for example, onBinderViewHodlerthe first parameter in the method is returned from the recycling pool
  2. 适配器: Adapter interface, which often assists RecyclerViewin implementing list display; adapter mode, which separates user interface display from interaction
  3. RecyclerView: It is the interaction of touch events, mainly to realize the boundary value judgment; according to the user's touch feedback, coordinate the work between the recycling pool object and the adapter object

We study with these questions RecyclerView:

  1. Listviewand Recycerviewcache difference
  2. RecyclerViewWhere did the one that slipped out Viewgo?
  3. RecyclerViewHow to reuse the recycling poolView
  4. RecyclerViewThe four-level cache mechanism

1.4 RecyclerView sliding related

As we all know, the performance of implementing lists RecyclerViewin androidis very good, so what is the reason for the good performance? The key lies in its viewrecycling and reuse during processing. When the list is sliding, it will be itemViewrecycled and reused, so let's onTouchEventanalyze it from the sliding callback

1.4.1 Basic concepts

  1. ViewHolder: The container of View, a View corresponds to a ViewHolder
  2. Recyler: The internal class of RecyclerView, which is mainly responsible for the recycling and reuse of View
  3. LinearLayoutManager: RecyclerView's linear layout manager

1.4.2 Function call chain when sliding

insert image description here
Here we have a general understanding of the function call chain when sliding down, to help understand the ideas related to the analysis of the four-level cache later.

1.4.3 onMeasrue initialization

RecyclerViewDevelopers like to set the width and height of the layer wrap_content or match_parent. Therefore, it is necessary to determine the height of RecyclerView through the actual content
情况1: When the number of items is insufficient, for example, RecyclerView only loads 2 Items, the height measured by the total height of the child controls shall prevail:
情况2When the number of items exceeds the actual screen height, the match_parent shall prevail, that is maximum height

1.4.4 onLayout

RecyclerViewAs a container class control inherited from ViewGroup. The onLayout method must be implemented to place the sub-controls correctly. Since our handwritten RecyclerVIew is vertical, the placement is from top to bottom. At the same time, in order not to load all items into memory, accurate control is also required

1.4.5 Event interception

RecyclerViewAs a container control, it is necessary to intercept sliding events. When the user slides his finger, all sub-items will slide. Sub-items cannot receive any events during sliding. When the RecyclerVIew is still, the child Item needs to receive the click event

2 Working mechanism of RecyclerView adapter and recycling pool

Here we first understand RecyclerViewthe relevant loading logic in the form of pictures.

2.1 The first screen loading in RecyclerView

insert image description here

2.2 The second screen in RecyclerView

insert image description here

2.3 Recycling Pond Recycling Strategy

insert image description here

2.4 Recycling Pool Filling Strategy

insert image description here

2.5 Design of recovery pool

insert image description here

3 RecyclerView recycling and reuse

insert image description here

3.1 Analysis of key methods of recovery

RecyclerView.java

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)) 
    // 1) 先尝试放到cacheView中
                    int cachedViewSize = mCachedViews.size();
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
    
    
                        // 如果 mCachedViews 已经满了,把第0个位置的移除并放到 缓存池中
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }
                    if (!cached) {
    
    
                    // 2) 如果CacheView中没放进去,就放到 缓存池中
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
           ...
}

3.2 Analysis of key methods of reuse

tryGetViewHolderForPositionByDeadline
Get it from the first-level cache mChangeScrap Get it
from the second-level cache mCachedViews Get
it from the third-level cache mViewCacheExtension
Get it from the fourth-level cache cache pool
If you don’t get the value in the cache, create it directly
Unbound and outdated, bind it

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
    
    
    ViewHolder holder = null;
            // 1) 从一级缓存 changed scrap 中取
            if (mState.isPreLayout()) {
    
    
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 2)从二级缓存 cache中取
            if (holder == null) {
    
    
                final int type = mAdapter.getItemViewType(offsetPosition);
                if (mAdapter.hasStableIds()) {
    
    
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                    if (holder != null) {
    
    
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrapOrHiddenOrCache = true;
                    }
                }
                // 3. 从三级缓存 CacheExtension 中取  
                if (holder == null && mViewCacheExtension != null) {
    
    
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
    
    
                        holder = getChildViewHolder(view);
                    }
                }
                // 4) 从四级缓存 缓存池中取
                if (holder == null) {
    
     // fallback to pool
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
    
    
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
    
    
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                // 5)缓存中都没有拿到值,就直接创建
                if (holder == null) {
    
    
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    if (ALLOW_THREAD_GAP_WORK) {
    
    
                        // only bother finding nested RV if prefetching
                        RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                        if (innerView != null) {
    
    
                            holder.mNestedRecyclerView = new WeakReference<>(innerView);
                        }
                    }

                    long end = getNanoTime();
                    mRecyclerPool.factorInCreateTime(type, end - start);
                    if (DEBUG) {
    
    
                        Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
                    }
                }
            }
            // 6)已经 bind过了,不会再去绑定,未绑定过时,进行绑定
            if (mState.isPreLayout() && holder.isBound()) {
    
    
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
    
    
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                //  尝试 bindView
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }
            return holder;
}

4 Level 4 cache mechanism

4.1 Level 1 cache - cache fragmentation

ViewHolder getChangedScrapViewForPosition(int position) {
    
    
            // If pre-layout, check the changed scrap for an exact match.
            final int changedScrapSize;
            if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
    
    
                return null;
            }
            // find by position
            for (int i = 0; i < changedScrapSize; i++) {
    
    
                final ViewHolder holder = mChangedScrap.get(i);
                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
    
    
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    return holder;
                }
            }
            // find by id
            if (mAdapter.hasStableIds()) {
    
    
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
    
    
                    final long id = mAdapter.getItemId(offsetPosition);
                    for (int i = 0; i < changedScrapSize; i++) {
    
    
                        final ViewHolder holder = mChangedScrap.get(i);
                        if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
    
    
                            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                            return holder;
                        }
                    }
                }
            }
            return null;
}

4.2 Second level cache - cache list

ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
    
    
            // 1) 先从mAttachedScrap中取,取到便返回
            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);
                        if (holder.isRemoved()) {
    
    
                            if (!mState.isPreLayout()) {
    
    
                                holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
                                        | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
                            }
                        }
                        return holder;
                    } else if (!dryRun) {
    
    
                        mAttachedScrap.remove(i);
                        removeDetachedView(holder.itemView, false);
                        quickRecycleScrapView(holder.itemView);
                    }
                }
            }

            // 2)二级缓存,从mCachedViews中取
            final int cacheSize = mCachedViews.size();
            for (int i = cacheSize - 1; i >= 0; i--) {
    
    
                final ViewHolder holder = mCachedViews.get(i);
                //从mCachedViews中取到后便返回
                if (holder.getItemId() == id) {
    
    
                    if (type == holder.getItemViewType()) {
    
    
                        if (!dryRun) {
    
    
                            mCachedViews.remove(i);
                        }
                        return holder;
                    } else if (!dryRun) {
    
    
                        recycleCachedViewAt(i);
                        return null;
                    }
                }
            }
            return null;
        }

4.3 Level 3 cache - custom cache

This level of cache is user-defined and will not be explained in detail here.

4.4 Level 4 cache - cache pool

public ViewHolder getRecycledView(int viewType) {
    
    
            //从mScrap中根据view的类型来取出一个
            final ScrapData scrapData = mScrap.get(viewType);
            if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
    
    
                final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
                //从 scrapData 中拿最后一个数据,先进后出
                return scrapHeap.remove(scrapHeap.size() - 1);
            }
            return null;
}

static class ScrapData {
    
    
            //ViewHolder是作为一个List被存进来的
            ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
            // 缓存池中 list的大小是5,也就是每个类型的view缓存池中存储5个
            int mMaxScrap = 5;
            long mCreateRunningAverageNs = 0;
            long mBindRunningAverageNs = 0;
 }

Guess you like

Origin blog.csdn.net/u010687761/article/details/132525589