当前位置: 代码迷 >> 综合 >> Android--源码分析RecyclerView三级缓存
  详细解决方案

Android--源码分析RecyclerView三级缓存

热度:13   发布时间:2023-09-14 06:57:14.0
之前说到ListView的缓存机制,利用RecycleBin缓存从屏幕移除的item,又利用RecycleBin重复利用给getView方法,今天我们来分析下RecyclerView的缓存机制,我们来到RecyclerView生成View的方法
/*** Obtain a view initialized for the given position.** This method should be used by {@link LayoutManager} implementations to obtain* views to represent data from an {@link Adapter}.* <p>* The Recycler may reuse a scrap or detached view from a shared pool if one is* available for the correct view type. If the adapter has not indicated that the* data at the given position has changed, the Recycler will attempt to hand back* a scrap view that was previously initialized for that data without rebinding.** @param position Position to obtain a view for* @return A view representing the data at <code>position</code> from <code>adapter</code>*/public View getViewForPosition(int position) {return getViewForPosition(position, false);}
先来看部分代码,RecyclerView使用的ViewHolder存储itemView,这边返回值就是ViewHolder的itemView
View getViewForPosition(int position, boolean dryRun) {
...// 0) If there is a changed scrap, try to find from thereif (mState.isPreLayout()) {//首先调用getChangedScrapViewForPosition方法holder = getChangedScrapViewForPosition(position);fromScrap = holder != null;}
...}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 positionfor (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 idif (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;}
先调用getChangedScrapViewForPosition方法从mChangedScrap中获取缓存的ViewHolder,可能为null
View getViewForPosition(int position, boolean dryRun) {
...// 1) Find from scrap by positionif (holder == null) {//如果上面没有获得,则调用getScrapViewForPosition获取holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);if (holder != null) {if (!validateViewHolderForOffsetPosition(holder)) {// recycle this scrapif (!dryRun) {// we would like to recycle this but need to make sure it is not used by// animation logic etc.holder.addFlags(ViewHolder.FLAG_INVALID);if (holder.isScrap()) {removeDetachedView(holder.itemView, false);holder.unScrap();} else if (holder.wasReturnedFromScrap()) {holder.clearReturnedFromScrapFlag();}recycleViewHolderInternal(holder);}holder = null;} else {fromScrap = true;}}}
...}/*** Returns a scrap view for the position. If type is not INVALID_TYPE, it also checks if* ViewHolder's type matches the provided type.** @param position Item position* @param type View type* @param dryRun  Does a dry run, finds the ViewHolder but does not remove* @return a ViewHolder that can be re-used for this position.*/ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) {final int scrapCount = mAttachedScrap.size();// Try first for an exact, non-invalid match from scrap.for (int i = 0; i < scrapCount; i++) {final ViewHolder holder = mAttachedScrap.get(i);if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {if (type != INVALID_TYPE && holder.getItemViewType() != type) {Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" +" wrong view type! (found " + holder.getItemViewType() +" but expected " + type + ")");break;}holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);return holder;}}if (!dryRun) {View view = mChildHelper.findHiddenNonRemovedView(position, type);if (view != null) {// This View is good to be used. We just need to unhide, detach and move to the// scrap list.final ViewHolder vh = getChildViewHolderInt(view);mChildHelper.unhide(view);int layoutIndex = mChildHelper.indexOfChild(view);if (layoutIndex == RecyclerView.NO_POSITION) {throw new IllegalStateException("layout index should not be -1 after "+ "unhiding a view:" + vh);}mChildHelper.detachViewFromParent(layoutIndex);scrapView(view);vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);return vh;}}// Search in our first-level recycled view cache.final int cacheSize = mCachedViews.size();for (int i = 0; i < cacheSize; i++) {final ViewHolder holder = mCachedViews.get(i);// invalid view holders may be in cache if adapter has stable ids as they can be// retrieved via getScrapViewForIdif (!holder.isInvalid() && holder.getLayoutPosition() == position) {if (!dryRun) {mCachedViews.remove(i);}if (DEBUG) {Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +") found match in cache: " + holder);}return holder;}}return null;}
如果为null,则调用getScrapViewForPosition方法,从mAttachedScrap和mCachedViews中获取
View getViewForPosition(int position, boolean dryRun) {
...// 2) Find from scrap via stable ids, if existsif (mAdapter.hasStableIds()) {//在用id查找下holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);if (holder != null) {// update positionholder.mPosition = offsetPosition;fromScrap = true;}}
...}ViewHolder getScrapViewForId(long id, int type, boolean dryRun) {// Look in our attached views firstfinal 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()) {// this might be valid in two cases:// > item is removed but we are in pre-layout pass// >> do nothing. return as is. make sure we don't rebind// > item is removed then added to another position and we are in// post layout.// >> remove removed and invalid flags, add update flag to rebind// because item was invisible to us and we don't know what happened in// between.if (!mState.isPreLayout()) {holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE |ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);}}return holder;} else if (!dryRun) {// if we are running animations, it is actually better to keep it in scrap// but this would force layout manager to lay it out which would be bad.// Recycle this scrap. Type mismatch.mAttachedScrap.remove(i);removeDetachedView(holder.itemView, false);quickRecycleScrapView(holder.itemView);}}}// Search the first-level cachefinal int cacheSize = mCachedViews.size();for (int i = cacheSize - 1; i >= 0; i--) {final ViewHolder holder = mCachedViews.get(i);if (holder.getItemId() == id) {if (type == holder.getItemViewType()) {if (!dryRun) {mCachedViews.remove(i);}return holder;} else if (!dryRun) {recycleCachedViewAt(i);}}}return null;}
如果还是为null,再用mAdapter.getItemId(offsetPosition)获取id,通过id从缓存中查找,继续往下
View getViewForPosition(int position, boolean dryRun) {
...if (holder == null && mViewCacheExtension != null) {// We are NOT sending the offsetPosition because LayoutManager does not// know it.final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);if (view != null) {holder = getChildViewHolder(view);if (holder == null) {throw new IllegalArgumentException("getViewForPositionAndType returned"+ " a view which does not have a ViewHolder");} else if (holder.shouldIgnore()) {throw new IllegalArgumentException("getViewForPositionAndType returned"+ " a view that is ignored. You must call stopIgnoring before"+ " returning this view.");}}}
...}void setViewCacheExtension(ViewCacheExtension extension) {mViewCacheExtension = extension;}
还没有找到的话,再从mViewCacheExtension中获取,mViewCacheExtension是提供给用户使用的,可以使用setViewCacheExtension方法设置,继续往下
View getViewForPosition(int position, boolean dryRun) {
...if (holder == null) { // fallback to recycler// try recycler.// Head to the shared pool.if (DEBUG) {Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "+ "pool");}holder = getRecycledViewPool().getRecycledView(type);if (holder != null) {holder.resetInternal();if (FORCE_INVALIDATE_DISPLAY_LIST) {invalidateDisplayListInt(holder);}}}
...}RecycledViewPool getRecycledViewPool() {if (mRecyclerPool == null) {mRecyclerPool = new RecycledViewPool();}return mRecyclerPool;}
如果还是为null,则从mRecyclerPool 中获取,继续往下
View getViewForPosition(int position, boolean dryRun) {
...if (holder == null) {holder = mAdapter.createViewHolder(RecyclerView.this, type);if (DEBUG) {Log.d(TAG, "getViewForPosition created new ViewHolder");}}
...}
如果实在没有找到缓存,则最终调用mAdapter.createViewHolder(RecyclerView.this, type)方法
总结一下RecyclerView 的三级缓存
  • 一级缓存:
    mChangedScrap RecyclerView中需要改变的Viewholder
    mAttachedScrap 还没有和RecyclerView分离的ViewHolder
    mCachedViews RecyclerView的ViewHolder的缓存
  • 二级缓存
    mViewCacheExtension 提供给开发者自己创建的缓存
  • 三级缓存
    mRecyclerPool 缓存池----一种用于多个RecyclerView之间共享View 的缓存
  相关解决方案