美文网首页
RecyclerView 缓存机制小结

RecyclerView 缓存机制小结

作者: jkwen | 来源:发表于2021-04-29 00:11 被阅读0次

    本文仅是记录自己在分析缓存机制的一个思路过程,如果想完全了解缓存机制原理,可以参看本文的参考文章会更有帮助。

    先从 RecyclerView 类文件里挑选了一些觉得用 RecyclerView 缓存复用机制主要相关的一些类,根据类注释先简单看下,

    ViewHolder 代表着列表里的一个 item,这里包含两部分,一部分是 item View, 一部分是 item Data。实现 Adapter 的时候,ViewHolder 应该要作为 Adapter 的子类,也就是说 ViewHolder 算是属于 Adapter 的。

    Recycler 管理着那些 detached,scrapped 的 item Views,在合适的时候用来复用。看来这个类是个关键类。这里还有一点,关于 scrapped item Views,注释说是指那些还没和父类(也就是 RecyclerView)解绑但又被标记为要移除的或者就是用来重用的,暂时能得到这么多,后面看看代码再理解理解,detached item Views 应该就是那些和 RecyclerView 解绑的 item Views 吧。

    RecycledViewPool 它可以使多个 RecyclerView 共享 Views 成为可能。这里打算整理的是纵向的复用机制,而这个类恰巧讨论的是横向的复用机制,暂且不做深入了解。

    ChildHelper 用来管理 item Views,包裹着 RecyclerView,为其提供一些获取 item Views 的方法,例如 getChildAt(), getChildCount() 等等。该类型对象会在 RecyclerView 初始化时创建,内部会初始化一个View 类型的 mHiddenViews 列表,一个 callback 回调,一个 mBucket 链表。

    要分析 RecyclerView 怎么复用,估计还得从 View 的工作流程入手。但在分析流程之前,按着平常代码实现的步骤,先看看 setLayoutManager() 和 setAdapter() 方法里有没有相关代码逻辑,

    //RecyclerView
    public void setLayoutManager(@Nullable LayoutManager layout) {
        if (mLayout != null) {
            
        } else {
            //mRecycler 在 RecyclerView 初始化时就完成了创建,且用 final 修饰
            //初始化ViewHolder类型的 mAttachedScrap, mCachedViews 列表
            mRcycler.clear();
        }
        mRecycler.updateViewCacheSize();
    }
    //Recycler
    public void clear() {
        mAttachedScrap.clear();
        //这个方法,倒叙遍历 mCachedViews,
        //依次取出 ViewHolder 把它放到了 RecycledViewPool 里面缓存
        //清空 mCachedViews
        recycleAndClearCachedViews();
    }
    //setAdapter 方法里也会使 mRecycler 进行 clear() 操作,更新 RecyclerViewPool 里的适配器
    

    假设这时赋值了数据列表,并调用了适配器的 notifyDataSetChanged() 方法,通过 requetLayout() 方法进行 View 的工作运作,最后会定位到 Recycler 的 tryGetViewHolderForPositionByDeadline() 方法,

    ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
        if (holder == null) {
            //这里是从缓存中取
            holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        }
        if (holder == null) {
            final int type = mAdapter.getItemViewType(offsetPosition);
            if (holder == null) {
                //这里从 RecycledViewPool 中取缓存 ViewHolder
                holder = getRecycledViewPool().getRecycledView(type);
            }
            if (holder == null) {
                //这里是新建
                holder = mAdapter.createViewHolder(RecyclerView.this, type);
            }
        }
    }
    ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
        //这个方法会从三个地方来取缓存
        //首先是从 mAttachedScrap 列表里取
        final int scrapCount = mAttachedScrap.size();
        for (int i = 0; i < scrapCount; i++) {
            final ViewHolder holder = mAttachedScrap.get(i);
            if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
                holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                return holder;
            }
        }
        if (!dryRun) {
            //如果 mAttachedScrap 没有获取到,将通过 ChildHelper 对象
            //因为 ChildHelper 对象有个 mHiddenViews 列表,会遍历它看看有没有
            View view = mChildHelper.findHiddenNonRemovedView(position);
            if (view != null) {
                //如果有的话将从 ChildHelper 对象的 mHiddenViews 中移除,并添加到 mAttachedScrap 中
                final ViewHolder vh = getChildViewHolderInt(view);
                mChildHelper.unhide(view);
                int layoutIndex = mChildHelper.indexOfChild(view);
                mChildHelper.detachViewFromParent(layoutIndex);
                scrapView(view);
                return vh;
            }
        }
        //最后如果上面也没取到,那就从 mCachedViews 里获取
        final int cacheSize = mCachedViews.size();
        for (int i = 0; i < cacheSize; i++) {
            final ViewHolder holder = mCachedViews.get(i);
            if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
                if (!dryRun) {
                    mCachedViews.remove(i);
                }
                return holder;
            }
        }
    }
    //三个地方。。。。。。莫非这就是传说中的 RecyclerView 三级缓存?
    

    从缓存中怎么取大概知道了,那缓存怎么存的?一开始我想应该是创建完 ViewHolder 之后会存起来,但找了一遍相关代码貌似没有看见。我准备从这几个缓存载体上入手,

    mAttachedScrap

    唯一操作 ViewHolder 对象添加到该列表里的地方就是 scrapView() 方法,而这个方法以及调用这个方法的方法还比较多,就拿上面取缓存的流程来说,要是 mAttachedScrap 里没有取到,那么会通过 ChildHelper 获取,并存入 mAttachedScrap,这样确保了下次能在第一个地方取到。

    mHiddenViews

    这个列表会将 item View 为 hidden 状态的存下来,那什么样的才是 hidden 状态,结合代码推测应该是那些 removed,detached 的,而不是我们把 item View 给 GONE 掉,它就会添加到这个列表里,两个是不一样的概念。

    mCachedViews

    唯一操作 ViewHolder 对象添加到该列表里的地方就是 recyclerViewHolderInternal() 方法,看上去这个会是优先缓存的载体。

    小结

    上面的分析还比较零碎,并没有形成面,也没有看到整体一个缓存机制运行的过程,但因为没有足够的时间来继续跟进,所以考虑暂时先完结掉,待后面有精力了再继续深究。

    有时候不能一味的自己摸索,也要借鉴,学习别人的优点,好的地方,所以我也搜索了一些相关文章,觉得还行的就引用来做参考文章了,方便下次阅读。

    参考内容

    深入理解 RecyclerView 的缓存机制
    RecyclerView缓存原理,有图有真相

    相关文章

      网友评论

          本文标题:RecyclerView 缓存机制小结

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