(六)RecycleView 回收复用机制总结

目录

前言

一、Recycle 类

二、四级缓存

三、缓存详解

四、复用详解


前言

众所周知,RecycleView 本身就是一款非常优秀的可回收、复用的原生控件,有着极高的灵活性,能够在应用中承担展示大量数据的艰巨任务。在日常开发中,使用也非常广泛。本篇将一起了解一下 RecyclerView 内部是通过怎样的缓存复用机制来实现这一功能的。

推荐阅读 

(一)RecycleView 初探回收复用,onCreateView和onBindView调用关系

(二)RecycleView 实现吸附小标题的Demo(附源码)

(三)RecycleView 自定义下拉刷新,上拉加载监听

(四)RecycleView 滑动到置顶、Adapter局部刷新

(五)RecycleView 动态设置改变列表显示的高度

(六)RecycleView 回收复用机制总结

(七)RecycleView 性能提升、卡顿优化


一、Recycle 类

RecycleView的特点是回收复用ViewHolder,殊不知,这一神技其实是由Recycle内部类完成的,它也是复用机制的核心部分。首先来了解一下Recycler 的主要成员变量都有哪些。

 public final class Recycler {
        //一级缓存
        final ArrayList mAttachedScrap = new ArrayList<>();
        ArrayList mChangedScrap = null;
        //二级缓存
        final ArrayList mCachedViews = new ArrayList();
        static final int DEFAULT_CACHE_SIZE = 2;
        //三级缓存
        private ViewCacheExtension mViewCacheExtension;
        //四级缓存
        RecycledViewPool mRecyclerPool;

}

Recycle类中有四个ArrayList缓存集合,它们的作用各不相同,可以按优先级从高到底分为 4个级别。


 

二、四级缓存

1、一级缓存:缓存屏幕内显示的ViewHolder

 

  • mAttachedScrap 存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder;
  • mChangedScrap 表示数据已经改变的 ViewHolder 列表, 存储例如notify()时需要改变的 ViewHolder。

2、二级缓存:缓存少量移除屏幕之外的 ViewHolder

mCachedViews ,“一个脚踏俩只船且喜新厌旧的花心大萝卜”。

因为它默认缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder。

3、三级缓存:用户自定义扩展缓存

ViewCacheExtension ,这个的创建和缓存完全由开发者自己控制,系统未往这里添加数据。

因为会脱离 Adapter.createViewHolder 进行ViewHolder的创建 ,在bindViewHolder中需要通过判断viewType做数据绑定时,不易维护。

4、四级缓存:缓存大量移除屏幕之外的ViewHolder 

RecycledViewPool,“一个有底线的收容所”。

当 mCachedViews 中存不下新的 ViewHolder时,会将旧的ViewHolder存入到RecycleViewPool列表里来。但它也不是无脑接受mCachedViews的“破鞋”,它也是有底线的!它缓存限制条件就是:按照 Type 类型,默认每个Type最多缓存 5 个。另外它是用public static 修饰的,因此可以被其他RecyclerView 共享。


 

三、缓存详解

当调用了Adapter 的 notifyDataSetChanged方法,会调用 LayoutManager 的onLayoutChildren 方法,然后再调用LayoutManager.detachAndScrapAttachedViews(recycler)方法中进行LayoutManager.scrapOrRecycleView(..)方法中,通过针对内部不同的状态 (mFlags) 进行相应的缓存复用 ViewHolder 的处理。

//废弃或者回收利用View
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        // 缓存到 mCacheViews 和 RecyclerViewPool
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        // 缓存到 mAttachedScrap 和 mChangedScrap
        recycler.scrapView(view); 
    }
}

1、mAttachedScrap 和 mChangedScrap

Recycle.scrapView(view)方法将屏幕上所有的 ViewHolder 回收到 mAttachedScrap 和 mChangedScrap。


void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
        // 标记为移除或失效的 || 完全没有改变 || item 无动画或动画不复用
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
        || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        mAttachedScrap.add(holder);
    } else {
        // 相反就得出放入 mChangedScrap 的条件啦
        mChangedScrap.add(holder);
    }
}

2、mCacheViews 和 RecyclerViewPool

在scrapOrRecycleView()方法中,当ViewHolder是无效或移除时进入recycleViewHolderInternal,进行缓存。

void recycleViewHolderInternal(ViewHolder holder) {
    if (forceRecycle || holder.isRecyclable()) {
        if(mViewCacheMax > 0
             && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                 | ViewHolder.FLAG_REMOVED
                 | ViewHolder.FLAG_UPDATE
                 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
          int cachedViewSize = mCachedViews.size();
          if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
              // 1. mCacheViews 满了,最早加入的不要了放 RecyclerViewPool
              recycleCachedViewAt(0); 
          }
           mCachedViews.add(targetCacheIndex, holder);
           cached = true;
        }

        if (!cached) { 
            // 2. 不能放进 mCacheViews 的放 RecyclerViewPool
            addViewHolderToRecycledViewPool(holder, true);
        }
    }   
}

//回收缓存的视图并从列表中删除该视图
void recycleCachedViewAt(int cachedViewIndex) {
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}

 

四、复用详解

复用的入口是getViewForPosition(int)方法,通过传入列表中item的位置索引,返回一个ViewHolder实例。

复用时,首先去四个缓存集合中依次寻找,是否有ViewHolder的实例,有的话就返回实例;如果都没有,才会调用createViewHolder进行创建。

public View getViewForPosition(int position) {
     return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
     return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {....}

大致流程:通过调用RecyclerView.getViewForPosition(int) 方法,进入到 Recycler.getViewForPosition(int, boolean)的内部方法中,最后进入到Recycler.tryGetViewHolderForPositionByDeadline()方法中,拿到ViewHolder实例。

tryGetViewHolderForPositionByDeadLine()完整调用链流程图 :

(六)RecycleView 回收复用机制总结_第1张图片

来看看这个方法是怎么拿到相应位置上的 ViewHolder :

ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
    if (mState.isPreLayout()) {
        // 0) 预布局从 mChangedScrap 里面去获取 ViewHolder
        holder = getChangedScrapViewForPosition(position);
    }

    if (holder == null) {
        // 1) 分别从 mAttachedScrap、mCachedViews 获取 ViewHolder
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    }

    if (holder == null) {
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) 通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
        }

        if (holder == null && mViewCacheExtension != null) {
            // 3) 从自定义缓存获取
            View view = mViewCacheExtension
                getViewForPositionAndType(this, position, type);
            holder = getChildViewHolder(view);
        }
    }
    if (holder == null) {
        // 4) 从 RecycledViewPool 获取 ViewHolder
        holder = getRecycledViewPool().getRecycledView(type);
    }

    if (holder == null) {
        // 缓存全取过了,没有,那只好 create 一个咯
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
    }
}

1、 从 mChangedScrap 里面去寻找(前提是预布局);

2、从 mAttachedScrap和mCachedViews 寻找;  (判断条件:position)

3、从 mAttachedScrap 和 mCachedViews 寻找;(判断条件:ItemId  ,viewType)

4、从 mViewCacheExtension 自定义缓存寻找;   (判断条件:ItemId  ,viewType)

5、从 RecycledViewPool 里面去寻找;(判断条件:viewType)

6、全寻找过了,依然没有ViewHolder,就会按viewType去创建createViewHolder()一个新的。


 彩蛋:

有没有人会好奇,mCachedViews 和 RecycledViewPool 这两个缓存集合,为什么要有这样俩呢?他俩内部有什么区别呢?

(一)RecycleView 初探回收复用,onCreateView和onBindView调用关系

点开看看吧,文章末尾应该有答案。

 

你可能感兴趣的:(Android)