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) :         
View view = mRecycler.getViewForPosition(position);
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) :            
final View view = recycler.getViewForPosition(mCurrentPosition);
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里查找
            // 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);                }            }        }



以下是getViewForPosition的后续处理,未完待续
            // 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;        }












原创粉丝点击