RecyclerViewd回收复用

之前也从鸿洋大神那看过类似博文,留下的大概印象是:recyclerView有四级缓存,其中一种是自定义缓存,当时作者还尝试使用自定义的缓存,但是结果是缓存错乱,作者说暂时还不明确它的作用,目前官方为我们定义的其他的缓存已经够用了。

看完之后感觉有点乱,还是没能理解它的原理和机制。

我们知道缓存其实缓存的就是ViewHolder,可以通过打印查看onCreateViewHolder和onBindViewHolder来查看缓存情况。

四级缓存:
1. mChangeScrap与 mAttachedScrap 用来缓存还在屏幕内的 ViewHolder
2. mCachedViews 用来缓存移除屏幕之外的 ViewHolder
3. mViewCacheExtension 开发给用户的自定义扩展缓存,需要用户自己 管理 View 的创建和缓存
4. RecycledViewPool ViewHolder 缓存池

缓存就是在我们滑动的时候做的,所以我们直接从recyclerView的onTouchEvent的move事件跟踪查看

recyclerView的源码实在太多,无法一一分析,只能跟踪大概流程,知道它的原理即可。

滑动:move–>scrollByInternal–>scrollStep–>水平和垂直两种我们选一种看scrollVerticallyBy
–>我们找一个它的实现类LinearLayoutManager–》scrollBy–》fill(关键逻辑代码都在这里面了)–》layoutChunk–》layoutState.next–》addView

layoutState.next–》getViewForPosition–》tryGetViewHolderForPositionByDeadline(核心)–》

1、getChangedScrapViewForPosition 主要与动画相关,失败了就从下面2找

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;
        }

2、getScrapOrHiddenOrCachedHolderForPosition–》获取:mAttachedScrap 、mCachedViews 如果失败了则从3找一个通过position一个通过viewType和itemId

3、getScrapOrCachedViewForId–》获取:mAttachedScrap 、mCachedViews

4、mViewCacheExtension .getViewForPositionAndType–》自定义缓存

5、getRecycledViewPool().getRecycledView–》从缓存池中获取

上面所有的判断都失败了,拿不到holder的时候则mAdapter.createViewHolder–》tryBindViewHolderByDeadline–》onBindViewHolder

多级缓存的目的–》性能

回收:布局刷新的时候会启动回收。从实际的操作中我们滑动recyclerView的时候,它是在不停地刷新,一边在回收,一边在复用。从onLayoutChildren开始看

开始:LinearLayoutManager.onLayoutChildren–》detachAndScrapAttachedViews–》scrapOrRecycleView–》
1、recycler.recycleViewHolderInternal(viewHolder);处理cacheView、recyclerViewPool

void recycleViewHolderInternal(ViewHolder holder) {
           ...

            if (forceRecycle || holder.isRecyclable()) {
            //mViewCacheMax默认 == 2, ViewHolder没有做出改变的时候进入这个判断
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                    //大于等于== 默认2的时候进入这个判断
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }

                    int targetCacheIndex = cachedViewSize;
                    if (ALLOW_THREAD_GAP_WORK
                            && cachedViewSize > 0
                            && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                        // when adding the view, skip past most recently prefetched views
                        int cacheIndex = cachedViewSize - 1;
                        while (cacheIndex >= 0) {
                            int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                            if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                                break;
                            }
                            cacheIndex--;
                        }
                        targetCacheIndex = cacheIndex + 1;
                    }
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } else {
                // NOTE: A view can fail to be recycled when it is scrolled off while an animation
                // runs. In this case, the item is eventually recycled by
                // ItemAnimatorRestoreListener#onAnimationFinished.

                // TODO: consider cancelling an animation when an item is removed scrollBy,
                // to return it to the pool faster
                if (DEBUG) {
                    Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                            + "re-visit here. We are still removing it from animation lists"
                            + exceptionLabel());
                }
            }
            // even if the holder is not removed, we still call this method so that it is removed
            // from view holder lists.
            mViewInfoStore.removeViewHolder(holder);
            if (!cached && !recycled && transientStatePreventsRecycling) {
                holder.mOwnerRecyclerView = null;
            }
        }

当满足cachedViewSize >= mViewCacheMax && cachedViewSize > 0的时候–》

recycleCachedViewAt(0);
cachedViewSize–;

 void recycleCachedViewAt(int cachedViewIndex) {
            if (DEBUG) {
                Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
            }
            ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
            if (DEBUG) {
                Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
            }
            addViewHolderToRecycledViewPool(viewHolder, true);
            mCachedViews.remove(cachedViewIndex);
        }

上面的代码非常简单,就是从cacheView里面拿0位置的缓存放到recycerViewPool里面
然后cacheView把0位置的删除掉,之后cachedViewSize–; (先进先出 队列)

这个if判断再之后的代码是将当前缓存放到cacheView里面

cacheView缓存的ViewHolder是怎么进行保存的:

public final View itemView;
        WeakReference<RecyclerView> mNestedRecyclerView;
        int mPosition = NO_POSITION;
        int mOldPosition = NO_POSITION;
        long mItemId = NO_ID;
        int mItemViewType = INVALID_TYPE;
        int mPreLayoutPosition = NO_POSITION;
        ...

可以看到有position和viewType,也佐证了上面找缓存通过position和viewType来找的原因,因为这里保存了对应的位置和类型

当上面的if判断://mViewCacheMax >= 2, 且ViewHolder没有做出改变的时候这个判断失败之后则–》addViewHolderToRecycledViewPool–》getRecycledViewPool().putRecycledView(holder);

public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }

上面的代码逻辑非常简单,根据对应的viewType创建对应的ViewHolder进行保存
其中mMaxScrap是==5,也就是每一种viewType的最大数量只保存5个

到了这里cacheView和recyclerViewPool这两种缓存就看完了

2、recycler.scrapView(view);

 void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
                    throw new IllegalArgumentException("Called scrap view with an invalid view."
                            + " Invalid views cannot be reused from scrap, they should rebound from"
                            + " recycler pool." + exceptionLabel());
                }
                holder.setScrapContainer(this, false);
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                holder.setScrapContainer(this, true);
                mChangedScrap.add(holder);
            }
        }

根据holder是否做出改变(包括标记、更新、动画、移除等判断)来确认是哪一种缓存
1、mAttachedScrap.add(holder); 没改变
2、mChangedScrap.add(holder);已经改变

在刷新布局的时候onlayout的时候会触发回收复用

回收入口:recyclerView.onLayout->dispatchLayout–>dispatchLayoutStep2->mLayout.onLayoutChildren(mRecycler, mState);–>fill(复用流程)

复用:fill–》recycleByLayoutState–》

 if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
        } else {
            recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
        }

上滑或者下滑

recycleViewsFromStart–》recycleChildren–》removeAndRecycleViewAt–》recycler.recycleView–》recycleViewHolderInternal–》缓存

在刷新的时候onLayout只会触发cacheView和recyclerViewPool这两种缓存

没有看到使用自定义缓存的地方,这貌似也是当初很早的时候recyclerView出来我看到的那篇博文,作者去使用自定义缓存出问题的原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值