Android篇

Recyclerview缓存复用机制

2021-03-14  本文已影响0人  w达不溜w

先了解一下Recyclerview主要有哪些类

类名 作用
LayoutManager 负责ItemView的布局和显示管理
ItemDecoration 给ItemView添加子View,如分割线
ItemAnimator 添加或删除的动画效果
Adapter 为ItemView创建视图
ViewHolder 承载包装ItemView
Recycler 四级缓存
Recycler

我们先要搞清楚缓存复用的对象是谁?ViewHolder(包装View Recyclerview的一个itemView)
Recycler是Recyclerview的内部类,它的主要成员变量是用来缓存和复用ViewHolder的。

public final class Recycler {
   
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
  
    ArrayList<ViewHolder> mChangedScrap = null;

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

    RecycledViewPool mRecyclerPool;

    private ViewCacheExtension mViewCacheExtension;
  
        //...
}

一级缓存:mAttachedScrap和mChangedScrap (用来缓存还在屏幕内的ViewHolder)

二级缓存:mCachedViews(用来缓存移出到屏幕之外的ViewHolder)

默认情况下缓存容量为2,如果mCachedViews容量已满,会根据FIFO的规则移除旧ViewHolder

三级缓存:mViewCacheExtension(开放给用户的自定义缓存)

四级缓存:mRecyclerPool(ViewHolder缓存池)

用SparseArray存ViewType和ArrayList<ViewHolder>,每个ViewType最多缓存5个ViewHolder。RecyclerPool只保存ViewType类型,不保存数据。

复用
我们从RecyclerView的滑动事件onTouchEvent和布局onLayout入手 RecyclerView.png

核心代码tryGetViewHolderForPositionByDeadline分析

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    
    //...
    if (mState.isPreLayout()) {
        // 0)从 mChangedScrap 里面去获取 ViewHolder,动画相关
        holder = getChangedScrapViewForPosition(position);
    }
   
    if (holder == null) {
      // 1) mAttachedScrap、mCachedViews
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    }
    if (holder == null) {

        if (mAdapter.hasStableIds()) {
          // 2) mAttachedScrap、mCachedViews (通过viewType,itemId获取)
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
           
        }
        if (holder == null && mViewCacheExtension != null) {
           // 3)mViewCacheExtension 自定义缓存 
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
            }
        }
        if (holder == null) { 
          // 4) 缓冲池里获取
            holder = getRecycledViewPool().getRecycledView(type);
           
        }
        if (holder == null) {
         
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
   
        }
    }

        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
  
    return holder;
}

可看到:

tryGetViewHolderForPositionByDeadline(从ViewHolder拿itemView)分情况获取

  1. getChangedScrapViewForPosition ----- mChangedScrap 动画相关

  2. getScrapOrHiddenOrCachedHolderForPosition ----- mAttachedScrap、mCachedViews

  3. getScrapOrCachedViewForId ----- mAttachedScrap、mCachedViews (通过viewType,itemId获取)

  4. mViewCacheExtension.getViewForPositionAndType ----- mViewCacheExtension 自定义缓存

  5. getRecycledViewPool().getRecycledView 从缓冲池里获取

当没有缓存的时候?—>mAdapter.createViewHolder—>onCreateViewHolder就是给我们重写的。

创建ViewHolder后绑定 —> tryBindViewHolderByDeadline—>mAdapter.bindViewHolder—>onBindViewHolder

缓存

什么时候缓存呢?

所谓缓存,就是RecyclerView怎么往四级缓存里添加数据的。

当我们调用notifyXXX的时候,会触发requestLayout方法,就会重新布局:

//调用链(源码太长,不一一贴出来,只贴核心代码,对照着源码看)
      RecyclerView.onLayout()
—>dispatchLayout()
—>dispatchLayoutStep2()
—>mLayout.onLayoutChildren
—>LinearLayoutManager.onLayoutChildren(recycler)
—>detachAndScrapAttachedViews(recycler)
—>RecyclerView.detachAndScrapAttachedViews
—>scrapOrRecycleView(recycler, i, v);  

跟踪到scrapOrRecycleView

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);
        //——>分析1 (缓存到 mCacheViews 和 RecyclerViewPool)
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        detachViewAt(index);
        //——>分析2 缓存到mAttachedScrap和mChangedScrap
        recycler.scrapView(view);
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}

——>分析1 缓存到 mCacheViewsRecyclerViewPool

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)) {
      // Retire oldest cached view
       int cachedViewSize = mCachedViews.size();
       if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
            //如果mCachedViews容量已满,会根据FIFO的规则取出旧ViewHolder,放到RecycledViewPool,然后再移除。
           recycleCachedViewAt(0);
           cachedViewSize--;
       }
        mCachedViews.add(targetCacheIndex, holder);
        cached = true;
    }
    
     if (!cached) {
         //ViewHolder有变化的放RecycledViewPool (RecyclerPool只保存ViewType类型,不保存数据)
         addViewHolderToRecycledViewPool(holder, true);
         recycled = true;
     }
  }
}

void recycleCachedViewAt(int cachedViewIndex) {
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}

——>分析2 缓存到mAttachedScrapmChangedScrap

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);
    //标记为移除或失效||没有改变||item无动画或动画不复用
    //放mAttachedScrap
    mAttachedScrap.add(holder);
  } else {
    if (mChangedScrap == null) {
      mChangedScrap = new ArrayList<ViewHolder>();
    }
    holder.setScrapContainer(this, true);
    //其它情况放也就是ViewHolder有改变
    //放mChangedScrap
    mChangedScrap.add(holder);
  }
}
上一篇下一篇

猜你喜欢

热点阅读