美文网首页
04-RecyclerView缓存机制与实践

04-RecyclerView缓存机制与实践

作者: 程序员星星_ | 来源:发表于2023-08-25 08:10 被阅读0次
    RecyclerView缓存机制

    缓存回收是什么:缓存满的时候,根据指定的策略清理缓存。
    缓存什么:ViewHolder
    为什么缓存回收:屏幕中的数据移出屏幕时,需要销毁,重新创建,有性能开销。

    怎么缓存:

    1.屏幕内缓存:N;屏幕内滚动的item数目 【Scrap缓存】不参与滚动的回收复用

    代码:
    Recycler类:行数:5896-6937
    public final class Recycler {

    变量:
    mAttachedScrap:List<ViewHolder>:表示未与RecyclerView分离的ViewHolder列表
    mChangedScrap:List<ViewHolder>:表示数据已经改变的ViewHolder列表
    方法:

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

    2.屏幕外缓存:保证屏幕外的 view【CacheView缓存】保证屏幕外的View 在进入屏幕时无须绑定数据 即 调用OnBind

    代码:Recycler类
    变量:mCachedViews
    方法:recycleViewHolderInternal

    static final int DEFAULT_CACHE_SIZE = 2;
    int mViewCacheMax = DEFAULT_CACHE_SIZE;
    
    void recycleViewHolderInternal(ViewHolder holder) {
        //此时的holder是屏幕内View,有父亲,即屏幕内缓存
        if (holder.isScrap() || holder.itemView.getParent() != null) {
            throw new IllegalArgumentException(
                    "Scrapped or attached views may not be recycled. isScrap:"
                            + holder.isScrap() + " isAttached:"
                            + (holder.itemView.getParent() != null) + exceptionLabel());
        }
        ...
        boolean cached = false;
        boolean recycled = false;
        if (DEBUG && mCachedViews.contains(holder)) {
            throw new IllegalArgumentException("cached view received recycle internal? "
                    + holder + exceptionLabel());
        }
        if (forceRecycle || holder.isRecyclable()) {
            //屏幕外缓存,数量为2
            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();
                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) {
                //如果不用屏幕外缓存,可以加 ViewHolder 到缓存池中
                addViewHolderToRecycledViewPool(holder, true);
                recycled = true;
            }
        } else {
            ...
        }
        ...
        mViewInfoStore.removeViewHolder(holder);
        ...
    }
    

    滑动过程中的回收和复用都是先处理的这个 List,这个集合里存的 ViewHolder 的原本数据信息都在,所以可以直接添加到 RecyclerView 中显示,不需要再次重新 onBindViewHolder()。
    移出屏幕的 item 是一个表项,再次回到界面,ViewGroup-addView,对应代码中: mCachedViews.add(targetCacheIndex, holder);
    后来调用 代码 #initChildrenHelper 即 RecyclerView.this.addView(child, index);

    private void initChildrenHelper() {
        mChildHelper = new ChildHelper(new ChildHelper.Callback() {
            @Override
            public int getChildCount() {
                return RecyclerView.this.getChildCount();
            }
    
            @Override
            public void addView(View child, int index) {
                if (VERBOSE_TRACING) {
                    TraceCompat.beginSection("RV addView");
                }
                RecyclerView.this.addView(child, index);
                if (VERBOSE_TRACING) {
                    TraceCompat.endSection();
                }
                dispatchChildAttached(child);
            }
            ...
        });
    }
    

    3.缓存池:5M 二级缓存 每个ViewType5

    【RecycledViewPool】大小为 viewholder.size,存在这里的 ViewHolder 的数据信息会被重置掉,相当于 ViewHolder 是一个重新创建的一样,所以需要重新调用 onBindViewHolder 来绑定数据。

    时机:当屏幕外缓存 数量 >= 2,ViewHolder就会放进缓存池 # addViewHolderToRecycledViewPool

    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);
    }
    
    void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
        ...
        getRecycledViewPool().putRecycledView(holder);
    }
    

    知识点:

    类 :Recycler

    初始化onLayout中,有一个 mAttachedxxx 集合,临时存在即将显示的第一屏的view,在最后一次onLayout结束之后,会从将该mAttachedxxx里面的view渲染到第一屏页面上。

    当向上滑动过程中,都是先将滑出屏幕的view放到Recycle中,然后从Recycle中经过转换,将该view渲染到界面上。

    原理:

    数据发生变化后,viewholder 被 detach 掉后 缓存在 mChangedScrap 之中,在这里拿到的 viewHolder后续需要重新绑定。

    滑动列表时,一旦item超出了屏幕,那么就会被放入到mCachedViews 中,如果满了,就会将“尾部”的元素移动到pool中,如果pool也满了,那么就会被丢弃,等待回收。

    从 被给的 position 中 尽力地 获取 ViewHolder,或从 Recycler scrap,cache,RecycledViewPool 或直接创建。

    相关文章

      网友评论

          本文标题:04-RecyclerView缓存机制与实践

          本文链接:https://www.haomeiwen.com/subject/jngmmdtx.html