美文网首页源码分析
RecycleView缓存原理

RecycleView缓存原理

作者: 唯爱_0834 | 来源:发表于2020-06-23 11:31 被阅读0次
    • 首先看一下为何说RecycleView(主要有适配器模式,观察者模式adapter通知更新UI)中的adapter是个适配器模式!

    适配器模式

    • 在使用recycleView时最常用的就是adapter,也叫作适配器模式,那我们看看都有哪些吧
    类适配器
    • 类适配器通过集成与adaptee也就是继承关系,并且实现target方法,重写target方法中调用adaptee的方法,这个是静态的方式
    • 而对象适配器通过委派与adaptee即持有adaptee对象,是动态的方式
    • 类适配器可以重定义实现行为,而对象适配器重定义适配的行为比较困难,但是添加行为较方便。
    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();
        }
    }
    
    • 类跟对象适配器区别: 类适配器模式需要自身来创建一个adaptee, 对象适配器模式可以直接使用一个已有的adaptee实例来转换接口

    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() {
            }
    
    • 通过以上分析,我们使用了RecycleView.Adapter是一个抽象类定义的适配器,这个抽象类的方法要提供给RecycleView内部使用的,因此实现一个FootAdapter的实现类,然后传递给RecycleView.setAdapter(adapter)就是一个对象适配器
    • 而且对象适配器是可以随时更换的,我们在RecycleView中也是可以更换Adapter的具体实现类从而根据不同的类型加载不同的Adapter实现不同的界面!
    • 查看setAdapter的创建
     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的缓存
    • 之所以使用RecycleView替换掉ListView是因为其优秀的缓存机制
    ListView缓存
    • ListView只有两级缓存,其是RecycleBin缓存机制,
      1. mActiveViews :一级是缓存屏幕上可见的view
      2. mScrapViews : 二级缓存ListView中所有废弃的,这是一个ArrayList<View>[] 数组,每一种type对应一个自己的arrayList缓存
    • 区别: RecycleView缓存的是ViewHolder,而list只是缓存View,需要自己构建一个ViewHolder对象
    四级缓存
    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的几个状态值
    • 通过阅读RecycleView的源码时,到处看到ViewHolder的isInvalid ,isRemoved , isBound , isTmpDetached , isScrap . isUpdated 这几个方法,其实是个状态机
    • isInvalid : flag_invalid : 表示当前ViewHolder是否已经失效,通常由三种情况会出现这种状况
      1. 调用了adapter.notifyDataSetChanged()方法
      2. 手动调用RecycleView.invalidateItemDecorations()方法
      3. 调用RecycleView的setAdapter方法或swapAdapter()方法
    • isRemoved : Flag_removed : 表示当前ViewHolder是否被移除, 通常来说,数据源被移除了部分数据,然后调用了adapter.notifyItemRemoved()
      • 即data中移除了一条数据以后调用
      public void removeItem(int position){
         data.remove(posiiton);
         notifyItemRemoved(position);
         notifyItemRangeChanged(position, data.size() - position); //不加这个position位置会错乱的.onBindViewHolder也不会调用
      }
      
    • isBound: flag_bound : 表示当前ViewHolder是否已经调用了onBindViewHolder
    • isTmpdetached : flag_tmp_detached : 表示当前的ItemView是否从RecycleView(父View)detach掉,通常由两种情况:
      1. 手动调用了RecycleView.detachView相关方法
       public void detachViewFromParent(int offset) {
                  final View view = getChildAt(offset);
                  if (view != null) {
                      final ViewHolder vh = getChildViewHolderInt(view);
                      if (vh != null) {
                          if (vh.isTmpDetached() && !vh.shouldIgnore()) {
                              throw new IllegalArgumentException("called detach on an already"
                                      + " detached child " + vh + exceptionLabel());
                          }
                          if (DEBUG) {
                              Log.d(TAG, "tmpDetach " + vh);
                          }
                          vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); //detachView(View view)调用的回调,设置Flag
                      }
                  }
                  RecyclerView.this.detachViewFromParent(offset);
              }
      
      1. 从mHideViews中获取viewHolder,会先detach掉这个ViewHolder关联的ItemView
    • isScrap : 无flag状态,用mScrapContainer是否为null来判断: 表示是否在MAttachedScrap或mChangeScrap数组里面,进而表示当前ViewHolder是否被废弃掉
    • isUpdated: flag_update : 表示当前ViewHolder是否已经跟新,通常由三种情况:
      1. isInvalid 方法存在的三种情况
      2. 调用了adapter.onBindviewHolder方法
      3. 调用了adapter.notifyItemChanged方法
    • 再上方我们有一个mHiddenViews,并不算入四级缓存中,因为其只在动画期间才会有元素,当动画结束后就清空了,自然不能算到四级缓存中
    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());
        }
    
    • 则mAttachedScrap里放着两种状态的ViewHolder:
      1. 被同时标记为remove和invalid
      2. 完全没有改变的viewHolder
      3. 这个跟RecyclerView的ItemAnimator有关,如果ItemAnimator为空或者ItemAnimator的canReuseUpdatedViewHolder方法为true,也会放入到mAttachedScrap。那正常情况下,什么情况返回为true呢?从SimpleItemAnimator的源码可以看出来,当ViewHolder的isInvalid方法返回为true时,会放入到 mAttachedScrap里面。也就是说,如果ViewHolder失效了,也会放到mAttachedScrap里面。
    • MchangedScrap 里面放的是isUpdated的返回true的情况,即调用了adapter.notifyItemChanged()并且RecycleView的ItemAnimator不为空时加入
    • 同时看一下mAttachedScrap / mChangedScrap 的scrap数组和mHiddenViews的区别
      1. mHiddenViews只存放动画的ViewHolder,动画结束自然就清空了,用于动画期间进行复用的可能性
      2. scrap数组和mHiddenViews两者不冲突,可能都存在相同的Viewholder,但是这并不影响,动画结束时会从mHiddenViews中移除的
    RecycleView的复用
    • 通过上面的分析只是说明了四级缓存的性质,但是并没有说明他的复用原理;我们从LayoutState.next()方法开始,我们知道RecycleView是交给LayoutManager在布局itemView时,需要获取一个ViewHolder对象,通过这个方法来获取的,自然复用逻辑也是在这里的
      • next() 调用规则: RecycleView中的重写View的scrollby ->scrollByInternal() -> mLayout.scrollHorizontallyBy()/mLayout.scrollVerticallyBy()-> LayoutManager中的->scrollBy() ->fill() ->next() 滑动时调用
    //我们以 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;
                }
            }
    
    • 可以看到LinerLayoutManager中通过next获取recycleView的下一个viewHolder对象,调用recycleView.getViewForPosition 最终调用的是 tryGetViewHolderForPositionByDeadline() ,这个才是真正复用的核心
    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 中获取ViewHolder,这里面存放的是更新的ViewHolder
      2. 分别从mAttachedScrap,mHiddenViews, mCachedViews获取ViewHolder
    1. 首先看从mChangedScrap中获取:如果当前是预布局阶段,则从mChangedScrap中获取ViewHolder
      • 预布局: preLayout,即当前RecycleView处于dispatchLayoutStep1阶段时,称为预布局
      • dispatchLayoutStep2阶段被称为真正的布局阶段
      • dispatchLayoutStep3称为postLayout阶段
      • 同时要想真正开启预布局,必须要有itemAnimator,并且每个recycleView对应的LayoutManager必须开启预处理动画
    • 只有当ItemAnimator不为空,被changed的ViewHolder会放在mChangedScrap数组里面。因为chang动画前后相同位置上的ViewHolder是不同的,所以当预布局时,从mChangedScrap缓存里面去,而正式布局时,不会从mChangedScrap缓存里面去,这就保证了动画前后相同位置上是不同的ViewHolder
    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;
                            }
                        }
                    }
    
    • 分别从mAttachedScrap. mHiddenViews,mCachedViews获取ViewHolder,但是还得判断ViewHolder是否有效,如果无效需要做一些清理操作从一级缓存中remove,然后重新放入到mCacheViews或者RecycleViewPool缓存里面
      • 因此从一级,二级缓存中取值,其ViewHolder的原本数据信息都在,直接添加到RecycleView中显示,不需要重新走onBindViewHolder()方法赋值了,只有原来的子项可以重新复用,新建的子项无法从该两级缓存中取值的
      • 而mREcycleViewPool:源码中只是缓存ViewHolder并重置数据,相当于一个新建的ViewHolder,只是不走onCreateView()方法,使用时需要重新调用onBindViewHolder()来绑定数据
    • 首先看从各种缓存中取值
    
    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;
            }
    
    • 上面分析的是通过position来获取ViewHolder,我们验证了position合法,则viewType肯定是正确对应的,但是通过viewType获取viewHolder时,position可能失效了,通过viewType可分为三步:
      1. 如果adapter的hasStableIds = true,优先通过viewType和id两个条件寻找
      2. 如果未找到,如果adapter.hasStablleIds = false,首先viewCacheExtension里面找,如果还未找到,最后会在RecycleViewPool里面获取ViewHolder
      3. 如果以上都没有找到合适的ViewHolder,最后会调用adapter的onCreateViewHolder创建一个新的ViewHolder对象
    • 首先上面我们只是分析了前两种缓存操作,第三种个人设置的不分析,则第四种 从 RecycleViewPool中获取缓存如何做的呢?首先搞清楚他是啥?
    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的复用机制,下面我们看一下回收机制
    RecycleView回收机制
    • 所有的框架一单存在复用,当然首次创建以后肯定会有回收,否则怎么复用呢?同时理解这两个过程,对于我们使用RecycleView及其原理分析会更加清晰明白!
    • 首先复习一下复用过程: 回收当然也是一一对应的
      1. 从scrap数组中查找是否可用,可用直接复用
      2. 从mCachedViews数组中查找是否可用
      3. 从mHiddenViews数组中查找,这是一个关于动画的数组
      4. RecycleViewPool根据type查找是否可用,如果存在则返回布局
      5. 都没有找到就从adapter.onCreateViewHolder创建一个新的ViewHolder对象返回
    • 则对应的回收过程为:
      1. scrap数组
      2. mCachedViews数组
      3. mHiddenViews数组
      4. RecycleViewPool数组
    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);
        }
    }
    
    • 在文章的最开始已经分析过add添加到两个scrap数组中的区别,这里只看scrapView都是那些地方调用的
      1. getScrapOrHiddenOrCachedHolderForPosition() : 如果从mHiddenViews中获取一个ViewHolder时,会先将这个ViewHolder从mHiddenViews中移除,然后调用scrapView方法将这个ViewHolder放入到scrap数组中
      if (!dryRun) {
          View view = mChildHelper.findHiddenNonRemovedView(position);
          if (view != null) {
              // This View is good to be used. We just need to unhide, detach and move to the
              // scrap list.
              final ViewHolder vh = getChildViewHolderInt(view);
              mChildHelper.unhide(view);
              int layoutIndex = mChildHelper.indexOfChild(view);
              if (layoutIndex == RecyclerView.NO_POSITION) {
                  throw new IllegalStateException("layout index should not be -1 after "
                          + "unhiding a view:" + vh + exceptionLabel());
              }
              mChildHelper.detachViewFromParent(layoutIndex);
              scrapView(view);
              vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                      | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
              return vh;
          }
      }
      
      1. 在LayoutManager中的scrapOrRecycleView()方法中调用,有两种情况
        1. 手动调用了LayoutManager相关方法
        2. RecycleView进行了一次布局(调用了requestLayout方法)
      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()) {
              removeViewAt(index);
              recycler.recycleViewHolderInternal(viewHolder);
          } else {
              detachViewAt(index);
              recycler.scrapView(view);
              mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
          }
      }
      
    1. mCacheViews回收
    • mCacheViews回收路径比较多,大致分为三类:
      1. 重新布局回收的,这种情况主要是调用adapter.notifyDataSetChange(),并且hasStableIds返回了false,这里就可以看出为何调用notifyDataSetChange()方法效率如此低效,同时也知道了为何重写hasStableIds方法可以提高效率,因为notifyDataSetChange使得RecycleView将回收的ViewHolder放在了二级 缓存中,效率自然比较低,重写hasStableIds返回true,则只刷新变化的,没变的就不会刷新了
      //notifyDataSetChange()调用Observable的轮询onChanged
       public void onChanged() {
              RecyclerView.this.assertNotInLayoutOrScroll((String)null);
              RecyclerView.this.mState.mStructureChanged = true;
              RecyclerView.this.processDataSetCompletelyChanged(true);
              if (!RecyclerView.this.mAdapterHelper.hasPendingUpdates()) {
                  RecyclerView.this.requestLayout(); //请求重新布局
              }
      
          }
      //调用这个清除CacheViews
       if (RecyclerView.this.mAdapter == null || !RecyclerView.this.mAdapter.hasStableIds()) {
                  this.recycleAndClearCachedViews();
              }
      
      1. 在复用时,从一级缓存里面获取到ViewHolder,但此时这个ViewHolder已经不符合一级缓存的特点了(如position失效,跟ViewType对不齐),就会从一级缓存里面移除这个ViewHolder添加到mCacheViews里面
      2. 当调用removeAnimatingView方法时,如果当前ViewHolder被标记为remove,会调用recycleViewHolderInternal()回收对应的ViewHolder调用removeAnimatingView方法的时机表示当前ItemAnimator已经做完了
    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);
          }
      }
      
    数据加载
    • 对于上方无论是从缓存中去数据还是新建onCreateView中获取一个ViewHolder,当然需要加载数据了.通过上面分析我们知道,缓存ViewHolder在一级二级缓存中都带有数据的,可以直接复用,但是对于四级或者新建的都相当于重建一个ViewHolder,其中是没有数据的,我们需要绑定走OnBindViewHolder
      • 依然是recycleView中的tryGetViewHolderForPositionByDeadline方法中
    //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重建的都会重新绑定数据,而其他缓存则不用,可以直接复用
    
    • 至此,recycleView布局和加载数据都讲解完成了, 布局的加载是委托给LayoutManager实现,而数据是托付给adapter的onBindViewHolder,recycleView只负责对viewHolder的缓存和交互,通过观察者模式对adapter设置Observable,一旦有数据更新则通知view重绘requestLayout
    常见问题
    1. RecycleView 的notifyDataSetChanged导致图片闪烁的原因
      • 仅使用notifyDataSetChanged,在重新布局时,会先remove View,然后根据相关情形,缓存到mCachedViews , ViewCahceExtension(个人实现), RecycleViewPool中,在调用LayoutManager.next()方法时,去recycleView中取值复用,如果在pool中都无法取到,则会直接调用oncreateViewHolder重新inflate创建View, 导致闪烁
      • 使用notifyDataSetChanged() + hasStableIds() true , + 复写getItemId() :防止条目重复抖动 或者刷新单个条目,会缓存在mAttachedScrap,因此不会重新createViewHolder

    相关文章

      网友评论

        本文标题:RecycleView缓存原理

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