RecyclerView回收机制分析--“取”
来源:互联网 发布:提醒喝水的软件 编辑:程序博客网 时间:2024/06/08 16:55
RecyclerView的回收被封装在内部类Recycler中 从这个类的成员变量就能略窥一斑
个人总结:
mChangedScrap: RecyclerView中需要改变的vh
mAttachedScrap: RecyclerView还没有分离的vh
mCacheViews: RecyclerView中vh缓存
RecycledViewPool: 可共享回收池
/** * A Recycler is responsible for managing scrapped or detached item views for reuse. * * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but * that has been marked for removal or reuse.</p> * * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for * an adapter's data set representing the data at a given position or item ID. * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. * If not, the view can be quickly reused by the LayoutManager with no further work. * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} * may be repositioned by a LayoutManager without remeasurement.</p> */ public final class Recycler { //如果仍依赖于 RecyclerView (比如已经滑动出可视范围,但还没有被移除掉),但已经被标记移除的 ItemView 集合会被添加到 mAttachedScrap 中。 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();//mChangedScrap存储 notifXXX 方法时需要改变的 ViewHolder 。ArrayList<ViewHolder> mChangedScrap = null;//如果 mAttachedScrap 中不再依赖时会被加入到 mCachedViews 中。 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); private final List<ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; int mViewCacheMax = DEFAULT_CACHE_SIZE;//被remove掉的ViewHolder会按照ViewType分组被存放在RecyclerViewPool里,默认最大缓存每组(ViewType)5个。 private RecycledViewPool mRecyclerPool; private ViewCacheExtension mViewCacheExtension; static final int DEFAULT_CACHE_SIZE = 2;这个类的最重要的入口即getViewForPosition方法,
可见的外部调用如下:
GridLayoutManager.getViewForPosition in GridLayoutManager.java (android\support\v17\leanback\widget) :
return mRecycler.getViewForPosition(position);
GridLayoutManager.measureScrapChild in GridLayoutManager.java (android\support\v17\leanback\widget) :
GridLayoutManager.measureScrapChild in GridLayoutManager.java (android\support\v17\leanback\widget) :
View view = mRecycler.getViewForPosition(position);
LayoutState.next in LayoutState.java (android\support\v7\widget) :
LayoutState.next in LayoutState.java (android\support\v7\widget) :
final View view = recycler.getViewForPosition(mCurrentPosition);
LinearLayoutManager.LayoutState.next in LinearLayoutManager.java (android\support\v7\widget) :
LinearLayoutManager.LayoutState.next in LinearLayoutManager.java (android\support\v7\widget) :
final View view = recycler.getViewForPosition(mCurrentPosition);
RecyclerView.Recycler.prefetch in RecyclerView.java (android\support\v7\widget) :
RecyclerView.Recycler.prefetch in RecyclerView.java (android\support\v7\widget) :
prefetchView = getViewForPosition(childPosition);
在LinearLayoutManager、GridLayoutManager中都是调用这个方法
进入源码,可以看到这个方法是一个重载方法
/** * 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); } View getViewForPosition(int position, boolean dryRun) {私有方法中,第二个参数是个boolean值(实弹执行? 传入true目测是内部单元测试用的?) public方法则是默认传入false的重载
以下分析默认该值为false
View getViewForPosition(int position, boolean dryRun) { if (position < 0 || position >= mState.getItemCount()) { throw new IndexOutOfBoundsException("Invalid item position " + position + "(" + position + "). Item count:" + mState.getItemCount()); } boolean fromScrap = false; ViewHolder holder = null;入口处是越界判断
接下来进入第1个缓存内获取
如果preLayout? 那么尝试从changed scrap里查找
注:笔者自己的理解:这个抽象类可以看做遵循开闭原则 对已有Adapter的一个拓展么 但是 实际使用场景到底是啥?
以下是getViewForPosition的后续处理,未完待续
// 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrap = holder != null; }
来看看getChangedScapViewForPosition的实现:
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 先以position为index 在mChangedScrap里查找 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 若position一致的没有找到的话(stableIds的情况下)用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; }回来继续看getViewForPosition方法:
第1步没找到 来第2步搜索 以position为index,从AttachScrap与CacheViews里查找
// 1) Find from scrap by position if (holder == null) { holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); //用postion去找缓存 if (holder != null) { if (!validateViewHolderForOffsetPosition(holder)) { //getViewForPosition的帮助方法. //Checks whether a given view holder can be used for the provided position. //检查这个vh是否能用在对应位置上 比如: //vh已经被remove后 rv是否处于preLayout状态 //若rv不处于preLayout状态 vh的viewType是否与item的对应 //若stableIds的话vh的id是否与item对应 // recycle this scrap if (!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); //detach holder.unScrap(); } else if (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal(holder); //内部实现检查视图是否被废弃或附加 //,并抛出异常。调用回收之前的公共版本。 } holder = null; } else { fromScrap = true; } } }进来看一下getScrapViewForPosition的实现:
/** * 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); //从mAttachedScrap里找 //position一致 有效 如果removed话rv在preLayout状态下的 if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { if (type != INVALID_TYPE && holder.getItemViewType() != type) { //校验viewType一致 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);//寻找正在消失的隐藏view 这种View是最理想的view 我们只需要1、unhide //2、detach //3、移动到scrap list 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); //1、unhide 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);//2、detach scrapView(view);//3、移动到scrap list vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); return vh; } } // Search in our first-level recycled view cache. 从一级缓存里用position找 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 getScrapViewForId if (!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; }ChildHelper的findHiddenNonRemovedView方法 寻找那些隐藏的view
/** * This can be used to find a disappearing view by position. * * @param position The adapter position of the item. * @param type View type, can be {@link RecyclerView#INVALID_TYPE}. * @return A hidden view with a valid ViewHolder that matches the position and type. */ View findHiddenNonRemovedView(int position, int type) { final int count = mHiddenViews.size(); for (int i = 0; i < count; i++) { final View view = mHiddenViews.get(i); RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view); if (holder.getLayoutPosition() == position && !holder.isInvalid() && !holder.isRemoved() && (type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) { return view; } } return null; }如果position为索引找不到?那么 继续探索getViewForPosition的缓存获取之路
下面是专为stableIds开设的快捷通道:
通过id为索引搜索
if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " + "position " + position + "(offset:" + offsetPosition + ")." + "state:" + mState.getItemCount()); } final int type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap via stable ids, if exists if (mAdapter.hasStableIds()) { holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrap = true; } }来看getScrapViewForId的实现
索引为id,套路类似getScrapViewForPosition
先从mAttachedScrap里找
再从mAttachedScrap里找
ViewHolder getScrapViewForId(long id, int type, boolean dryRun) { // Look in our attached views first final int count = mAttachedScrap.size();//索引为id,套路类似getScrapViewForPosition 先从mAttachedScrap里找 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); } } }//再从mAttachedScrap里找 // Search the first-level cache final 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; }到此为止cache缓存搜索完毕 如果都没有找到可用的vh
getViewForPosition下面就进入外部获取或回收池获取阶段了
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."); } } }
ViewCacheExtension 抽象类
抽象了方法abstract public View getViewForPositionAndType(Recycler recycler, int position, int type);
开发者可以实现这个抽象方法,将View实例加入这个自定义重用机制;或根据type决定是否要返回一个null,从而交给pool或adapter处理注:笔者自己的理解:这个抽象类可以看做遵循开闭原则 对已有Adapter的一个拓展么 但是 实际使用场景到底是啥?
真的脑洞不够大,想象不到,毕竟这个类只抽象了“取”并没有定义“存”
/** * ViewCacheExtension is a helper class to provide an additional layer of view caching that can * be controlled by the developer. * <p> * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and * first level cache to find a matching View. If it cannot find a suitable View, Recycler will * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking * {@link RecycledViewPool}. * <p> * 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 { /** * Returns a View that can be binded to the given Adapter position. * <p> * This method should <b>not</b> create a new View. Instead, it is expected to return * an already created View that can be re-used for the given type and position. * If the View is marked as ignored, it should first call * {@link LayoutManager#stopIgnoringView(View)} before returning the View. * <p> * RecyclerView will re-bind the returned View to the position if necessary. * * @param recycler The Recycler that can be used to bind the View * @param position The adapter position * @param type The type of the View, defined by adapter * @return A View that is bound to the given position or NULL if there is no View to re-use * @see LayoutManager#ignoreView(View) */ abstract public View getViewForPositionAndType(Recycler recycler, int position, int type); }这个开发者的扩展支持(Recycler的成员变量mViewCacheExtension)在RecyclerView的set方法
/** * Sets a new {@link ViewCacheExtension} to be used by the Recycler. * * @param extension ViewCacheExtension to be used or null if you want to clear the existing one. * * @see {@link ViewCacheExtension#getViewForPositionAndType(Recycler, int, int)} */ public void setViewCacheExtension(ViewCacheExtension extension) { mRecycler.setViewCacheExtension(extension); }如果以上都无法获取到vh,那么getViewForPosition就正式进入到垃圾回收池RecycledViewPool了!
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是ListView中垃圾箱的增强版本,让我们拭目以待
/** * RecycledViewPool lets you share Views between multiple RecyclerViews. * <p> * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. * <p> * RecyclerView automatically creates a pool for itself if you don't provide one. * */ public static class RecycledViewPool { private SparseArray<ArrayList<ViewHolder>> mScrap = new SparseArray<ArrayList<ViewHolder>>(); private SparseIntArray mMaxScrap = new SparseIntArray(); private int mAttachCount = 0; private static final int DEFAULT_MAX_SCRAP = 5;它支持在多个RecyclerView之间共享缓存数据!!! D爆了有没有!
意味着 只要ViewType一致 多个装着RecyclerView的Fragment切换之后vh数据依然可以重用!
另外不出意外 mScrap的类型是一个SparseArray<ArrayList<ViewHolder>> 以整型的viewType为key
缺省的羊肉串长度为5, 超出之后会被遗弃
当然也开放了自定义长度
public void setMaxRecycledViews(int viewType, int max) { mMaxScrap.put(viewType, max); final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType); if (scrapHeap != null) { while (scrapHeap.size() > max) { scrapHeap.remove(scrapHeap.size() - 1); } } }
// This is very ugly but the only place we can grab this information // before the View is rebound and returned to the LayoutManager for post layout ops. // We don't need this in pre-layout since the VH is not updated by the LM. if (fromScrap && !mState.isPreLayout() && holder .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); if (mState.mRunSimpleAnimations) { int changeFlags = ItemAnimator .buildAdapterChangeFlagsForAnimations(holder); changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, holder, changeFlags, holder.getUnmodifiedPayloads()); recordAnimationInfoIfBouncedHiddenView(holder, info); } } boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { if (DEBUG && holder.isRemoved()) { throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder); } final int offsetPosition = mAdapterHelper.findPositionOffset(position); holder.mOwnerRecyclerView = RecyclerView.this; mAdapter.bindViewHolder(holder, offsetPosition); attachAccessibilityDelegate(holder.itemView); bound = true; if (mState.isPreLayout()) { holder.mPreLayoutPosition = position; } } final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } else if (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; rvLayoutParams.mPendingInvalidate = fromScrap && bound; return holder.itemView; }
阅读全文
0 0
- RecyclerView回收机制分析--“取”
- RecyclerView回收原理分析
- RecyclerView机制分析: Recycler
- RecyclerView机制分析: State
- Java回收机制分析
- java垃圾回收机制分析
- Java垃圾回收机制分析
- JAVA垃圾回收机制分析
- ListView回收机制相关分析
- ListView回收机制相关分析
- ListView回收机制相关分析
- ListView回收机制相关分析
- ListView回收机制相关分析
- ListView 源码 回收机制分析
- ListView回收机制相关分析
- java垃圾回收机制分析
- JAVA垃圾回收机制分析
- 内存分析#垃圾回收机制
- 机器学习实战 决策树
- 二、策略模式——设计模式学习笔记
- 20. Valid Parentheses
- Python Challenge闯关游戏——持续更新
- Linux—基本命令
- RecyclerView回收机制分析--“取”
- shell脚本中的eval、‘‘和$()
- 描述符表(descriptor table) 学习总结
- 王一题一题一题题
- 数据库设计范式
- sprintf在51单片机中转换字符出错问题解决
- OkHttp Wiki翻译(五)拦截器
- C#技巧:Webbrowser控件光弹出错误框怎么办?
- 初探JsonCpp