美文网首页
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