RecyclerView的缓存机制

作者: SHHO | 来源:发表于2019-04-09 21:52 被阅读36次

    RecyclerView的使用相信大家都很熟悉了,可以完全代替ListView和GridView,并且口味更佳,例如

    • 四级缓存,ListView只有两级缓存
    • Item添加动画
    • 数据局部跟新
    • 瀑布流。当然也可以自定义LayoutManager,规则布局你说了算
    • .....

    本文只讲解缓存机制。先来看一张Structure图

    写RecyclerView的大佬可能有内部类情节,所以整个RecyclerView下来有1.2w余行,这还不包括已经实现的几个LinearLayoutManager、DividerItemDecoration等等几个类,所以想看完整个源码是不可能的,看懂大概流程就可以了

    四级缓存

    RecyclerView的缓存由内部类Recycler负责,回收和复用都是以ViewHolder为单位

    第一级缓存

    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;
    

    第一级缓存有两个类型为ViewHolder的ArrayList集合,用来临时缓存屏幕中的Item。当数据更新重新绘制列表前会先清除所有Item,mChangedScrap保存被标记更新的Item,mAttachedScrap保存其他的Item

    第二级缓存

    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    

    第二级缓存保存刚刚被移除屏幕,保存的ViewHolder都是有效的,默认最大缓存是2。先进先出原则,当大于最大缓存数时,会先移除第一个,再添加

    第三级缓存

    private ViewCacheExtension mViewCacheExtension;
    

    第三级缓存Google没有实现,开发者一般也不会实现,可能是Google为了以后扩展。可以忽略

    第四级缓存

    RecycledViewPool mRecyclerPool;
    

    第四级缓存保存无效的ViewHolder,从二级缓存放进去的,默认大小是5
    RecycledViewPool有一个mScrap,保存不同类型的ViewHolder

    SparseArray<RecyclerView.RecycledViewPool.ScrapData> mScrap = new SparseArray();
    

    保存

    Adapter和RecycledView之间是通过观察者模式来实现数据变化通知。当调用Adapter一系列notify方法时,Adapter会通知观察者,RecyclerView的观察者RecyclerViewDataObserver就会执行对应的方法

    RecyclerViewDataObserver

    public void onChanged() {
      ...  
      RecyclerView.this.requestLayout();
      ...
    }
    
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
      ...
      this.triggerUpdateProcessor();
      ...
    }
    
    void triggerUpdateProcessor() {
      //先执行动画,最后还是跑不掉了requestLayout,代码不贴了
      if (RecyclerView.POST_UPDATES_ON_ANIMATION && RecyclerView.this.mHasFixedSize && RecyclerView.this.mIsAttached) {
        ViewCompat.postOnAnimation(RecyclerView.this, RecyclerView.this.mUpdateChildViewsRunnable);
      } else {
        RecyclerView.this.mAdapterUpdateDuringMeasure = true;
        RecyclerView.this.requestLayout();
      }
    }
    

    观察者的方法里都又直接间接地调用了RecyclerView的requestLayout(会执行View绘制的三部曲onMeasure、onLayout、onDraw),在onLayout经过层层调用到mLayout.onLayoutChildren,mLayout是LayoutManager,LayoutManager是抽象类,这里以LinearLayoutManager为例看一下onLayoutChildren方法的具体实现

    RecyclerView.java

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
      ...
      this.dispatchLayout();
      ...
    }
    
    void dispatchLayout() {
      ...
      if (this.mState.mLayoutStep == 1) {
        //预布局,记录数据更新时需要执行的动画的信息
        this.dispatchLayoutStep1();
        this.mLayout.setExactMeasureSpecsFrom(this);
        //实际布局
        this.dispatchLayoutStep2();
      } else if (!this.mAdapterHelper.hasUpdates() && this.mLayout.getWidth() == this.getWidth() && this.mLayout.getHeight() == this.getHeight()) {
        this.mLayout.setExactMeasureSpecsFrom(this);
      } else {
        this.mLayout.setExactMeasureSpecsFrom(this);
        this.dispatchLayoutStep2();
      }
      
      //执行动画
      this.dispatchLayoutStep3();
      ...
    }
    
    private void dispatchLayoutStep2() {
      ...
      //布局
      mLayout.onLayoutChildren(mRecycler, mState);
      ...
    }
    

    LinearLayoutManager.java

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
      if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
        if (state.getItemCount() == 0) {
          //1.清空列表,回收所有的View,return
          removeAndRecycleAllViews(recycler);
          return;
        }
      }
    
      ...
      //2.临时回收
      detachAndScrapAttachedViews(recycler);
      ...
    }
    

    调用流程,代码不粘了,很简单

    1. LayoutManager.removeAndRecycleAllViews ->
      LayoutManager.removeAndRecycleViewAt ->
      Recycler.recycleViewHolderInternal()
    2. LayoutManager.detachAndScrapAttachedViews ->
      Recycler.scrapOrRecycleView ->
      Recycler.scrapView或者Recycler.recycleViewHolderInternal()

    Recycler

    void recycleViewHolderInternal(ViewHolder holder) {
      ...
      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) {
          //超过最大缓存数,删除第一个
          recycleCachedViewAt(0);
          cachedViewSize--;
        }
        ...
        //存到第二级缓存
        mCachedViews.add(targetCacheIndex, holder);
        cached = true;
      }
      
      if (!cached) {
        //没有存到第二季缓存的话,存第四级
        addViewHolderToRecycledViewPool(holder, true);
        recycled = true;
      }
      ...
    }
    
    //临时缓存,数据未改变,可以直接使用
    void scrapView(View view) {
      final ViewHolder holder = getChildViewHolderInt(view);
      if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                   
        holder.setScrapContainer(this, false);
        //没有变化的
        mAttachedScrap.add(holder);
      } else {
        if (mChangedScrap == null) {
          mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        //位置变化的
        mChangedScrap.add(holder);
      }
    }
    

    复用

    在LayoutManager的onLayoutChildren里面调用fill()为RecyclerView填充Item,fill里面循环调用layoutChunk来addView,直到填满屏幕

    LinearLayoutManager.java

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
      ...
      fill(recycler, mLayoutState, state, false);
      ...
    }
    
    
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
                RecyclerView.State state, boolean stopOnFocusable) {
      ...
      while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        if (layoutChunkResult.mFinished) {
          break;
        }
        ...
        if (stopOnFocusable && layoutChunkResult.mFocusable) {
          break;
        }
        ...
      }
      ...
      return start - layoutState.mAvailable;
    }
    
    
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
      ...
      View view = layoutState.next(recycler);
      ...
    }
    
    View next(RecyclerView.Recycler recycler) {
      ...
      //通过position获取View
      final View view = recycler.getViewForPosition(mCurrentPosition);
      mCurrentPosition += mItemDirection;
      return view;
    }
    

    Recycler
    最终获取缓存都会调用Recycler的tryGetViewHolderForPositionByDeadline方法,这个方法里可以看到尝试从四级缓存中获取ViewHolder,如果获取不到就重新创建ViewHolder

    public View getViewForPosition(int position) {
      return getViewForPosition(position, false);
    }
    
    View getViewForPosition(int position, boolean dryRun) {
      return tryGetViewHolderForPositionByDeadline(position,dryRun,FOREVER_NS).itemView;
    }
    
    //非常重要的一个方法
    @Nullable
    ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
      ...
      ViewHolder holder = null;
      //通过mChangedScrap获取缓存
      if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
      }
    
      ...
      //通过mAttachedScrap或者mCachedViews获取缓存
      if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
      }
    
      ...
      //通过StableId进行获取缓存,也是从mAttachedScrap或者mCachedViews获取
      if (holder == null) {
        ...
        if (mAdapter.hasStableIds()) {
        holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),  type, dryRun);
      }
    
      //通过mViewCacheExtension获取缓存
      if (holder == null && mViewCacheExtension != null) {
        final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
        if (view != null) {
          holder = getChildViewHolder(view);
        }
      }
      
      //通过mRecyclerPool获取缓存
      if (holder == null) {
        holder = getRecycledViewPool().getRecycledView(type);
      }
      
      //只能创建ViewHolder
      if (holder == null) {
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
      }
     
      return holder;
    }
    

    这就是RecyclerView的四级缓存机制,其实也就三级,ViewCacheExtension这里是空实现

    脑瓜疼,想喝手磨咖啡

    End

    相关文章

      网友评论

        本文标题:RecyclerView的缓存机制

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