源码分析

RecycleView缓存原理

2020-06-23  本文已影响0人  唯爱_0834

适配器模式

类适配器
public interface Target {
    public void request();
}

public class Adaptee {

    public void specialRequest(){
        System.out.println("被适配类 具有特殊功能...");
    }
}
//类继承Adaptee并实现接口,然后通过接口方法调取adaptee的方法即可
public class AdapterInfo extends Adaptee implements Target {
    public static void main(String[] args) {
        Target adaptee = new AdapterInfo();
        adaptee.request();
    }
    @Override
    public void request() {
        super.specialRequest();
    }
}

对象适配器: 使用较多

class Adapter implements Target{
    // 直接关联被适配类
    private Adaptee adaptee;

    // 可以通过构造函数传入具体需要适配的被适配类对象
    public Adapter (Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    public void request() {
        // 这里是使用委托的方式完成特殊功能
        this.adaptee.specificRequest();
    }
}

RecycleView中的adapter

//RecycleView的类继承关系
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2
//adapter的继承关系
class FooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
//Adapter
public abstract static class Adapter<VH extends RecyclerView.ViewHolder> {
        private final RecyclerView.AdapterDataObservable mObservable = new RecyclerView.AdapterDataObservable();
        private boolean mHasStableIds = false;

        public Adapter() {
        }
        
//mObservable是每一个Adapter创建一个内部类,观察者模式监听条目的变化
 static class AdapterDataObservable extends Observable<RecyclerView.AdapterDataObserver> {
        AdapterDataObservable() {
        }
 public abstract static class Adapter<VH extends ViewHolder> {
 //静态类用于Adapter数据变化的Observable,每一个类都有一个自己的Observable对象
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        
//setAdapter
  public void setAdapter(Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        requestLayout();
    }
    
//setAdapterInternal:使用心得adapter替换掉老的adapter,并且触发监听mObserver
   private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver); // 注销老的adapter观察者
            mAdapter.onDetachedFromRecyclerView(this); //解绑recycleview
        }
        ....
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver); //注册观察者
            adapter.onAttachedToRecyclerView(this);
        }
        ...
    }
RecycleView的缓存
ListView缓存
四级缓存
  1. 一级缓存: mAttachedScrap 和mChangedScrap : 优先级最高的缓存,RecycleView在获取viewHolder时,优先会从这两个缓存中找,其中前者存储的是当前还在屏幕中的viewHolder,后者存储的是数据被更新的viewHolder,比如调用了adapter.notifyItemChanged()方法更新条目
    • mAttachedScrap : 他表示存储在当前还在屏幕中的ViewHolder,实际上是从屏幕上分离出来的ViewHolder,但是又即将添加到屏幕上去的ViewHolder,比如上下滑动,滑出一个新的Item,此时会重新调用LayoutManager的onLayoutChildren方法,从而会将屏幕上所有的ViewHolder先scrap掉(废弃掉),添加到mAttachedScrap中去,然后在重新布局每一个ItemView时,会优先从mAttachedScrap中获取,这样效率就会非常的高,这个过程不会重新onBindViewHolder
  2. 二级缓存: mCachedViews : 默认大小为2,通常用来存储预取的viewHolder,同时在回收ViewHolder时,也会可能存储一部分的viewHolder,这部分的viewHolder通常同一级缓存差不太多,下面分析
    • 默认是2,不过通常是3,3由默认的大小2+ 预取的个数1,,所以在RecycleView在首次加载时,mCacheView的size为3(LinerLayoutManager布局为例)
  3. 三级缓存: ViewCachedExtension : 自定义缓存,通常用不到
  4. RecycleViewPool : 根据viewType来缓存ViewHolder,每个viewType的数组大小为5,可以动态改变
ViewHolder的几个状态值
  1. 上方一级缓存中有两个,即如果调用了adapter.notifyItemChanged(),会重新回调到LayoutManager的onLayoutChildren()中,那么他两又有啥区别呢? 我们不妨看一段代码逻辑:
//根据viewHolder的flag状态决定是放入mAttachedScrap还是mChangedScrap中去
void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) //同时标记remove和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);
            }
        }
        
     boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
        return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
                viewHolder.getUnmodifiedPayloads());
    }
RecycleView的复用
//我们以 LinearLayoutManager.next为例
View next(Recycler recycler) {
            if (this.mScrapList != null) {
                return this.nextViewFromScrapList();
            } else {
                View view = recycler.getViewForPosition(this.mCurrentPosition);
                this.mCurrentPosition += this.mItemDirection;
                return view;
            }
        }
  1. 通过position方式获取对应的viewHolder: 优先级比较高,因为每个viewHolder还未被该变,都是由于某一个itemView对应的viewHolder被更新导致的,所以在屏幕上其他viewHolder可以快速对应原来的ItemView
if (position >= 0 && position < RecyclerView.this.mState.getItemCount()) { //position合法
                boolean fromScrapOrHiddenOrCache = false;
                RecyclerView.ViewHolder holder = null;
                if (RecyclerView.this.mState.isPreLayout()) {
                    holder = this.getChangedScrapViewForPosition(position); //从mChangedScrap中获取
                    fromScrapOrHiddenOrCache = holder != null;
                }

                if (holder == null) {
                    holder = this.getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                    if (holder != null) {
                        if (!this.validateViewHolderForOffsetPosition(holder)) { //检查当前position是否位置匹配,如果不匹配说明滑出去或者失效了
                            if (!dryRun) {
                                holder.addFlags(4);
                                if (holder.isScrap()) {
                                    RecyclerView.this.removeDetachedView(holder.itemView, false);
                                    holder.unScrap();
                                } else if (holder.wasReturnedFromScrap()) {
                                    holder.clearReturnedFromScrapFlag();
                                }
                                //两种情况,如果是滑出屏幕说明可服用,加入到mCacheViews中直接复用,如果失效则说明数据不可用,但视图可用,直接回收ViewHolder加入到viewPool中即可,每次从他里面加载都会重新走onBindViewHolder
                                this.recycleViewHolderInternal(holder);
                            }

                            holder = null;
                        } else {
                            fromScrapOrHiddenOrCache = true;
                        }
                    }
                }


  1. 首先看从mChangedScrap中获取:如果当前是预布局阶段,则从mChangedScrap中获取ViewHolder
    • 预布局: preLayout,即当前RecycleView处于dispatchLayoutStep1阶段时,称为预布局
    • dispatchLayoutStep2阶段被称为真正的布局阶段
    • dispatchLayoutStep3称为postLayout阶段
    • 同时要想真正开启预布局,必须要有itemAnimator,并且每个recycleView对应的LayoutManager必须开启预处理动画
if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
 if (holder == null) {
                    holder = this.getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                    if (holder != null) { 
                        if (!this.validateViewHolderForOffsetPosition(holder)) {
                            if (!dryRun) {
                                holder.addFlags(4);
                                if (holder.isScrap()) {
                                    RecyclerView.this.removeDetachedView(holder.itemView, false);
                                    holder.unScrap();
                                } else if (holder.wasReturnedFromScrap()) {
                                    holder.clearReturnedFromScrapFlag();
                                }

                                this.recycleViewHolderInternal(holder);
                            }

                            holder = null;
                        } else {
                            fromScrapOrHiddenOrCache = true;
                        }
                    }
                }

final ArrayList<RecyclerView.ViewHolder> mAttachedScrap = new ArrayList();
final ArrayList<RecyclerView.ViewHolder> mCachedViews = new ArrayList(); //缓存大小有默认的2加上layoutManager的设置决定

void updateViewCacheSize() {
    int extraCache = RecyclerView.this.mLayout != null ? RecyclerView.this.mLayout.mPrefetchMaxCountObserved : 0;
    this.mViewCacheMax = this.mRequestedCacheMax + extraCache;

    for(int i = this.mCachedViews.size() - 1; i >= 0 && this.mCachedViews.size() > this.mViewCacheMax; --i) {
        this.recycleCachedViewAt(i);
    }

}
//如果CachedViews中超过了2,则将其移到recycleViewPool缓存中去
void recycleCachedViewAt(int cachedViewIndex) {
    RecyclerView.ViewHolder viewHolder = (RecyclerView.ViewHolder)this.mCachedViews.get(cachedViewIndex);
    this.addViewHolderToRecycledViewPool(viewHolder, true);
    this.mCachedViews.remove(cachedViewIndex);
}
        


RecyclerView.ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
            int scrapCount = this.mAttachedScrap.size();
            //从mAttachedScrap中获取,如果可用则返回
            int cacheSize;
            RecyclerView.ViewHolder vh;
            for(cacheSize = 0; cacheSize < scrapCount; ++cacheSize) {
                vh = (RecyclerView.ViewHolder)this.mAttachedScrap.get(cacheSize);
                if (!vh.wasReturnedFromScrap() && vh.getLayoutPosition() == position && !vh.isInvalid() && (RecyclerView.this.mState.mInPreLayout || !vh.isRemoved())) {
                    vh.addFlags(32);
                    return vh;
                }
            }

            if (!dryRun) { //false默认值
                //从mHiddrenViews中获取
                View view = RecyclerView.this.mChildHelper.findHiddenNonRemovedView(position);
                if (view != null) {
                    vh = RecyclerView.getChildViewHolderInt(view);
                    RecyclerView.this.mChildHelper.unhide(view);
                    int layoutIndex = RecyclerView.this.mChildHelper.indexOfChild(view);
                    if (layoutIndex == -1) {
                        throw new IllegalStateException("layout index should not be -1 after unhiding a view:" + vh + RecyclerView.this.exceptionLabel());
                    }

                    RecyclerView.this.mChildHelper.detachViewFromParent(layoutIndex);
                    this.scrapView(view);
                    vh.addFlags(8224);
                    return vh;
                }
            }
            //从mCacheViews中获取
            cacheSize = this.mCachedViews.size();

            for(int i = 0; i < cacheSize; ++i) {
                RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder)this.mCachedViews.get(i);
                if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
                    if (!dryRun) {
                        this.mCachedViews.remove(i);
                    }

                    return holder;
                }
            }

            return null;
        }
public static class RecycledViewPool {
        private static final int DEFAULT_MAX_SCRAP = 5; 
        SparseArray<RecyclerView.RecycledViewPool.ScrapData> mScrap = new SparseArray(); //使用SparseArray键是int型的 viewType 的值,value是ScrapData对象
        private int mAttachCount = 0;

//在分析ScrapData是RecycledViewPool静态内部类
 static class ScrapData {
            final ArrayList<RecyclerView.ViewHolder> mScrapHeap = new ArrayList(); //对于每个viewType的不同保存在不同的ArrayList集合中,相同的viewType保存在同一个集合中,且一种viewType类型最多保存5条ViewHolder
            int mMaxScrap = 5;
            long mCreateRunningAverageNs = 0L;
            long mBindRunningAverageNs = 0L;

            ScrapData() {
            }
        }
int offsetPosition;
int type;
if (holder == null) {
offsetPosition = RecyclerView.this.mAdapterHelper.findPositionOffset(position); //获取position对应的偏移量
if (offsetPosition < 0 || offsetPosition >= RecyclerView.this.mAdapter.getItemCount()) { 
//如果当前偏移量没有出现在屏幕上,即更改了数据但是却没有刷新RecycleView时,会报错的,因此我们setData以后一定要notify通知更新UI操作
    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item position " + position + "(offset:" + offsetPosition + ")." + "state:" + RecyclerView.this.mState.getItemCount() + RecyclerView.this.exceptionLabel());
}

type = RecyclerView.this.mAdapter.getItemViewType(offsetPosition); //获取当前位置对应的type
if (RecyclerView.this.mAdapter.hasStableIds()) { //如果从新hasStableIds返回true,即使通过position获取ViewHolder失败,还会尝试通过ViewType去获取ViewHolder,优先在Scrap和cached缓存中寻找
    //根据itemId , type判断当前位置是否存在缓存中,上面只是根据position来判断
    holder = this.getScrapOrCachedViewForId(RecyclerView.this.mAdapter.getItemId(offsetPosition), type, dryRun);
    if (holder != null) {
        holder.mPosition = offsetPosition;
        fromScrapOrHiddenOrCache = true;
    }
}
//用户自定义缓存策略,没啥用
if (holder == null && this.mViewCacheExtension != null) {
    View view = this.mViewCacheExtension.getViewForPositionAndType(this, position, type);
    if (view != null) {
        holder = RecyclerView.this.getChildViewHolder(view);
        if (holder == null) {
            throw new IllegalArgumentException("getViewForPositionAndType returned a view which does not have a ViewHolder" + RecyclerView.this.exceptionLabel());
        }

        if (holder.shouldIgnore()) {
            throw new IllegalArgumentException("getViewForPositionAndType returned a view that is ignored. You must call stopIgnoring before returning this view." + RecyclerView.this.exceptionLabel());
        }
    }
}

if (holder == null) {
//根据type从recycleViewPool中获取holder缓存对象
    holder = this.getRecycledViewPool().getRecycledView(type); //从五个里面拿数组的最后一个,拿到从缓存数组中移除一个数据
    if (holder != null) {
        holder.resetInternal(); //只是拿到布局文件,重置里面的信息,以便后续设置值使用
        if (RecyclerView.FORCE_INVALIDATE_DISPLAY_LIST) {
            this.invalidateDisplayListInt(holder);
        }
    }
}

 if (holder == null) { //如果上方缓存中都没有值了,则调用onCreateViewHolder创建一个新的ViewHolder
    long start = RecyclerView.this.getNanoTime();
    if (deadlineNs != 9223372036854775807L && !this.mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
        return null;
    }
    
    holder = RecyclerView.this.mAdapter.createViewHolder(RecyclerView.this, type); //根据type创建一个新的ViewHolder
    if (RecyclerView.ALLOW_THREAD_GAP_WORK) {
        RecyclerView innerView = RecyclerView.findNestedRecyclerView(holder.itemView);
        if (innerView != null) {
            holder.mNestedRecyclerView = new WeakReference(innerView);
        }
    }

    long end = RecyclerView.this.getNanoTime();
    this.mRecyclerPool.factorInCreateTime(type, end - start);
}
RecycleView回收机制
  1. scrap数组回收
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);
    }
}
  1. mCacheViews回收
  1. mHiddenViews数组
    • 一个ViewHolder回收到mHiddenView数组里面的条件比较简单,如果当前操作支持动画,就会调用到RecyclerView的addAnimatingView方法,在这个方法里面会将做动画的那个View添加到mHiddenView数组里面去。通常就是动画期间可以会进行复用,因为mHiddenViews只在动画期间才会有元素。
  2. RecycleViewPool
    • recycleViewPool同mCacheView相同,都是通过recycleViewHolderInternal方法来回收的,只是当不满足mCacheView条件是,即数量大于2时,才会放入到RecycleViewPool中
  3. 为何说重写hasStableIds方法返回true会提高效率呢?
    • 复用方面: 首先看next()调用代码
    if (mAdapter.hasStableIds()) { //如果重写了,就尝试根据viewType从一级,二级缓存中去取
        holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                type, dryRun);
        if (holder != null) {
            // update position
            holder.mPosition = offsetPosition;
            fromScrapOrHiddenOrCache = true;
        }
    }
    
    • 回收方面:
     private void scrapOrRecycleView(Recycler recycler, int index, View view) {
        final ViewHolder viewHolder = getChildViewHolderInt(view);
        if (viewHolder.shouldIgnore()) {
            if (DEBUG) {
                Log.d(TAG, "ignoring view " + viewHolder);
            }
            return;
        }
        if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                && !mRecyclerView.mAdapter.hasStableIds()) { //如果为true,走下面
            removeViewAt(index);
            recycler.recycleViewHolderInternal(viewHolder); //全部回收到了cachedViews和RecycleViewPool中了
        } else {
            detachViewAt(index);
            recycler.scrapView(view); //回收到scrap数组中,一级缓存中
            mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
        }
    }
    
数据加载
//bound状态的解释: 该ViewHolder已绑定到一个位置; mPosition,mItemId和mItemViewType均有效。
if (mState.isPreLayout() && holder.isBound()) { //如果当前是bound,就不用在tryBindViewHolderByDeadline-> onBindViewHolder
    // do not update unless we absolutely have to.
    holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
    if (DEBUG && holder.isRemoved()) {
        throw new IllegalStateException("Removed holder should be bound and it should"
                + " come here only in pre-layout. Holder: " + holder
                + exceptionLabel());
    }
    final int offsetPosition = mAdapterHelper.findPositionOffset(position);
    //该方法会调用重写的onBindViewHolder
    bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}

//RecycleViewPool中去ViewHolder调用 resetInternal()重置ViewHolder
void resetInternal() {
    mFlags = 0; 
    ...
}

//而上方的判断hold.isBound() 
boolean isBound() {
    return (mFlags & FLAG_BOUND) != 0; //0 & 任何数 = 0 返回false
}
//因此从viewPool池中和oncreateView重建的都会重新绑定数据,而其他缓存则不用,可以直接复用
常见问题
  1. RecycleView 的notifyDataSetChanged导致图片闪烁的原因
    • 仅使用notifyDataSetChanged,在重新布局时,会先remove View,然后根据相关情形,缓存到mCachedViews , ViewCahceExtension(个人实现), RecycleViewPool中,在调用LayoutManager.next()方法时,去recycleView中取值复用,如果在pool中都无法取到,则会直接调用oncreateViewHolder重新inflate创建View, 导致闪烁
    • 使用notifyDataSetChanged() + hasStableIds() true , + 复写getItemId() :防止条目重复抖动 或者刷新单个条目,会缓存在mAttachedScrap,因此不会重新createViewHolder
上一篇 下一篇

猜你喜欢

热点阅读