RecyclerView源码分析(二)--缓存机制(二)

353 阅读2分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

我们理解RecyclerView的缓存的话,可以从三个问题入手:

  • 缓存是什么?复用是什么
  • 缓存到哪里去了?又从哪里获得的复用
  • 什么时候缓存?什么时候复用

RecyclerView是如何从缓存取出来数据来呢?

我们可以想一下,RecyclerView的复用是不是滑动的时候产生的呢?基于这一点我们可以先看看onTouchEvent逻辑,会不会发现有用的信息

  • onTouchEvent中有个scrollByInternal() 是进行Recycler的滑动
public boolean onTouchEvent(MotionEvent e) {
    ...
    switch (action) {
        case MotionEvent.ACTION_MOVE: {
            if (scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0,e)) {
                 getParent().requestDisallowInterceptTouchEvent(true);
            }
        }
    }
}
  • scrollByInternal 执行scrollStep()方法
boolean scrollByInternal(int x, int y, MotionEvent ev) {
   if (mAdapter != null) {
       scrollStep(x, y, mReusableIntPair);
   }
}
  • scrollStep 执行水平/垂直滚动逻辑,我们以LinearLayoutManager为例,看一下它的scrollVerticallyBy()这个方法具体执行逻辑
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
    if (dx != 0) {
        consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
    }
    if (dy != 0) {
        consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
    }
  • scrollVerticallyBy()调用了scrollBy(),我们看看scrollBy()方法中有什么操作
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,RecyclerView.State state) {
    ...
    return scrollBy(dy, recycler, state);
}
  • scrollBy中执行了fill()方法
  • fill() 开始ItemView的布局
int scrollBy(...) {
    final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
}

int fill(...) {
    layoutChunk(recycler, state, layoutState, layoutChunkResult);
  • layoutChunk() 是填充ItemView的核心方法,这个方法执行一次就填充一个ItemView到屏幕上
void layoutChunk(...){
    View view = layoutState.next(recycler);
}
  • next()是从一二级缓存中获取或者是创建一个ItemView
View next(RecyclerView.Recycler recycler) {
    final View view = recycler.getViewForPosition(mCurrentPosition);
}
  • getViewForPosition() 会通过position获取一个HolderView实例,然后调用到tryGetViewHolderForPositionByDeadline()方法
public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
     return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
  • tryGetViewHolderForPositionByDeadline() 就是获取ViewHolder机制的逻辑了

我们先复习一下ViewHolder的四级缓存

  • mChangeScrap 和 mAttachedScrap (屏幕内的缓存数据,mChangeScrap表示数据已经改变的ViewHolder列表,mAttachedScrap表示数据已经改变的ViewHolder列表)
  • mCachedViews (刚刚移除屏幕的缓存数据)
  • mViewCacheExtension (开发者自定义缓存,需要自己管理View的创建和缓存)
  • RecycledViewPool (ViewHolder的缓存池)
ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
if (holder == null) {
    holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
}
if (holder == null) {
    final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
}
if (holder == null) {
    holder = getRecycledViewPool().getRecycledView(type);
}
if (holder == null) {
    holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
  • 如果这四个缓存都取不出来的话,就会调用createViewHolder创建一个ViewHolder,创建完ViewHolder了,就需要绑定数据,绑定数据也是在tryGetViewHolderForPositionByDeadline()调用tryBindViewHolderByDeadline()
  • tryBindViewHolderByDeadline()中调用bindViewHolder(),也就是我们new Adapter的时候,写的onBindViewHolder()方法
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,int position, long deadlineNs) {
    mAdapter.bindViewHolder(holder, offsetPosition);
}

RecyclerView的缓存已经分析的差不多了,我们可以做一个总结:

RecyclerView是四级缓存,有三个类共同完成的(Recycler、RecyclerViewPool,ViewCacheExtension)。Recycler管理已经废弃或者与RecyclerView分离的ViewHolder。RecyclerViewPool类是用来缓所有的ViewHolder。ViewCacheExtension是扩展内的缓存对象,默认是不加载的,需要自己实现。